Skip to content

Commit

Permalink
Add Events Generator Common Template (#35)
Browse files Browse the repository at this point in the history
* feat:Events Geenrator to add event registrations to the config file

* Add providermetadata to provider id mapping to the .env file

* add runtime action for events registration

* select existing or new actions

* update aio-lib-events to fetch provider metadata

* fix indent

* Add unit tests for Events Generator

* Add unit tests for EventGenerator and fix bugs

* Add unit tests and fix bugs

* remove fs-extra and add unit tests for utils

* add unit test for runtime action helper

* fix action name to create runtime action

* fix runtime action name in manifest file

* add option to pass provider metadata id

* add additional assertion for unit test
  • Loading branch information
sangeetha5491 authored Aug 2, 2023
1 parent d3085d2 commit 8fdbc77
Show file tree
Hide file tree
Showing 17 changed files with 1,443 additions and 7 deletions.
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ governing permissions and limitations under the License.

const utils = require('./lib/utils')
const ActionGenerator = require('./lib/ActionGenerator')
const EventsGenerator = require('./lib/EventsGenerator')
const constants = require('./lib/constants')
const commonTemplates = require('./lib/common-templates')

module.exports = {
utils,
constants,
ActionGenerator,
EventsGenerator,
commonTemplates
}
3 changes: 1 addition & 2 deletions lib/ActionGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ Note: characters can only be split by '-'.
this.addPackageJsonNodeEngines()
}

