@alithya-oss/backstage-plugin-time-saver-backend
Version:
This plugin provides an implementation of charts and statistics related to your time savings that are coming from usage of your templates. Plugins is built from frontend and backend part. Backend plugin is responsible for scheduled stats parsing process a
384 lines (380 loc) • 12.7 kB
JavaScript
'use strict';
var scaffolderClient = require('./scaffolderClient.cjs.js');
var defaultValues = require('./defaultValues.cjs.js');
var backstagePluginTimeSaverCommon = require('@alithya-oss/backstage-plugin-time-saver-common');
const TimeSaverApiError = Error;
const TemplateTaskIdNotFoundError = {
error: TimeSaverApiError("Template task ID not found"),
errorMessage: "Template task ID not found"
};
const EmptyDatabaseError = {
error: TimeSaverApiError("Plugin database is empty"),
errorMessage: "Plugin database is empty"
};
const NoStatisticsFoundError = {
error: TimeSaverApiError("No statistics found"),
errorMessage: "No statistics found"
};
const DatabaseError = {
error: TimeSaverApiError("Database error"),
errorMessage: "Database error"
};
class TimeSaverApi {
constructor(auth, logger, config, discovery, timeSaverDb, scaffolderDb) {
this.auth = auth;
this.logger = logger;
this.config = config;
this.discovery = discovery;
this.timeSaverDb = timeSaverDb;
this.scaffolderDb = scaffolderDb;
}
ok(result, logMessage) {
this.logger.debug(
`${logMessage ? `${logMessage} ` : ""}${JSON.stringify(result)}`
);
return result;
}
fail(errorResponse, origin = "") {
const { error, errorMessage } = errorResponse;
this.logger.error(
`${origin !== "" ? `[${origin}] - ` : ""}${errorMessage}`,
error ? error : undefined
);
return {
errorMessage
};
}
async getStatsByTemplateTaskId(templateTaskId) {
const templateName = await this.timeSaverDb.getTemplateNameByTemplateTaskId(
templateTaskId
);
if (templateName === undefined) {
return this.fail(TemplateTaskIdNotFoundError, "getStatsByTemplateTaskId");
}
const queryResult = await this.timeSaverDb.getStatsByTemplateTaskId(
templateTaskId
);
if (queryResult === undefined) {
return this.fail(NoStatisticsFoundError, "getStatsByTemplateTaskId");
}
return this.ok({
templateTaskId,
templateName,
stats: queryResult
});
}
async getStatsByTeam(team) {
const queryResult = await this.timeSaverDb.getStatsByTeam(team);
if (queryResult === undefined) {
return this.fail(NoStatisticsFoundError, "getStatsByTeam");
}
return this.ok({
team,
stats: queryResult
});
}
async getStatsByTemplate(template) {
const queryResult = await this.timeSaverDb.getStatsByTemplate(template);
if (queryResult === undefined) {
return this.fail(NoStatisticsFoundError, "getStatsByTemplate");
}
return this.ok({
templateName: template,
stats: queryResult
});
}
async getAllStats() {
const queryResult = await this.timeSaverDb.getAllStats();
if (queryResult === undefined) {
return this.fail(EmptyDatabaseError, "getAllStats");
}
return this.ok({
stats: queryResult
});
}
async getGroupDivisionStats() {
const queryResult = await this.timeSaverDb.getGroupSavingsDivision();
if (queryResult === undefined) {
return this.fail(EmptyDatabaseError, "getGroupDivisionStats");
}
return this.ok({
stats: queryResult
});
}
async getDailyTimeSummariesByTeam() {
const queryResult = await this.timeSaverDb.getDailyTimeSummariesByTeam();
if (queryResult === undefined) {
return this.fail(EmptyDatabaseError, "getDailyTimeSummariesByTeam");
}
return this.ok({
stats: queryResult
});
}
async getDailyTimeSummariesByTemplate() {
const queryResult = await this.timeSaverDb.getDailyTimeSummariesByTemplate();
if (queryResult === undefined) {
return this.fail(EmptyDatabaseError, "getDailyTimeSummariesByTemplate");
}
return this.ok({
stats: queryResult
});
}
async getTimeSavedSummaryByTeam() {
const queryResult = await this.timeSaverDb.getTimeSavedSummaryByTeam();
if (queryResult === undefined) {
return this.fail(EmptyDatabaseError, "getTimeSavedSummaryByTeam");
}
return this.ok({
stats: queryResult
});
}
async getTimeSavedSummaryByTemplate() {
const queryResult = await this.timeSaverDb.getTimeSavedSummaryByTemplate();
if (queryResult === undefined) {
return this.fail(EmptyDatabaseError, "getTimeSavedSummaryByTemplate");
}
return this.ok({
stats: queryResult
});
}
async getAllGroups() {
const queryResult = await this.timeSaverDb.getDistinctColumn("team");
if (!queryResult) {
return this.fail(EmptyDatabaseError, "getAllGroups");
}
return this.ok({
groups: queryResult.team
});
}
async getAllTemplateNames() {
const queryResult = await this.timeSaverDb.getDistinctColumn(
"template_name"
);
if (!queryResult) {
return this.fail(EmptyDatabaseError, "getAllTemplateNames");
}
return this.ok({
templates: queryResult.template_name
});
}
async getAllTemplateTasks() {
const queryResult = await this.timeSaverDb.getDistinctColumn(
"template_task_id"
);
if (!queryResult) {
return this.fail(EmptyDatabaseError, "getAllTemplateTasks");
}
return this.ok({
templateTasks: queryResult.template_task_id
});
}
async getTemplateCount() {
const queryResult = await this.timeSaverDb.getTemplateCount();
if (queryResult === undefined) {
return this.fail(DatabaseError, "getTemplateCount");
}
this.logger.debug(`${typeof queryResult === "number"}`);
return this.ok({
count: queryResult
});
}
async getTimeSavedSum(divider) {
const dividerInt = divider ?? 1;
const queryResult = await this.timeSaverDb.getTimeSavedSum();
if (queryResult === undefined) {
return this.fail(DatabaseError, "getTimeSavedSum");
}
return this.ok({
timeSaved: queryResult ? queryResult / dividerInt : queryResult
});
}
async getSampleMigrationClassificationConfig(customClassificationRequest, options) {
if (typeof customClassificationRequest === "object" && !Object.keys(customClassificationRequest).length) {
const errorMessage = `getSampleMigrationClassificationConfig : customClassificationRequest cannot be an empty object`;
this.logger.error(
`getSampleMigrationClassificationConfig : customClassificationRequest cannot be an empty object`
);
return {
status: "FAIL",
errorMessage
};
}
const sampleClassification = customClassificationRequest || defaultValues.DEFAULT_SAMPLE_CLASSIFICATION;
let templatesList = [];
if (options?.useScaffolderTasksEntries) {
const templateTaskResponse = await this.getAllTemplateTasks();
if (backstagePluginTimeSaverCommon.isTemplateTaskResponse(templateTaskResponse)) {
templatesList = templateTaskResponse.templateTasks;
} else {
templatesList = defaultValues.DEFAULT_SAMPLE_TEMPLATES_TASKS;
}
}
this.logger.debug(
`Generating sample classification configuration with ${options?.useScaffolderTasksEntries ? "scaffolder DB" : "user-defined"} templates tasks list and ${customClassificationRequest ? "user-defined" : "default"} classification`
);
return {
status: "OK",
data: templatesList.map((t) => ({
entityRef: t,
...sampleClassification
}))
};
}
async updateTemplatesWithSubstituteData(requestData) {
let templateClassification;
let migrationStatisticsReport = {
updatedTemplates: {
total: 0,
list: []
},
missingTemplates: {
total: 0,
list: []
}
};
if (requestData) {
try {
if (typeof requestData !== "object") {
templateClassification = JSON.parse(requestData);
} else {
templateClassification = requestData;
}
if (!templateClassification || !Object.keys(templateClassification).length) {
throw new Error(
`Invalid classification ${JSON.stringify(
requestData
)}. Either it was empty or could not parse JSON string. Aborting...`
);
}
this.logger.debug(
`Found classification in API POST body: ${JSON.stringify(
templateClassification
)}`
);
} catch (error) {
const msg = `Migration: Could not parse JSON object from POST call body "${JSON.stringify(
requestData
)}", aborting...`;
this.logger.error(msg, error ? error : undefined);
return {
status: "FAIL",
message: `${msg} - ${error}`
};
}
} else {
const tsConfigObj = this.config.getOptionalString("ts.backward.config") || undefined;
if (!tsConfigObj) {
const errorMessage = "Migration: Could not find backward migration configuration in app-config.x.yaml, aborting...";
this.logger.error(errorMessage);
return {
status: "FAIL",
message: errorMessage
};
}
try {
templateClassification = JSON.parse(String(tsConfigObj));
this.logger.debug(
`Found classification in app-config.x.yaml: ${JSON.stringify(
templateClassification
)}`
);
} catch (error) {
const msg = "Migration: Could not parse backward migration configuration as JSON object from app-config.x.yaml, aborting...";
this.logger.error(msg, error ? error : undefined);
return {
status: "FAIL",
message: `${msg} - ${error}`
};
}
}
try {
this.logger.info(`Starting backward migration`);
const taskTemplateList = await new scaffolderClient.ScaffolderClient(
this.auth,
this.logger,
this.discovery
).fetchTemplatesFromScaffolder();
for (let i = 0; i < taskTemplateList.length; i++) {
const scaffolderTaskRecord = taskTemplateList[i];
this.logger.debug(
`Migrating template ${JSON.stringify(scaffolderTaskRecord)}`
);
const { entityRef: templateEntityRef } = scaffolderTaskRecord.spec.templateInfo;
this.logger.debug(
`Found template with entityRef: ${templateEntityRef}`
);
const classificationEntry = templateClassification.find(
(con) => con.entityRef === templateEntityRef
);
if (classificationEntry) {
const newClassificationEntry = Object.assign(
{},
classificationEntry
);
delete newClassificationEntry.entityRef;
const newTemplateTaskRecordSpecs = {
...scaffolderTaskRecord.spec,
templateInfo: {
...scaffolderTaskRecord.spec.templateInfo,
entity: {
...scaffolderTaskRecord.spec.templateInfo.entity,
metadata: {
...scaffolderTaskRecord.spec.templateInfo.entity.metadata,
substitute: newClassificationEntry
}
}
}
};
const patchQueryResult = await this.scaffolderDb.updateTemplateTaskById(
scaffolderTaskRecord.id,
JSON.stringify(newTemplateTaskRecordSpecs)
);
if (patchQueryResult) {
migrationStatisticsReport = {
...migrationStatisticsReport,
updatedTemplates: {
total: ++migrationStatisticsReport.updatedTemplates.total,
list: [
...migrationStatisticsReport.updatedTemplates.list,
scaffolderTaskRecord.id
]
}
};
this.logger.debug(
`scaffolderTaskRecord with id ${scaffolderTaskRecord.id} was patched`
);
}
} else {
migrationStatisticsReport = {
...migrationStatisticsReport,
missingTemplates: {
total: ++migrationStatisticsReport.missingTemplates.total,
list: [
...migrationStatisticsReport.missingTemplates.list,
scaffolderTaskRecord.id
]
}
};
this.logger.debug(
`scaffolderTaskRecord with id ${scaffolderTaskRecord.id} was not found in scaffolder DB`
);
}
}
} catch (error) {
this.logger.error(
`Could not continue with backward migration, aborting...`,
error ? error : undefined
);
return {
status: "error",
error: error ? error : undefined
};
}
return {
status: "SUCCESS",
migrationStatisticsReport
};
}
}
exports.TimeSaverApi = TimeSaverApi;
//# sourceMappingURL=timeSaverApi.cjs.js.map