Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RELEASE] Utltimate Ulmus #621

Merged
merged 62 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
008cfc3
[TM-1400] Add the tree_species_research table and the connection from…
roguenet Nov 25, 2024
4ba206a
[TM-1400] Implement script to ingest classification.csv into the tree…
roguenet Nov 25, 2024
9b16f76
[TM-1400] Also modify the v2_seedings table.
roguenet Nov 26, 2024
be70f3b
[TM-1400] Update "restored" collections to "historical-tree-species"
roguenet Nov 26, 2024
6f66d69
[TM-1400] Update collections that are missing or incorrect.
roguenet Nov 26, 2024
5909214
[TM-1400] Fix a couple places that missed the org property name update.
roguenet Nov 26, 2024
4e30e3b
[TM-1400] Remove type from the tree species test factory.
roguenet Nov 26, 2024
9fa19bf
[TM-1523] improve validation (#592)
egrojMonroy Nov 29, 2024
1b98f92
[TM-1528] send cover image for project in dashboard (#594)
cesarLima1 Nov 29, 2024
7873ac9
[TM-1523] send to python overlap only intersecting polygons. (#595)
egrojMonroy Dec 2, 2024
1c2c749
[TM-1536] add delayed job progress (#593)
LimberHope Dec 2, 2024
94da7ab
Merge pull request #596 from wri/main
roguenet Dec 2, 2024
6d660a1
Merge pull request #588 from wri/feat/TM-1400-taxonomic-schema
roguenet Dec 2, 2024
2b7366c
Merge pull request #600 from wri/main
roguenet Dec 4, 2024
9b433dc
[TM-1401] Implement CSV import for cleaned tree species.
roguenet Dec 5, 2024
14f9c7a
[TM-1401] Establish the belongsTo association between v2_tree_species…
roguenet Dec 5, 2024
21b8138
[TM-1401] lint fix
roguenet Dec 5, 2024
cb17f0f
[TM-1401] Run the test suite on all pull requests.
roguenet Dec 5, 2024
b05d9cb
Merge pull request #601 from wri/feat/TM-1401-cleaned-tree-species
roguenet Dec 6, 2024
2619ca9
[TM-1531] delayed job with data (#603)
egrojMonroy Dec 9, 2024
c870bee
feat: added new field for PR and SR
Scriptmatico Dec 9, 2024
9c5ad6c
Merge pull request #604 from wri/TM-1542
Scriptmatico Dec 10, 2024
94bef93
[TM-1564] change logic (#605)
LimberHope Dec 10, 2024
165c7b9
[TM-1402] Include taxon_id in the tree species resource.
roguenet Dec 10, 2024
3af8790
[TM-1402] Updates to data import for tree species.
roguenet Dec 10, 2024
888f1d6
[TM-1402] Update the saved form fields for tree species collection.
roguenet Dec 11, 2024
00591ef
[TM-1425] dev monitoring indicators (#602)
LimberHope Dec 11, 2024
027ac18
[TM-1564] add validation to verify if exist project (#606)
LimberHope Dec 12, 2024
5a47a49
[TM-1542] change input type for pct_survival_to_date (#607)
pachonjcl Dec 12, 2024
8a0dc46
[TM-1425] sort categories and add validation to missing slug (#608)
LimberHope Dec 12, 2024
c8fe1f9
[TM-1402] Trim the headers on tree species assocations.
roguenet Dec 13, 2024
3a83f0b
[TM-1531] delayed job with data (#610)
cesarLima1 Dec 13, 2024
0e76817
update requirements file (#611)
LimberHope Dec 13, 2024
46f3f71
Merge pull request #609 from wri/feat/TM-1402-tree-species-design-int…
roguenet Dec 13, 2024
11f3a08
[TM-1398] Merge remote-tracking branch 'origin/staging' into epic/TM-…
roguenet Dec 13, 2024
26303ae
Merge pull request #613 from wri/epic/TM-1398-tree-species
roguenet Dec 13, 2024
0b41066
[TM-1467] update requirements file (#612)
LimberHope Dec 14, 2024
524aab9
[TM-1400] Switch to bulk inserts to speed things up.
roguenet Dec 16, 2024
bc4f6d9
[TM-1400] A quick google claims 1k rows is a good number for batched …
roguenet Dec 16, 2024
62db653
[TM-1400] Fix reference to scientific name.
roguenet Dec 16, 2024
7c5641a
Merge pull request #614 from wri/feat/TM-1400-research-insert-fixes
roguenet Dec 16, 2024
2f5acb6
[TM-1400] Allow null in infraspecific_epithet.
roguenet Dec 16, 2024
fd95eda
Merge pull request #615 from wri/feat/TM-1400-research-insert-fixes
roguenet Dec 16, 2024
50aa495
[TM-1400] Allow null in specific epithet.
roguenet Dec 16, 2024
d66b705
Merge pull request #616 from wri/feat/TM-1400-research-insert-fixes
roguenet Dec 16, 2024
ffdbff1
[TM-1466] remove cached functinality from Redis (#618)
LimberHope Dec 17, 2024
e3d0e8c
[TM-1531] delayed job with data (#617)
egrojMonroy Dec 17, 2024
a5ee992
[TM-1579] A script to update v2_tree_species with a taxon_id when the…
roguenet Dec 17, 2024
a5b3be3
Merge pull request #620 from wri/feat/TM-1579-exact-match-tree-cleaning
roguenet Dec 18, 2024
6c90fbd
[TM-1531] delayed job with data (#619)
egrojMonroy Dec 18, 2024
c477f52
[TM-1581] Respect the UUID the client sends when creating.
roguenet Dec 18, 2024
d75fd98
[TM-1581] To actually use the UUID from the client, it needs to be fi…
roguenet Dec 18, 2024
95ebc85
[TM-1583] fix email polygon validation link based on role (#625)
pachonjcl Dec 19, 2024
1015d07
[TM-1583] fix email polygon validation link (#626)
pachonjcl Dec 19, 2024
70f4388
[TM-1562] add new fields to resources (#627)
LimberHope Dec 19, 2024
48be67b
[TM-1531] delayed job with data (#624)
egrojMonroy Dec 19, 2024
082ab4e
Merge pull request #628 from wri/fix/TM-1581-tree-collections
roguenet Dec 19, 2024
4f4d8a2
[TM-1562] add count to replanting collection (#629)
LimberHope Dec 19, 2024
be8bde2
[TM-1467] polygon area discrepancy (#622)
cesarLima1 Dec 19, 2024
10ae0cc
[TM-1467] dev monitoring indicators update command (#630)
LimberHope Dec 20, 2024
0d3763a
[TM-1467] Adjust GFW data areas with correction factor using ESRI:540…
cesarLima1 Dec 20, 2024
8f21795
[TM-1531] remove entity from delayedjobs and create metadata (#632)
egrojMonroy Dec 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: pull-request
on:
pull_request:
branches: [main, staging, release/**]
jobs:
lintTest:
runs-on: ubuntu-latest
Expand Down
103 changes: 103 additions & 0 deletions app/Console/Commands/ImportTreeSpeciesAssociations.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace App\Console\Commands;

use App\Console\Commands\Traits\Abortable;
use App\Console\Commands\Traits\AbortException;
use App\Console\Commands\Traits\ExceptionLevel;
use App\Models\V2\TreeSpecies\TreeSpecies;
use App\Models\V2\TreeSpecies\TreeSpeciesResearch;
use Illuminate\Console\Command;
use Symfony\Component\Process\Process;

class ImportTreeSpeciesAssociations extends Command
{
use Abortable;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'import-tree-species-associations {file}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Imports a CSV that links UUIDs from v2_tree_species to taxon_ids from tree_species_research';

protected int $treeSpeciesUuidColumn;

protected int $taxonIdColumn;

/**
* Execute the console command.
*/
public function handle()
{
$this->executeAbortableScript(function () {
$process = new Process(['wc', '-l', $this->argument('file')]);
$process->run();
$this->assert($process->isSuccessful(), "WC failed {$process->getErrorOutput()}");

$lines = ((int)explode(' ', $process->getOutput())[0]) - 1;

$fileHandle = fopen($this->argument('file'), 'r');
$this->parseHeaders(fgetcsv($fileHandle));

$this->withProgressBar($lines, function ($progressBar) use ($fileHandle) {
$abortExceptions = [];
while ($csvRow = fgetcsv($fileHandle)) {
$treeSpeciesUuid = $csvRow[$this->treeSpeciesUuidColumn];
$taxonId = $csvRow[$this->taxonIdColumn];

if ($taxonId != 'NA') {
try {
$research = TreeSpeciesResearch::find($taxonId);
$this->assert($research != null, "Taxon ID not found: $taxonId", ExceptionLevel::Warning);

TreeSpecies::isUuid($treeSpeciesUuid)->update([
'taxon_id' => $taxonId,
'name' => $research->scientific_name,
]);
} catch (AbortException $e) {
$abortExceptions[] = $e;
}
}

$progressBar->advance();
}

$progressBar->finish();

if (! empty($abortExceptions)) {
$this->warn("Errors and warnings encountered during parsing CSV Rows:\n");
foreach ($abortExceptions as $error) {
$this->logException($error);
}
}
});

fclose($fileHandle);
});
}

protected function parseHeaders(array $headerRow): void
{
foreach ($headerRow as $index => $header) {
$header = trim($header, "\xEF\xBB\xBF\"");
if ($header == 'tree_species_uuid') {
$this->treeSpeciesUuidColumn = $index;
} elseif ($header == 'taxon_id') {
$this->taxonIdColumn = $index;
}
}

$this->assert(
is_numeric($this->treeSpeciesUuidColumn) && is_numeric($this->taxonIdColumn),
'Not all required columns were found'
);
}
}
43 changes: 43 additions & 0 deletions app/Console/Commands/OneOff/AssociateExactMatchTrees.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Console\Commands\OneOff;

use App\Models\V2\TreeSpecies\TreeSpecies;
use Illuminate\Console\Command;

class AssociateExactMatchTrees extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'one-off:associate-exact-match-trees';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Update tree species rows without a taxon_id but that do have an exact match in the backbone.';

/**
* Execute the console command.
*/
public function handle()
{
TreeSpecies::withoutTimestamps(function () {
$query = TreeSpecies::withTrashed()
->join('tree_species_research', 'v2_tree_species.name', '=', 'tree_species_research.scientific_name')
->where('v2_tree_species.taxon_id', null);
$this->withProgressBar((clone $query)->count(), function ($progressBar) use ($query) {
$query->chunkById(100, function ($trees) use ($progressBar) {
foreach ($trees as $tree) {
TreeSpecies::where('id', $tree->id)->update(['taxon_id' => $tree->taxon_id]);
$progressBar->advance();
}
});
});
});
}
}
132 changes: 132 additions & 0 deletions app/Console/Commands/OneOff/PopulateTreeSpeciesResearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

namespace App\Console\Commands\OneOff;

use App\Console\Commands\Traits\Abortable;
use App\Console\Commands\Traits\AbortException;
use App\Console\Commands\Traits\ExceptionLevel;
use App\Models\V2\TreeSpecies\TreeSpeciesResearch;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Symfony\Component\Process\Process;

class PopulateTreeSpeciesResearch extends Command
{
use Abortable;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'one-off:populate-tree-species-research {file}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Given an input file, populates the tree_species_research table';

// The names of the columns we require for inserting into the DB. Key is column name in the header row of the CSV,
// value is the column name in the DB definition
protected const COLUMN_MAPPING = [
'taxonID' => 'taxon_id',
'scientificName' => 'scientific_name',
'family' => 'family',
'genus' => 'genus',
'specificEpithet' => 'specific_epithet',
'infraspecificEpithet' => 'infraspecific_epithet',
];

// Populated by parseHeaders(), a mapping of DB colum name to the index in each row where that data is expected to
// exist
protected $columns = [];

/**
* Execute the console command.
*/
public function handle()
{
$this->executeAbortableScript(function () {
$process = new Process(['wc', '-l', $this->argument('file')]);
$process->run();
$this->assert($process->isSuccessful(), "WC failed {$process->getErrorOutput()}");

$lines = ((int)explode(' ', $process->getOutput())[0]) - 1;

$fileHandle = fopen($this->argument('file'), 'r');
$this->parseHeaders(fgetcsv($fileHandle));

$this->withProgressBar($lines, function ($progressBar) use ($fileHandle) {
$abortExceptions = [];
$bulkInsert = [];
while ($csvRow = fgetcsv($fileHandle)) {
$data = [];
foreach ($this->columns as $column => $index) {
$data[$column] = $csvRow[$index] == 'NA' ? null : $csvRow[$index];
}

// These don't get set automatically with bulk insert
$now = Carbon::now();
$data['created_at'] = $now;
$data['updated_at'] = $now;

try {
$existing = TreeSpeciesResearch::where('scientific_name', $data['scientific_name'])->first();
$this->assert(
$existing == null,
'Scientific name already exists, skipping: ' . json_encode([
'existing_id' => $existing?->taxon_id,
'new_id' => $data['taxon_id'],
'scientific_name' => $data['scientific_name'],
'infraspecific_epithet' => $data['infraspecific_epithet'],
], JSON_PRETTY_PRINT),
ExceptionLevel::Warning
);

$bulkInsert[] = $data;
if (count($bulkInsert) >= 1000) {
TreeSpeciesResearch::insert($bulkInsert);
$bulkInsert = [];
}
} catch (AbortException $e) {
$abortExceptions[] = $e;
}
$progressBar->advance();
}

$progressBar->finish();

if (! empty($abortExceptions)) {
$this->warn("Errors and warnings encountered during parsing CSV Rows:\n");
foreach ($abortExceptions as $error) {
$this->logException($error);
}
}
});

fclose($fileHandle);
});
}

/**
* @throws AbortException
*/
protected function parseHeaders(array $headerRow): void
{
foreach ($headerRow as $index => $header) {
// Excel puts some garbage at the beginning of the file that we need to filter out.
$header = trim($header, "\xEF\xBB\xBF\"");

if (array_key_exists($header, self::COLUMN_MAPPING)) {
$this->columns[self::COLUMN_MAPPING[$header]] = $index;
}
}

$this->assert(
count(self::COLUMN_MAPPING) === count($this->columns),
'Not all required columns were found'
);
}
}
83 changes: 83 additions & 0 deletions app/Console/Commands/OneOff/UpdateTreeCollections.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace App\Console\Commands\OneOff;

use App\Models\V2\Forms\FormQuestion;
use App\Models\V2\Organisation;
use App\Models\V2\ProjectPitch;
use App\Models\V2\Projects\Project;
use App\Models\V2\Sites\SiteReport;
use App\Models\V2\TreeSpecies\TreeSpecies;
use App\Models\V2\UpdateRequests\UpdateRequest;
use Illuminate\Console\Command;

class UpdateTreeCollections extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'one-off:update-tree-collections';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Removes old / missing collection names and updates to a correct value.';

/**
* Execute the console command.
*/
public function handle()
{
$this->info('Updating collections in v2_tree_species');
TreeSpecies::withoutTimestamps(function () {
TreeSpecies::withTrashed()->where('speciesable_type', ProjectPitch::class)
->update(['collection' => TreeSpecies::COLLECTION_PLANTED]);
TreeSpecies::withTrashed()->where('speciesable_type', Project::class)->where('collection', 'primary')
->update(['collection' => TreeSpecies::COLLECTION_PLANTED]);
TreeSpecies::withTrashed()->where('speciesable_type', Organisation::class)
->update(['collection' => TreeSpecies::COLLECTION_HISTORICAL]);
TreeSpecies::withTrashed()->where('speciesable_type', SiteReport::class)->where('collection', null)
->update(['collection' => TreeSpecies::COLLECTION_NON_TREE]);
});

$this->info('Updating collections in v2_update_requests content');
// This is kind of a hassle; fortunately, the only model type above that has bad data embedded in update requests
// is Project
UpdateRequest::withoutTimestamps(function () {
$updateRequests = UpdateRequest::where('updaterequestable_type', Project::class)
->where('content', 'LIKE', '%"collection":"primary"%')
->get();
foreach ($updateRequests as $updateRequest) {
$content = $updateRequest->content;
foreach (array_keys($content) as $key) {
$collections = data_get($content, "$key.*.collection");
if (is_array($collections) && in_array('primary', $collections)) {
data_set($content, "$key.*.collection", TreeSpecies::COLLECTION_PLANTED);
}
}

$updateRequest->update(['content' => $content]);
}
});

$this->info('Updating form fields');
FormQuestion::withoutTimestamps(function () {
$relationSets = data_get(config('wri.linked-fields.models'), '*.relations');
foreach ($relationSets as $relations) {
foreach ($relations as $linkedFieldKey => $properties) {
if ($properties['input_type'] != 'treeSpecies') {
continue;
}

FormQuestion::withTrashed()
->where('linked_field_key', $linkedFieldKey)
->update(['collection' => $properties['collection']]);
}
}
});
}
}
Loading
Loading