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

[TM-1400] Tree Species taxonomic schema #588

Open
wants to merge 7 commits into
base: staging
Choose a base branch
from
87 changes: 87 additions & 0 deletions app/Console/Commands/OneOff/PopulateTreeSpeciesResearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

namespace App\Console\Commands\OneOff;

use App\Console\Commands\Traits\Abortable;
use App\Console\Commands\Traits\AbortException;
use App\Models\V2\TreeSpecies\TreeSpeciesResearch;
use Illuminate\Console\Command;

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',
];

// 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 () {
$fileHandle = fopen($this->argument('file'), 'r');
$this->parseHeaders(fgetcsv($fileHandle, separator: "\t"));

// The input file at the time of this writing has 1618549 rows of data
$this->withProgressBar(1618549, function ($progressBar) use ($fileHandle) {
while ($csvRow = fgetcsv($fileHandle, separator: "\t")) {
$data = [];
foreach ($this->columns as $column => $index) {
$data[$column] = $csvRow[$index];
}
TreeSpeciesResearch::create($data);
$progressBar->advance();
}

$progressBar->finish();
});

fclose($fileHandle);
});
}

/**
* @throws AbortException
*/
protected function parseHeaders(array $headerRow): void
{
foreach ($headerRow as $index => $header) {
$header = trim($header);

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'
);
}
}
66 changes: 66 additions & 0 deletions app/Console/Commands/OneOff/UpdateTreeCollections.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace App\Console\Commands\OneOff;

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]);
}
});
}
}
6 changes: 3 additions & 3 deletions app/Exports/V2/OrganisationsExport.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ private function addFileCollectionHeadings(array $headings): array
private function buildTreeSpecies(Organisation $organisation): string
{
$list = [];
$treeSpecies = $organisation->treeSpecies()->select('name', 'amount')->get();
foreach ($treeSpecies as $treeSpecies) {
$list[] = $treeSpecies->name . '(' . $treeSpecies->amount . ')';
$treeSpecies = $organisation->treeSpeciesHistorical()->select('name', 'amount')->get();
foreach ($treeSpecies as $instance) {
$list[] = $instance->name . '(' . $instance->amount . ')';
}

return '[ ' . implode(',', $list) . ' ]';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public function __invoke(Request $request): EntityWithSchemaResource

foreach ($projectPitch->treeSpecies()->get() as $treeSpecies) {
$project->treeSpecies()->create([
'collection' => $treeSpecies->collection ?? TreeSpecies::COLLECTION_PRIMARY,
'collection' => $treeSpecies->collection ?? TreeSpecies::COLLECTION_PLANTED,
'name' => $treeSpecies->name,
'amount' => $treeSpecies->amount,
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function toArray($request)
'founding_date' => $this->founding_date,
'description' => $this->description,

'tree_species' => TreeSpeciesResource::collection($this->treeSpecies),
'tree_species' => TreeSpeciesResource::collection($this->treeSpeciesHistorical),

'web_url' => $this->web_url,
'facebook_url' => $this->facebook_url,
Expand Down
3 changes: 1 addition & 2 deletions app/Http/Resources/V2/Organisation/OrganisationResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ public function toArray($request)
'founding_date' => $this->founding_date,
'description' => $this->description,

'tree_species' => TreeSpeciesResource::collection($this->treeSpecies),
'tree_species_restored' => TreeSpeciesResource::collection($this->treeSpeciesRestored),
'tree_species_historical' => TreeSpeciesResource::collection($this->treeSpeciesHistorical),
'project_pitches' => ProjectPitchResource::collection($this->projectPitches),
'leadership_team' => LeadershipTeamResource::collection($this->leadershipTeam),
'core_team_leaders' => CoreTeamLeaderResource::collection($this->coreTeamLeaders),
Expand Down
10 changes: 2 additions & 8 deletions app/Models/V2/Organisation.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,16 +225,10 @@ public static function search($query)
->where('organisations.name', 'like', "%$query%");
}

public function treeSpecies(): MorphMany
public function treeSpeciesHistorical(): MorphMany
{
return $this->morphMany(TreeSpecies::class, 'speciesable')
->whereNull('collection');
}

public function treeSpeciesRestored(): MorphMany
{
return $this->morphMany(TreeSpecies::class, 'speciesable')
->where('collection', TreeSpecies::COLLECTION_RESTORED);
->where('collection', TreeSpecies::COLLECTION_HISTORICAL);
}

public function owners(): HasMany
Expand Down
6 changes: 2 additions & 4 deletions app/Models/V2/TreeSpecies/TreeSpecies.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,14 @@ class TreeSpecies extends Model implements EntityRelationModel
public const COLLECTION_PLANTED = 'tree-planted';
public const COLLECTION_NON_TREE = 'non-tree';
public const COLLECTION_NURSERY = 'nursery-seedling';
public const COLLECTION_RESTORED = 'restored';
public const COLLECTION_PRIMARY = 'primary';
public const COLLECTION_HISTORICAL = 'historical-tree-species';

public static $collections = [
self::COLLECTION_DIRECT_SEEDING => 'Direct Seeding',
self::COLLECTION_PLANTED => 'Planted',
self::COLLECTION_NON_TREE => 'Non Tree',
self::COLLECTION_NURSERY => 'Nursery Seedling',
self::COLLECTION_RESTORED => 'Restored',
self::COLLECTION_PRIMARY => 'Primary',
self::COLLECTION_HISTORICAL => 'Historical Tree Species',
];

public static function createResourceCollection(EntityModel $entity): JsonResource
Expand Down
27 changes: 27 additions & 0 deletions app/Models/V2/TreeSpecies/TreeSpeciesResearch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace App\Models\V2\TreeSpecies;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class TreeSpeciesResearch extends Model
{
use SoftDeletes;

public $table = 'tree_species_research';

public $primaryKey = 'taxon_id';

public $keyType = 'string';

public $incrementing = false;

protected $fillable = [
'taxon_id',
'scientific_name',
'family',
'genus',
'specific_epithet',
];
}
8 changes: 4 additions & 4 deletions config/wri/linked-fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,11 @@
'input_type' => 'coreTeamLeaders',
],
'org-tree-species-restored' => [
'property' => 'treeSpeciesRestored',
'property' => 'treeSpeciesHistorical',
'label' => 'Tree species restored in landscape',
'resource' => 'App\Http\Resources\V2\TreeSpecies\TreeSpeciesResource',
'input_type' => 'treeSpecies',
'collection' => 'restored'
'collection' => 'historical-tree-species'
],
'org-ownership-stake' => [
'property' => 'ownershipStake',
Expand Down Expand Up @@ -277,7 +277,7 @@
'label' => 'Tree Species',
'resource' => App\Http\Resources\V2\TreeSpecies\TreeSpeciesResource::class,
'input_type' => 'treeSpecies',
'collection' => 'primary',
'collection' => 'tree-planted',
],
],
],
Expand Down Expand Up @@ -364,7 +364,7 @@
'label' => 'Tree Species',
'resource' => 'App\Http\Resources\V2\TreeSpecies\TreeSpeciesResource',
'input_type' => 'treeSpecies',
'collection' => 'primary',
'collection' => 'tree-planted',
],
],
],
Expand Down
1 change: 0 additions & 1 deletion database/factories/V2/TreeSpecies/TreeSpeciesFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public function definition()
'speciesable_id' => Project::factory()->create(),
'name' => $this->faker->word(),
'amount' => $this->faker->numberBetween(0, 2147483647),
'type' => 'tree',
'collection' => $this->faker->randomElement(TreeSpecies::$collections),
];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('tree_species_research', function (Blueprint $table) {
// This table intentionally avoids having an auto increment int ID PK.
$table->string('taxon_id')->primary();

$table->string('scientific_name');
$table->string('family');
$table->string('genus');
$table->string('specific_epithet');

$table->timestamps();
$table->softDeletes();
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tree_species_research');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('v2_tree_species', function (Blueprint $table): void {
if (Schema::hasColumn('v2_tree_species', 'old_model')) {
$table->dropColumn('old_model');
}
if (Schema::hasColumn('v2_tree_species', 'old_id')) {
$table->dropColumn('old_id');
}
if (Schema::hasColumn('v2_tree_species', 'type')) {
$table->dropColumn('type');
}

$table->string('taxon_id')->nullable();
$table->index('taxon_id');
});

Schema::table('v2_seedings', function (Blueprint $table): void {
if (Schema::hasColumn('v2_seedings', 'old_model')) {
$table->dropColumn('old_model');
}
if (Schema::hasColumn('v2_seedings', 'old_id')) {
$table->dropColumn('old_id');
}

$table->string('taxon_id')->nullable();
$table->index('taxon_id');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('v2_tree_species', function (Blueprint $table): void {
$table->dropColumn('taxon_id');
});
Schema::table('v2_seedings', function (Blueprint $table): void {
$table->dropColumn('taxon_id');
});
}
};
Loading