/** @private */
loadRuntimeManifest (configPath, fullKeyToManifest, defaultPkgName) {
const config = utils.readYAMLConfig(this, configPath)
const runtimeManifest = fullKeyToManifest.split('.').reduce((obj, k) => obj && obj[k], config) || {}
Expand Down Expand Up @@ -176,7 +175,7 @@ Note: characters can only be split by '-'.
relActionPath = upath.toUnix(relActionPath) // relative to config File
runtimeManifest.packages[pkgName].actions[actionName] = {
function: relActionPath,
web: 'yes',
web: actionManifestConfig.web || 'yes',
runtime: defaultRuntimeKind,
...actionManifestConfig,
annotations: { 'require-adobe-auth': true, ...actionManifestConfig.annotations }
Expand Down
179 changes: 179 additions & 0 deletions lib/EventsGenerator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
Copyright 2023 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

const utils = require('./utils')
const eventsSdk = require('@adobe/aio-lib-events')
const config = require('@adobe/aio-lib-core-config')
const { getToken } = require('@adobe/aio-lib-ims')
const { CLI } = require('@adobe/aio-lib-ims/src/context')

const ActionGenerator = require('./ActionGenerator')
const { promptForEventsOfInterest, getProviderMetadataToProvidersExistingMap } = require('./events/EventsOfInterestHelper')
const { promptForRuntimeAction } = require('./events/RuntimeActionForEventsHelper')
const eventsProvidermetadataToProviderMapping = 'AIO_events_providermetadata_to_provider_mapping'

class EventsGenerator extends ActionGenerator {
constructor (args, opts) {
super(args, opts)
this.option('skip-prompt', { default: false }) // prompt to ask action name
// required
this.option('config-path',
{ type: String, description: 'relative path to the config yaml file' })
this.option('full-key-to-events-manifest', {
type: String,
description: 'key in config path that resolves to events manifest e.g. "application.events"',
default: ''
})

this.configPath = this.destinationPath(this.options['config-path'])
this.fullKeyToRuntimeManifest = this.options['full-key-to-manifest']
this.actionFolder = this.options['action-folder'] // todo ensure this is relative to root
this.fullKeyToEventsManifest = this.options['full-key-to-events-manifest']
this.projectConfig = config.get('project')
this.providerMetadataToProviderMappingDotEnv = process.env.AIO_events_providermetadata_to_provider_mapping
}

async initEventsClient () {
if (!this.projectConfig) {
this.env.error('Incomplete .aio configuration, please import a valid Adobe Developer Console configuration via `aio app use` first.')
}
const orgCode = this.projectConfig.org.ims_org_id
const accessToken = await getToken(CLI)
const X_API_KEY = process.env.SERVICE_API_KEY
const eventsClient = await eventsSdk.init(orgCode, X_API_KEY, accessToken)
return eventsClient
}

async promptForEventsDetails (defaultValues, options) {
if (!this.options['skip-prompt']) {
const eventsClient = await this.initEventsClient()
const runtimeActionName = await promptForRuntimeAction(this)
let regName = defaultValues.regName
let regDesc = defaultValues.regDesc

const basicDetails = await this.prompt([
{
type: 'input',
name: 'regName',
message: 'We are about to create a new Event registration.\nHow would you like to name this registration?',
default: regName,
when: !this.options['skip-prompt']
},
{
type: 'input',
name: 'regDesc',
message: 'What is this registration being created for?',
default: regDesc,
when: !this.options['skip-prompt']
}
])
regName = basicDetails.regName
regDesc = basicDetails.regDesc
const selectedProvidersToEventMetadata = await promptForEventsOfInterest(eventsClient, this, options)
return { regName, regDesc, selectedProvidersToEventMetadata, runtimeActionName }
}
}

/**
* Adds a new event registration to the project
*
* @param {string} eventDetails
* @param {object} [options={}]
* @param {object} [options.dotenvStub]
* @param {string} options.dotenvStub.label
* @param {Array<string>} options.dotenvStub.vars
* @param {object} [options.dependencies]
* @param {object} [options.devDependencies]
* @memberof EventsGenerator
*/
addEvents (eventDetails, tplActionPath, options = {}) {
// NOTE: it's important to load a fresh manifest now, as we do want to include the
// latest written data
if (!eventDetails) {
return
}
const events = this.loadEventsManifest(this.configPath,
this.fullKeyToEventsManifest)
const { runtimeManifest, runtimePackageName } = this.loadRuntimeManifest(
this.configPath, this.fullKeyToManifest,
this.defaultRuntimePackageName)
if (!runtimeManifest.packages[runtimePackageName].actions[eventDetails.runtimeActionName]) {
this.addAction(eventDetails.runtimeActionName, tplActionPath, options)
}
this.setEventsManifestDetails(eventDetails, events, runtimePackageName)
this.writeEventsManifest(this.configPath, this.fullKeyToEventsManifest,
events)
const mapping = this.setDotEnvFileDetails(eventDetails)
this.writeDotEnvFile(mapping)
}

/** @private */
loadEventsManifest (configPath, fullKeyToEventsManifest) {
const config = utils.readYAMLConfig(this, configPath)
let events = fullKeyToEventsManifest.split('.').reduce((obj, k) => obj && obj[k], config) || {}
if (!events.registrations) {
events = {
registrations: {}
}
}
return events
}

/** @private */
writeEventsManifest (configPath, fullKeyToEventsManifest, events) {
utils.writeKeyYAMLConfig(this, configPath, fullKeyToEventsManifest, events)
}

/** @private */
writeDotEnvFile (valueMap) {
utils.appendVarsToDotenv(this, 'Provider metadata to provider id mapping', eventsProvidermetadataToProviderMapping, valueMap)
}

/** @private */
setEventsManifestDetails (regDetails, events, runtimePackageName) {
const eventsOfInterest = []
for (const providerMetadata of Object.keys(regDetails.selectedProvidersToEventMetadata)) {
eventsOfInterest.push({
provider_metadata: providerMetadata,
event_codes: regDetails.selectedProvidersToEventMetadata[providerMetadata].eventmetadata
})
}

events.registrations[regDetails.regName] = {
description: regDetails.regDesc,
events_of_interest: eventsOfInterest,
runtime_action: runtimePackageName + '/' + regDetails.runtimeActionName
}

return events
}

setDotEnvFileDetails (regDetails) {
const providerMetadataToProvidersExistingMap = getProviderMetadataToProvidersExistingMap(this.providerMetadataToProviderMappingDotEnv)
const newMapping = providerMetadataToProvidersExistingMap || {}
for (const providerMetadata of Object.keys(regDetails.selectedProvidersToEventMetadata)) {
// TODO: Ask for confirmation if provider id for an existing mapping changes
newMapping[providerMetadata] = regDetails.selectedProvidersToEventMetadata[providerMetadata].provider.id
}
let mapping = ''
const separator = ','
for (const key of Object.keys(newMapping)) {
if (mapping === '') {
mapping = mapping + key + ':' + newMapping[key]
} else {
mapping = mapping + separator + key + ':' + newMapping[key]
}
}
return mapping
}
}

module.exports = EventsGenerator
76 changes: 76 additions & 0 deletions lib/events/EventsOfInterestHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2023 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

const { selectEventMetadataForProvider, selectProviderForProviderMetadata } = require('./ProviderHelper')
const { getEntitledProviderMetadataForOrg, getProviderMetadata } = require('./ProviderMetadataHelper')

async function promptForEventsOfInterest (eventsClient, obj, options) {
const providerMetadataList = await getEntitledProviderMetadataForOrg(eventsClient)
// get all providers filtered by the selected provider metadata ids
const providerMetadataIds = await getProviderMetadata(obj, providerMetadataList, options)
// get a map of provider_metadata_id -> providers
const providerMetadataToProvidersMap = await getProviderMetadataToProvidersMap(obj.projectConfig.org.id, eventsClient, providerMetadataIds)
// get the existing map of provider_metadata_id -> provider_id from the .env file
const selectedProvidersToEventMetadata = new Map()
for (const providerMetadataId of providerMetadataIds) {
const providerSelected = await selectProviderForProviderMetadata(
providerMetadataToProvidersMap, providerMetadataId, obj)
const eventMetadataChoices = []
const eventMetadataSelected = await selectEventMetadataForProvider(
providerSelected, eventMetadataChoices, obj)
selectedProvidersToEventMetadata[providerSelected.provider_metadata] = {
provider: providerSelected,
eventmetadata: eventMetadataSelected
}
}
return selectedProvidersToEventMetadata
}

/** @private */
async function getProviderMetadataToProvidersMap (consumerId, eventsClient, providerMetadataIds) {
const providersHalModel = await eventsClient.getAllProviders(consumerId, {
fetchEventMetadata: true,
filterBy: {
providerMetadataIds
}
})
const providers = providersHalModel._embedded.providers
const providerMetadataToProvidersMap = providers.reduce((providerMetadataToProvidersMap, provider) => {
providerMetadataToProvidersMap[provider.provider_metadata] = providerMetadataToProvidersMap[provider.provider_metadata] || []
providerMetadataToProvidersMap[provider.provider_metadata].push({
id: provider.id,
label: provider.label,
description: provider.description,
provider_metadata: provider.provider_metadata,
eventmetadata: provider._embedded.eventmetadata
})
return providerMetadataToProvidersMap
}, [])
return providerMetadataToProvidersMap
}

function getProviderMetadataToProvidersExistingMap (providerMetadataToProviderMappingDotEnv) {
if (providerMetadataToProviderMappingDotEnv) {
const entries = providerMetadataToProviderMappingDotEnv.split(',')
const providerMetadataToProviderIdMappingExistingMap = {}
for (let i = 0; i < entries.length; i++) {
const tokens = entries[i].split(':')
providerMetadataToProviderIdMappingExistingMap[tokens[0].trim()] = tokens[1].trim()
}
return providerMetadataToProviderIdMappingExistingMap
}
return {}
}

module.exports = {
promptForEventsOfInterest,
getProviderMetadataToProvidersExistingMap
}
69 changes: 69 additions & 0 deletions lib/events/ProviderHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright 2023 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

async function selectProviderForProviderMetadata (providerMetadataToProvidersMap,
providerMetadata, obj) {
const providerChoices = []
const providersList = providerMetadataToProvidersMap[providerMetadata]
if (providersList.length === 1) {
providerChoices.push({
name: providersList[0].label,
description: providersList[0].description,
checked: true,
value: providersList[0]
})
} else {
for (const provider of providersList) {
providerChoices.push({
name: provider.label,
description: provider.description,
value: provider
})
}
}
const { provider } = await obj.prompt({
type: 'list',
name: 'provider',
message: `Choose from below provider for provider metadata: ${providerMetadata}`,
choices: providerChoices,
pageSize: 5
})
return provider
}

async function selectEventMetadataForProvider (providerSelected, eventMetadataChoices, obj) {
for (const eventMetadata of providerSelected.eventmetadata) {
eventMetadataChoices.push({
name: eventMetadata.label,
description: eventMetadata.description,
value: eventMetadata.event_code
})
}
const { eventMetadataSelection } = await obj.prompt({
type: 'checkbox',
name: 'eventMetadataSelection',
message: `Choose event metadata for provider: ${providerSelected.label}`,
choices: eventMetadataChoices,
pageSize: 5,
validate: (options) => {
if (!options.length) {
return 'Choose at least one of the above, use space to choose the option'
}
return true
}
})
return eventMetadataSelection
}

module.exports = {
selectProviderForProviderMetadata,
selectEventMetadataForProvider
}
Loading

0 comments on commit 8fdbc77

Please sign in to comment.