Skip to content
This repository has been archived by the owner on Jan 29, 2021. It is now read-only.

Command to migrate legacy campaigns from Ashes -> Rogue #783

Merged
merged 24 commits into from
Dec 3, 2018

Conversation

chloealee
Copy link
Contributor

@chloealee chloealee commented Nov 21, 2018

What's this PR do?

  • Updates to campaigns table:
    • adds secondary_causes column
    • adds campaign_run_Id column
    • adds internal_doc column
    • makes fields nullable
  • Creates script to import legacy campaigns from Ashes to Rogue based on rules outlined here.
  • Creates test to make sure script is working as expected!

How should this be reviewed?

  • Did I capture all of the logic correctly?
  • Is there anything I am missing?
  • Feel free to ping me if you'd like to see a screen shot of the database and CSV to make sure all logic checks out! Another easy way to make sure this is correct is by looking at the sample CSV that the test imports and the logic there.
  • Finally, happy to share the SQL query @DFurnes and I came up to for the CSV export to also make sure that is correct since that is what this logic is based on!

Relevant tickets

Fixes task 3 in this Pivotal Card.

How to deploy

https://github.com/DoSomething/rogue/blob/master/docs/development/deployments.md

@chloealee chloealee requested a review from a team as a code owner November 21, 2018 19:27
@chloealee
Copy link
Contributor Author

One note - there is only one campaign that I found in the export that has NULL as the start_date. Since we don't want start_date to be nullable in Rogue and this is only one campaign, when we do the export from Ashes, can we just manually put its created_at date for the start_date?

