Skip to content

Commit

Permalink
feat: migrates notifications category to support in app messaging cha…
Browse files Browse the repository at this point in the history
…nnel notifications (#11170)

* feat: migrates notifications category to support in app messaging channel notifications
  • Loading branch information
lazpavel authored Oct 15, 2022
1 parent 9dfbf6c commit 52f5787
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 57 deletions.
2 changes: 1 addition & 1 deletion packages/amplify-category-analytics/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const getPermissionPolicies = async (context: $TSContext, resourceOpsMapp
*/
export const executeAmplifyCommand = async (context: $TSContext) : Promise<$TSAny> => {
context.exeInfo = context.amplify.getProjectDetails();
migrationCheck(context);
await migrationCheck(context);

let commandPath = path.normalize(path.join(__dirname, 'commands'));
commandPath = context.input.command === 'help'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,83 @@
import {
$TSAny,
$TSContext, AmplifyCategories, JSONUtilities, pathManager, stateManager,
$TSContext,
AmplifyCategories,
AmplifySupportedService,
JSONUtilities,
pathManager,
stateManager,
readCFNTemplate, writeCFNTemplate,
} from 'amplify-cli-core';
import fs from 'fs-extra';
import * as path from 'path';
import { pinpointHasInAppMessagingPolicy, pinpointInAppMessagingPolicyName } from '../utils/pinpoint-helper';
import { analyticsPush } from '../commands/analytics';
import { invokeAuthPush } from '../plugin-client-api-auth';
import { getAllDefaults } from '../provider-utils/awscloudformation/default-values/pinpoint-defaults';
import { getAnalyticsResources } from '../utils/analytics-helper';
import {
getNotificationsCategoryHasPinpointIfExists,
getPinpointRegionMappings,
pinpointHasInAppMessagingPolicy,
pinpointInAppMessagingPolicyName,
} from '../utils/pinpoint-helper';

/**
* checks if the project has been migrated to the latest version of in-app messaging
*/
export const inAppMessagingMigrationCheck = async (context: $TSContext): Promise<void> => {
if (['add', 'update', 'push'].includes(context.input.command) && !pinpointHasInAppMessagingPolicy(context)) {
const projectBackendDirPath = pathManager.getBackendDirPath();
const projectBackendDirPath = pathManager.getBackendDirPath();
const resources = getAnalyticsResources(context);

if (resources.length > 0 && !pinpointHasInAppMessagingPolicy(context)) {
const amplifyMeta = stateManager.getMeta();
const analytics = amplifyMeta[AmplifyCategories.ANALYTICS] || {};
Object.keys(analytics).forEach(resourceName => {
const resourcePath = path.join(projectBackendDirPath, AmplifyCategories.ANALYTICS, resourceName);
const templateFilePath = path.join(resourcePath, 'pinpoint-cloudformation-template.json');
const analyticsResourcePath = path.join(projectBackendDirPath, AmplifyCategories.ANALYTICS, resourceName);
const templateFilePath = path.join(analyticsResourcePath, 'pinpoint-cloudformation-template.json');
const cfn = JSONUtilities.readJson(templateFilePath);
const updatedCfn = migratePinpointCFN(cfn);
fs.ensureDirSync(resourcePath);
fs.ensureDirSync(analyticsResourcePath);
JSONUtilities.writeJson(templateFilePath, updatedCfn);
});
}

const pinpointApp = getNotificationsCategoryHasPinpointIfExists();
if (resources.length === 0 && pinpointApp) {
const resourceParameters = getAllDefaults(context.amplify.getProjectDetails());
const notificationsInfo = {
appName: pinpointApp.appName,
resourceName: pinpointApp.appName,
};

Object.assign(resourceParameters, notificationsInfo);

const resource = resourceParameters.resourceName;
delete resourceParameters.resourceName;
const analyticsResourcePath = path.join(projectBackendDirPath, AmplifyCategories.ANALYTICS, resource);
stateManager.setResourceParametersJson(undefined, AmplifyCategories.ANALYTICS, resource, resourceParameters);

const templateFileName = 'pinpoint-cloudformation-template.json';
const templateFilePath = path.join(analyticsResourcePath, templateFileName);
if (!fs.existsSync(templateFilePath)) {
const templateSourceFilePath = path.join(__dirname, '..', 'provider-utils', 'awscloudformation', 'cloudformation-templates', templateFileName);
const { cfnTemplate } = readCFNTemplate(templateSourceFilePath);
cfnTemplate.Mappings = await getPinpointRegionMappings(context);
await writeCFNTemplate(cfnTemplate, templateFilePath);
}

const options = {
service: AmplifySupportedService.PINPOINT,
providerPlugin: 'awscloudformation',
};
context.amplify.updateamplifyMetaAfterResourceAdd(AmplifyCategories.ANALYTICS, resource, options);

context.parameters.options.yes = true;
context.exeInfo.inputParams = (context.exeInfo.inputParams) || {};
context.exeInfo.inputParams.yes = true;

await invokeAuthPush(context);
await analyticsPush(context);
}
};

const migratePinpointCFN = (cfn: $TSAny): $TSAny => {
Expand Down
4 changes: 3 additions & 1 deletion packages/amplify-category-analytics/src/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ export const analyticsMigrations = async (context: $TSContext): Promise<void> =>
* checks if the project has been migrated to the latest version
*/
export const migrationCheck = async (context: $TSContext): Promise<void> => {
await analyticsMigrations(context);
if (['add', 'update', 'push'].includes(context.input.command)) {
await analyticsMigrations(context);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import os from 'os';
import {
$TSContext, ResourceAlreadyExistsError, exitOnNextTick, AmplifyCategories,
$TSAny,
JSONUtilities,
} from 'amplify-cli-core';
import { printer } from 'amplify-prompts';
import { getNotificationsCategoryHasPinpointIfExists, getPinpointRegionMappings } from '../../../utils/pinpoint-helper';

const providerName = 'awscloudformation';
// FIXME: may be removed from here, since addResource can pass category to addWalkthrough
const category = AmplifyCategories.ANALYTICS;
const parametersFileName = 'parameters.json';
Expand Down Expand Up @@ -63,7 +64,7 @@ const configure = (
Object.assign(defaultValues, parameters);
}

const pinpointApp = checkIfNotificationsCategoryExists(context);
const pinpointApp = getNotificationsCategoryHasPinpointIfExists();

if (pinpointApp) {
Object.assign(defaultValues, pinpointApp);
Expand Down Expand Up @@ -172,30 +173,11 @@ const configure = (
const resourceDirPath = path.join(projectBackendDirPath, category, resource);
delete defaultValues.resourceName;
writeParams(resourceDirPath, defaultValues);
writeCfnFile(context, resourceDirPath);
await writeCfnFile(context, resourceDirPath);
return resource;
});
};

const checkIfNotificationsCategoryExists = (context: $TSContext): $TSAny => {
const { amplify } = context;
const { amplifyMeta } = amplify.getProjectDetails();
let pinpointApp: $TSAny;

if (amplifyMeta.notifications) {
const categoryResources = amplifyMeta.notifications;
Object.keys(categoryResources).forEach(resource => {
if (categoryResources[resource].service === serviceName && categoryResources[resource].output.Id) {
pinpointApp = {};
pinpointApp.appId = categoryResources[resource].output.Id;
pinpointApp.appName = resource;
}
});
}

return pinpointApp;
};

const resourceAlreadyExists = (context: $TSContext): string | undefined => {
const { amplify } = context;
const { amplifyMeta } = amplify.getProjectDetails();
Expand All @@ -213,33 +195,17 @@ const resourceAlreadyExists = (context: $TSContext): string | undefined => {
return resourceName;
};

const writeCfnFile = (context: $TSContext, resourceDirPath: string, force = false): void => {
const writeCfnFile = async (context: $TSContext, resourceDirPath: string, force = false): Promise<void> => {
fs.ensureDirSync(resourceDirPath);
const templateFilePath = path.join(resourceDirPath, templateFileName);
if (!fs.existsSync(templateFilePath) || force) {
const templateSourceFilePath = `${__dirname}/../cloudformation-templates/${templateFileName}`;
const templateSourceFilePath = path.join(__dirname, '..', 'cloudformation-templates', templateFileName);
const templateSource = context.amplify.readJsonFile(templateSourceFilePath);
templateSource.Mappings = getTemplateMappings(context);
const jsonString = JSON.stringify(templateSource, null, 4);
fs.writeFileSync(templateFilePath, jsonString, 'utf8');
templateSource.Mappings = await getPinpointRegionMappings(context);
JSONUtilities.writeJson(templateFilePath, templateSource);
}
};

const getTemplateMappings = (context:$TSContext):Record<string, $TSAny> => {
const Mappings: Record<string, $TSAny> = {
RegionMapping: {},
};
const providerPlugins = context.amplify.getProviderPlugins(context);
const provider = require(providerPlugins[providerName]);
const regionMapping = provider.getPinpointRegionMapping();
Object.keys(regionMapping).forEach(region => {
Mappings.RegionMapping[region] = {
pinpointRegion: regionMapping[region],
};
});
return Mappings;
};

/**
* Save the params.json file for analytics category
* @param {*} resourceDirPath Path to params.json
Expand Down
49 changes: 48 additions & 1 deletion packages/amplify-category-analytics/src/utils/pinpoint-helper.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import {
AmplifySupportedService,
pathManager, readCFNTemplate,
open, $TSAny, $TSContext, $TSMeta, AmplifyCategories,
open, $TSAny, $TSContext, $TSMeta, AmplifyCategories, stateManager,
} from 'amplify-cli-core';
import { printer } from 'amplify-prompts';
import * as path from 'path';
import { getAnalyticsResources } from './analytics-helper';

/**
* Pinpoint app type definition
*/
export type PinpointApp = {
appId: string;
appName: string;
};

export const pinpointInAppMessagingPolicyName = 'pinpointInAppMessagingPolicyName';

/**
Expand Down Expand Up @@ -84,3 +92,42 @@ export const pinpointHasInAppMessagingPolicy = (context: $TSContext): boolean =>
const { cfnTemplate } = readCFNTemplate(pinpointCloudFormationTemplatePath, { throwIfNotExist: false }) || {};
return !!cfnTemplate?.Parameters?.[pinpointInAppMessagingPolicyName];
};

/**
* checks if notifications category has a pinpoint resource - legacy projects
*/
export const getNotificationsCategoryHasPinpointIfExists = (): PinpointApp | undefined => {
const amplifyMeta = stateManager.getMeta();
if (amplifyMeta.notifications) {
const categoryResources = amplifyMeta.notifications;
const pinpointServiceResource = Object.keys(categoryResources).find(
(resource: string) => categoryResources[resource].service === AmplifySupportedService.PINPOINT
&& categoryResources[resource].output.Id,
);

if (pinpointServiceResource) {
return {
appId: categoryResources[pinpointServiceResource].output.Id,
appName: pinpointServiceResource,
};
}
}

return undefined;
};

/**
* returns provider pinpoint region mapping
*/
export const getPinpointRegionMappings = async (context: $TSContext): Promise<Record<string, $TSAny>> => {
const Mappings: Record<string, $TSAny> = {
RegionMapping: {},
};
const regionMapping: $TSAny = await context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'getPinpointRegionMapping', []);
Object.keys(regionMapping).forEach(region => {
Mappings.RegionMapping[region] = {
pinpointRegion: regionMapping[region],
};
});
return Mappings;
};
2 changes: 1 addition & 1 deletion packages/amplify-category-notifications/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const migrate = async (context: $TSContext): Promise<void> => {
*/
export const executeAmplifyCommand = async (context: $TSContext): Promise<void> => {
context.exeInfo = context.amplify.getProjectDetails();
migrationCheck(context);
await migrationCheck(context);

let commandPath = path.normalize(path.join(__dirname, 'commands'));
commandPath = context.input.command === 'help' ? path.join(commandPath, category) : path.join(commandPath, category, context.input.command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import { invokeAnalyticsMigrations } from '../plugin-client-api-analytics';
* checks if the project has been migrated to the latest version
*/
export const migrationCheck = async (context: $TSContext): Promise<void> => {
await invokeAnalyticsMigrations(context);
if (['add', 'update', 'push'].includes(context.input.command)) {
await invokeAnalyticsMigrations(context);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ export const getPinpointAppStatus = async (context: $TSContext, amplifyMeta: $TS
if (resources.length > 0) {
// eslint-disable-next-line prefer-destructuring
resultPinpointApp.app = resources[0];
resultPinpointApp.status = (resultPinpointApp.app.id) ? IPinpointDeploymentStatus.APP_IS_DEPLOYED
resultPinpointApp.status = resultPinpointApp.app.id
? IPinpointDeploymentStatus.APP_IS_DEPLOYED
: IPinpointDeploymentStatus.APP_IS_CREATED_NOT_DEPLOYED;
}
// Check if Notifications is using an App but different from Analytics - Legacy behavior
Expand Down
4 changes: 2 additions & 2 deletions packages/amplify-e2e-core/src/categories/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export type AddAuthIdentityPoolAndUserPoolWithOAuthSettings = AddAuthUserPoolOnl
idpAppleAppId: string;
};

export function addAuthWithDefault(cwd: string, settings: any = {}): Promise<void> {
export function addAuthWithDefault(cwd: string, settings: any = {}, testingWithLatestCodebase = false): Promise<void> {
return new Promise((resolve, reject) => {
spawn(getCLIPath(), ['add', 'auth'], { cwd, stripColors: true })
spawn(getCLIPath(testingWithLatestCodebase), ['add', 'auth'], { cwd, stripColors: true })
.wait('Do you want to use the default authentication')
.sendCarriageReturn()
.wait('How do you want users to be able to sign in')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
addNotificationChannel,
amplifyPushAuth,
createNewProjectDir,
deleteProject,
deleteProjectDir,
} from '@aws-amplify/amplify-e2e-core';
import { initJSProjectWithProfile, versionCheck } from '../../../migration-helpers';
import { addLegacySmsNotificationChannel } from '../../../migration-helpers/notifications-helpers';
import { getShortId } from '../../../migration-helpers/utils';

describe('amplify add notifications', () => {
let projectRoot: string;
const migrateFromVersion = { v: '10.0.0' };
const migrateToVersion = { v: 'uninitialized' };

beforeEach(async () => {
projectRoot = await createNewProjectDir('notification-migration-2');
});

afterEach(async () => {
await deleteProject(projectRoot, undefined, true);
deleteProjectDir(projectRoot);
});

beforeAll(async () => {
await versionCheck(process.cwd(), false, migrateFromVersion);
await versionCheck(process.cwd(), true, migrateToVersion);
});

it('should add in app notifications if another notification channel added with an older version', async () => {
expect(migrateFromVersion.v).not.toEqual(migrateToVersion.v);
const settings = { resourceName: `notification${getShortId()}` };

await initJSProjectWithProfile(projectRoot, {}, false);
await addLegacySmsNotificationChannel(projectRoot, settings.resourceName);
await addNotificationChannel(projectRoot, settings, 'In-App Messaging', true, true, true);
await amplifyPushAuth(projectRoot, true);
});

it('should add in app notifications if another notification channel added and pushed with an older version', async () => {
expect(migrateFromVersion.v).not.toEqual(migrateToVersion.v);
const settings = { resourceName: `notification${getShortId()}` };

await initJSProjectWithProfile(projectRoot, {}, false);
await addLegacySmsNotificationChannel(projectRoot, settings.resourceName);
await amplifyPushAuth(projectRoot, false);
await addNotificationChannel(projectRoot, settings, 'In-App Messaging', true, true, true);
await amplifyPushAuth(projectRoot, true);
});
});
Loading

0 comments on commit 52f5787

Please sign in to comment.