-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #190 from celonis/j-halili/EMA-4088_add-action-flo…
…ws-import-export-analyze-functionality [EMA-4088 & EMA-4345]: Integrate Action Flow Import/Export functionality
- Loading branch information
Showing
13 changed files
with
547 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { httpClientV2 } from "../services/http-client-service.v2"; | ||
import { FatalError } from "../util/logger"; | ||
import * as FormData from "form-data"; | ||
|
||
class ActionFlowApi { | ||
public static readonly INSTANCE = new ActionFlowApi(); | ||
|
||
public async exportRawAssets(packageId: string): Promise<Buffer> { | ||
return httpClientV2.getFile(`/ems-automation/api/root/${packageId}/export/assets`).catch(e => { | ||
throw new FatalError(`Problem getting Action Flow assets: ${e}`); | ||
}); | ||
} | ||
|
||
public async analyzeAssets(packageId: string): Promise<any> { | ||
return httpClientV2.get(`/ems-automation/api/root/${packageId}/export/assets/analyze`).catch(e => { | ||
throw new FatalError(`Problem analyzing Action Flow assets: ${e}`); | ||
}); | ||
} | ||
|
||
public async importAssets(packageId: string, data: FormData, dryRun: boolean): Promise<any> { | ||
const params = { | ||
dryRun: dryRun, | ||
}; | ||
|
||
return httpClientV2.postFile(`/ems-automation/api/root/${packageId}/import/assets`, data, params).catch(e => { | ||
throw new FatalError(`Problem importing Action Flow assets: ${e}`); | ||
}); | ||
} | ||
} | ||
|
||
export const actionFlowApi = ActionFlowApi.INSTANCE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { actionFlowService } from "../services/action-flow/action-flow-service"; | ||
|
||
export class ActionFlowCommand { | ||
|
||
public async exportActionFlows(packageId: string, metadataFile: string): Promise<void> { | ||
await actionFlowService.exportActionFlows(packageId, metadataFile); | ||
} | ||
|
||
public async analyzeActionFlows(packageId: string, outputToJsonFile: boolean): Promise<void> { | ||
await actionFlowService.analyzeActionFlows(packageId, outputToJsonFile); | ||
} | ||
|
||
public async importActionFlows(packageId: string, filePath: string, dryRun: boolean, outputToJsonFile: boolean): Promise<void> { | ||
await actionFlowService.importActionFlows(packageId, filePath, dryRun, outputToJsonFile); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { ContextInitializer } from "./util/context-initializer"; | ||
import { logger } from "./util/logger"; | ||
import { ActionFlowCommand } from "./commands/action-flow.command"; | ||
import commander = require("commander"); | ||
|
||
type CommanderStatic = commander.CommanderStatic; | ||
|
||
class Analyze { | ||
public static actionFlows(program: CommanderStatic): CommanderStatic { | ||
program | ||
.command("action-flows") | ||
.description("Analyze Action Flows dependencies for a certain package") | ||
.option("-p, --profile <profile>", "Profile which you want to use to analyze Action Flows") | ||
.requiredOption("--packageId <packageId>", "ID of the package from which you want to export Action Flows") | ||
.option("-o, --outputToJsonFile", "Output the analyze result in a JSON file") | ||
.action(async cmd => { | ||
await new ActionFlowCommand().analyzeActionFlows(cmd.packageId, cmd.outputToJsonFile); | ||
process.exit(); | ||
}); | ||
return program; | ||
} | ||
} | ||
|
||
const loadCommands = () => { | ||
getAllCommands(); | ||
}; | ||
|
||
ContextInitializer.initContext() | ||
.then(loadCommands, loadCommands) | ||
.catch(e => { | ||
logger.error(e); | ||
}); | ||
|
||
if (!process.argv.slice(2).length) { | ||
commander.outputHelp(); | ||
process.exit(1); | ||
} | ||
|
||
function getAllCommands(): void { | ||
Analyze.actionFlows(commander); | ||
|
||
commander.parse(process.argv); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { logger } from "../../util/logger"; | ||
import { v4 as uuidv4 } from "uuid"; | ||
import { FileService, fileService } from "../file-service"; | ||
import { actionFlowApi } from "../../api/action-flow-api"; | ||
import * as AdmZip from "adm-zip"; | ||
import * as FormData from "form-data"; | ||
import * as fs from "fs"; | ||
|
||
class ActionFlowService { | ||
public static readonly METADATA_FILE_NAME = "metadata.json"; | ||
|
||
public async exportActionFlows(packageId: string, metadataFilePath: string): Promise<void> { | ||
const exportedActionFlowsData = await actionFlowApi.exportRawAssets(packageId); | ||
const tmpZip: AdmZip = new AdmZip(exportedActionFlowsData); | ||
|
||
const zip = new AdmZip(); | ||
tmpZip.getEntries().forEach(entry => { | ||
zip.addFile(entry.entryName, entry.getData()); | ||
}); | ||
|
||
if (metadataFilePath) { | ||
this.attachMetadataFile(metadataFilePath, zip); | ||
} | ||
|
||
const fileName = "action-flows_export_" + uuidv4() + ".zip"; | ||
zip.writeZip(fileName); | ||
logger.info(FileService.fileDownloadedMessage + fileName); | ||
} | ||
|
||
public async analyzeActionFlows(packageId: string, outputToJsonFile: boolean): Promise<void> { | ||
const actionFlowsMetadata = await actionFlowApi.analyzeAssets(packageId); | ||
const actionFlowsMetadataString = JSON.stringify(actionFlowsMetadata, null, 4); | ||
|
||
if (outputToJsonFile) { | ||
const metadataFileName = "action-flows_metadata_" + uuidv4() + ".json"; | ||
fileService.writeToFileWithGivenName(actionFlowsMetadataString, metadataFileName); | ||
logger.info(FileService.fileDownloadedMessage + metadataFileName); | ||
} else { | ||
logger.info("Action flows analyze metadata: \n" + actionFlowsMetadataString); | ||
} | ||
} | ||
|
||
public async importActionFlows(packageId: string, filePath: string, dryRun: boolean, outputToJsonFile: boolean): Promise<void> { | ||
const actionFlowsZip = this.createBodyForImport(filePath); | ||
const eventLog = await actionFlowApi.importAssets(packageId, actionFlowsZip, dryRun); | ||
const eventLogString = JSON.stringify(eventLog, null, 4); | ||
|
||
if (outputToJsonFile) { | ||
const eventLogFileName = "action-flows_import_event_log_" + uuidv4() + ".json"; | ||
fileService.writeToFileWithGivenName(eventLogString, eventLogFileName); | ||
logger.info(FileService.fileDownloadedMessage + eventLogFileName); | ||
} else { | ||
logger.info("Action flows import event log: \n" + eventLogString); | ||
} | ||
} | ||
|
||
private createBodyForImport(fileName: string): FormData { | ||
fileName = fileName + (fileName.endsWith(".zip") ? "" : ".zip"); | ||
|
||
const formData = new FormData(); | ||
formData.append("file", fs.createReadStream(fileName, { encoding: null }), { filename: fileName }); | ||
|
||
return formData; | ||
} | ||
|
||
private attachMetadataFile(fileName: string, zip: AdmZip): void { | ||
fileName = fileName + (fileName.endsWith(".json") ? "" : ".json"); | ||
const metadata = fileService.readFile(fileName); | ||
|
||
zip.addFile(ActionFlowService.METADATA_FILE_NAME, Buffer.from(metadata)); | ||
} | ||
} | ||
|
||
export const actionFlowService = new ActionFlowService(); | ||
export const metadataFileName = ActionFlowService.METADATA_FILE_NAME; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { FileService } from "../../src/services/file-service"; | ||
import * as path from "path"; | ||
import { mockWriteFileSync, testTransport } from "../jest.setup"; | ||
import { mockedAxiosInstance } from "../utls/http-requests-mock"; | ||
import { ActionFlowCommand } from "../../src/commands/action-flow.command"; | ||
|
||
describe("Analyze action-flows", () => { | ||
|
||
const packageId = "123-456-789"; | ||
const mockAnalyzeResponse = { | ||
"actionFlows": [ | ||
{ | ||
"key": "987_asset_key", | ||
"rootNodeKey": "123_root_key_node", | ||
"parentNodeKey": "555_parent_node_key", | ||
"name": "T2T - simple package Automation", | ||
"scenarioId": "321", | ||
"webHookUrl": null, | ||
"version": "10", | ||
"sensorType": null, | ||
"schedule": { | ||
"type": "indefinitely", | ||
"interval": 900, | ||
}, | ||
"teamSpecific": { | ||
"connections": [], | ||
"variables": [], | ||
"celonisApps": [], | ||
"callingOtherAf": [], | ||
"datastructures": [], | ||
}, | ||
}, | ||
], | ||
"connections": [], | ||
"dataPools": [], | ||
"dataModels": [], | ||
"skills": [], | ||
"analyses": [], | ||
"datastructures": [], | ||
"mappings": [], | ||
"actionFlowsTeamId": "1234", | ||
}; | ||
|
||
it("Should call import API and return non-json response", async () => { | ||
const resp = { data: mockAnalyzeResponse }; | ||
(mockedAxiosInstance.get as jest.Mock).mockResolvedValue(resp); | ||
|
||
await new ActionFlowCommand().analyzeActionFlows(packageId, false); | ||
|
||
expect(testTransport.logMessages.length).toBe(1); | ||
expect(testTransport.logMessages[0].message).toContain(JSON.stringify(mockAnalyzeResponse, null, 4)); | ||
|
||
expect(mockedAxiosInstance.get).toHaveBeenCalledWith(`https://myTeam.celonis.cloud/ems-automation/api/root/${packageId}/export/assets/analyze`, expect.anything()); | ||
}); | ||
|
||
it("Should call import API and return json response", async () => { | ||
const resp = { data: mockAnalyzeResponse }; | ||
(mockedAxiosInstance.get as jest.Mock).mockResolvedValue(resp); | ||
|
||
await new ActionFlowCommand().analyzeActionFlows(packageId, true); | ||
|
||
expect(testTransport.logMessages.length).toBe(1); | ||
expect(testTransport.logMessages[0].message).toContain(FileService.fileDownloadedMessage); | ||
const expectedFileName = testTransport.logMessages[0].message.split(FileService.fileDownloadedMessage)[1]; | ||
|
||
expect(mockWriteFileSync).toHaveBeenCalledWith(path.resolve(process.cwd(), expectedFileName), JSON.stringify(mockAnalyzeResponse, null, 4), { encoding: "utf-8" }); | ||
expect(mockedAxiosInstance.get).toHaveBeenCalledWith(`https://myTeam.celonis.cloud/ems-automation/api/root/${packageId}/export/assets/analyze`, expect.anything()); | ||
}); | ||
}); |
Oops, something went wrong.