// Create the campaign if there isn't one already
if (! $existing_campaign) {
// If there is no campaign_id, set the campaign_run_id to the id.
if (is_null($legacy_campaign['campaign_id'])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that in the csv runs will not have the campaign_id column populated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katiecrane great q! It will have the campaign_id column! However, after running the query, some campaigns didn't have this populated so wanted to account for these edge cases. Happy to share the CSV I'm working with too if that is helpful!

Copy link
Contributor

@katiecrane katiecrane Nov 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OH or does it mean that only CURRENT runs will have the `campaign_id populated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chloealee hmmmm does that mean that there are runs not attached to any campaign? I think seeing the CSV would help!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chloealee ah okay I see, the first 7 rows are the ones with null. Are these actual campaigns? Seems like one of them is the DS app and 5 of them are in Spanish?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, these look like placeholder campaigns. I think it's safe to add them with the script, and we can delete later.

if (is_null($legacy_campaign['campaign_id'])) {
$campaign = $this->createCampaign($legacy_campaign['run_id'], $legacy_campaign);
// If the campaign_id does not equal the next record's campaign_id or the previous record's campaign id, this is either the latest run or there is only one campaign run, so keep the original campaign_id as the id in Rogue.
} else if ($legacy_campaigns_csv->fetchOne($iterator) && $legacy_campaign['campaign_id'] != $legacy_campaigns_csv->fetchOne($iterator)['campaign_id'] && $legacy_campaign['campaign_id'] != $legacy_campaigns_csv->fetchOne($iterator - 1)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be easier to look at if this logic was extracted into a shouldUseCampaignId function? But I guess it might still all end up on one line. What do you think @chloealee ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this line is pretty intimidating. I'm not super concerned since this is a one-time script and we can easily check our work, but could be nice to clean up for readability.

public function testImportingLegacyCampaigns()
{
// Run the legacy campaign import command
$this->artisan('rogue:ashescampaignsimport', ['path' => 'tests/Console/example-legacy-campaigns.csv']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yesssss thank you for making tests for this!! 🙌

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1! So helpful!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

Copy link
Contributor

@DFurnes DFurnes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few little thoughts, but I'm good to merge this as long as it's thoroughly tested!

{
// Make a local copy of the CSV
$path = $this->argument('path');
info('rogue:legacycampaignimport: Loading in csv from ' . $path);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just print these to standard output?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DFurnes not sure what you mean by that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chloealee that would be doing $this->line('rogue:legacycampaignimport: Loading in csv from ' . $path). It'll print out in your terminal locally and when running on a one-off dyno in Heroku, it will print to the logs for that dyno (which is something we figured out semi-recently!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh!! this is so much better then taking up room in the Papertrail! updated!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it will still log to PaperTrail

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooooo what are the benefits of logging this way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chloealee I was trying to remember that too! Looks like it's a memory thing and using info() a bunch of times can cause a memory leak

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah got it, thanks for explaining, @katiecrane !

}

// See if the campaign exists
$existing_campaign = Campaign::where('id', $legacy_campaign['campaign_id'])->orWhere('id', $legacy_campaign['run_id'])->first();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this always find the "current run" that has the campaign ID? Say, if we create a campaign 6170 for campaign_id:6170,run_id:8, and then want to create a new campaign 7 for campaign_id:6170,run_id:7, I think this would just skip that second one, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or does this work since we know campaigns are sorted in reverse order of old runs → current?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DFurnes great q! Yes, this should work because the way we write the query and export the results to a CSV from Ashes, it'll always be reverse order of old runs -> current like you said.

Here's an example!

screen shot 2018-11-28 at 1 22 05 pm

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, sounds good! 🤞

@DFurnes
Copy link
Contributor

DFurnes commented Nov 28, 2018

One note - there is only one campaign that I found in the export that has NULL as the start_date. Since we don't want start_date to be nullable in Rogue and this is only one campaign, when we do the export from Ashes, can we just manually put its created_at date for the start_date?

Yes, let's do that!

} else if ($legacy_campaigns_csv->fetchOne($iterator) &&
$legacy_campaign['campaign_id'] != $legacy_campaigns_csv->fetchOne($iterator)['campaign_id'] &&
$legacy_campaign['campaign_id'] != $legacy_campaigns_csv->fetchOne($iterator - 1)) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katiecrane @DFurnes I agree this is a little hard to read! Since it is just for this script, I'm not sure if it would make it that much easier to read if broken into a separate helper function but I played with the formatting to see if this would make it easier to read - lmk what you think!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chloealee I love this! makes it much more readable!!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok great, thanks @katiecrane !

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, much nicer! 👁

}

$this->line('rogue:legacycampaignimport: Created campaign ' . $campaign->internal_title);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katiecrane @DFurnes I changed this log message to log the campaign title instead of the ID because I feel like that is more readable - lmk if you don't think this is a good idea though!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chloealee whatever you think will be the most useful works for me 😄

@@ -21,7 +21,7 @@ class Campaign extends Model
*
* @var array
*/
protected $fillable = ['internal_title', 'cause', 'start_date', 'end_date'];
protected $fillable = ['id','internal_title', 'cause', 'secondary_causes', 'campaign_run_id', 'impact_doc', 'start_date', 'end_date', 'created_at', 'updated_at'];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove id, created_at, and updated_at from the fillable array & use Campaign::forceCreate in the script (since we don't want these to be mass-assignable elsewhere).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool i didn't know about forceCreate ! Much better solution!

Schema::table('campaigns', function (Blueprint $table) {
$table->string('secondary_causes')->index()->after('cause')->nullable()->comment('Secondary causes associated with legacy campaigns');
$table->integer('campaign_run_id')->index()->after('secondary_causes')->nullable()->comment('Campaign Run Id to reference when we run script to update signup and post ids after legacy campaign migration. This column can be deleted once migration and update script are complete.');
$table->string('impact_doc')->index()->after('campaign_run_id')->nullable()->comment('URL to proof of impact doc.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need an index on the impact_doc column since it won't be queried against.

@@ -64,6 +64,7 @@ public function store(Request $request)
$this->validate($request, [
'internal_title' => 'required|string|unique:campaigns',
'cause' => 'required|string',
'impact_doc' => 'required|string',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the url validator to enforce that this is a URL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great call!

@@ -126,6 +127,7 @@ public function update(Request $request, Campaign $campaign)
$this->validate($request, [
'internal_title' => 'string',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing the unique:campaigns validation rule that we use when creating a new campaign.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can include it here by specifying a campaign ID to ignore for the uniqueness check, e.g:

'internal_title' => [Rule::unique('campaigns')->ignore($campaign->id)],

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very cool, thanks @DFurnes !

Copy link
Contributor

@DFurnes DFurnes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me!

@chloealee chloealee changed the title Command to migrate legacy campaigns from Ashes -> Rogue (pending with final OK from all teams on migration doc) Command to migrate legacy campaigns from Ashes -> Rogue Dec 3, 2018
@chloealee chloealee merged commit fb53be0 into master Dec 3, 2018
@chloealee chloealee deleted the import-ashes-campaigns-script branch December 3, 2018 19:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants