rally-tools
Version:
The rally tools cli interface
334 lines (285 loc) • 11.4 kB
JavaScript
import {cached, defineAssoc} from "./decorators.js";
import {RallyBase, lib, Collection, AbortError, orderedObjectKeys} from "./rally-tools.js";
import {configObject} from "./config.js";
import Preset from "./preset.js";
import Provider from "./providers.js";
import Notification from "./notification.js";
import Tag from "./tag.js";
import {writeFileSync, readFileSync} from "./fswrap.js";
import {join, resolve as pathResolve} from "path";
class Rule extends RallyBase{
constructor({path, data, remote, subProject} = {}){
super();
if(path){
path = pathResolve(path);
try{
let f = readFileSync(path, "utf-8")
data = JSON.parse(readFileSync(path, "utf-8"));
}catch(e){
if(e.code === "ENOENT"){
if(configObject.ignoreMissing){
this.missing = true;
return undefined;
}else{
throw new AbortError("Could not load code of local file");
}
}else{
throw new AbortError(`Unreadable JSON in ${path}. ${e}`);
}
}
}
this.meta = {};
this.subproject = subProject;
if(!data){
data = Rule.newShell();
}
this.data = data;
this.remote = remote;
delete this.data.relationships.transitions;
delete this.data.meta;
delete this.data.attributes.updatedAt;
delete this.data.attributes.createdAt;
this.isGeneric = !this.remote;
}
static newShell(){
return {
"attributes": {
"description": "-",
"priority": "PriorityNorm",
"starred": false,
},
"relationships": {},
"type": "workflowRules",
};
}
async acclimatize(env){
this.remote = env;
let preset = await this.resolveField(Preset, "preset", false, "specific");
let pNext = await this.resolveField(Rule, "passNext", false, "specific");
let eNext = await this.resolveField(Rule, "errorNext", false, "specific");
let proType = await this.resolveField(Provider, "providerType", false, "specific");
let proTag = await this.resolveField(Tag, "providerFilterTag", false, "specific");
if(proTag){
this.data.attributes.providerFilter = proTag.id;
}else{
this.data.attributes.providerFilter = null;
}
let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true, "specific");
let enterNotif = await this.resolveField(Notification, "enterNotifications", true, "specific");
let errorNotif = await this.resolveField(Notification, "errorNotifications", true, "specific");
let passNotif = await this.resolveField(Notification, "passNotifications", true, "specific");
}
async saveA(env){
if(lib.isLocalEnv(env)) return;
return await this.createOrUpdate(env);
}
async saveB(env){
if(!this.isGeneric){
await this.resolve();
}
this.cleanup();
if(lib.isLocalEnv(env)){
log(chalk`Saving rule {green ${this.name}} to {blue ${lib.envName(env)}}.`)
writeFileSync(this.localpath, JSON.stringify(orderedObjectKeys(this.data), null, 4));
}else{
return await this.createOrUpdate(env);
}
}
get immutable(){
return false;
}
async createOrUpdate(env){
write(chalk`First pass rule {green ${this.name}} to {green ${env}}: `);
await this.acclimatize(env);
if(this.immutable){
log(chalk`{magenta IMMUTABLE}. Nothing to do.`);
return;
}
//First query the api to see if this already exists.
let remote = await Rule.getByName(env, this.name);
this.idMap = this.idMap || {};
this.relationships.transitions = {
data: await this.constructWorkflowTransitions(),
};
if(remote){
this.idMap[env] = remote.id;
log(chalk`exists ${remote.chalkPrint(false)}`);
if (configObject.skipStarred) {
write("no starred, ");
this.data.attributes.starred = undefined;
}
write("replace, ");
let res = await lib.makeAPIRequest({
env, path: `/workflowRules/${this.idMap[env]}`, method: "PUT",
payload: {data: this.data},
});
} else {
write("create, ");
let res = await lib.makeAPIRequest({
env, path: `/workflowRules`, method: "POST",
payload: {data: this.data},
});
this.idMap[env] = res.data.id;
}
write("id ");
log(this.idMap[env]);
}
async patchStrip(){
delete this.data.attributes.createdAt;
delete this.data.attributes.starred;
delete this.data.attributes.updatedAt;
this.nexts = this.data.relationships.dynamicNexts;
delete this.data.relationships.dynamicNexts;
// TEMP FIX FOR BUG IN SDVI
if(this.relationships.passMetadata && this.relationships.passMetadata[0]){
log("HAS PASS");
log(this.name);
log("HAS PASS");
}
delete this.relationships.passMetadata;
if(this.relationships.errorMetadata && this.relationships.errorMetadata[0]){
log("HAS PASS");
log(this.name);
log("HAS PASS");
}
delete this.relationships.errorMetadata;
// This is commented out because it was fixed.
//for(let key in this.relationships){
//let relationship = this.relationships[key];
//if(!relationship.data || relationship.data instanceof Array && !relationship.data[0]){
//delete this.relationships[key];
//}
//}
}
async deleteRemoteVersion(env, id=null){
if(lib.isLocalEnv(env)) return false;
if(!id){
let remote = await Rule.getByName(env, this.name);
id = remote.id;
}
return await lib.makeAPIRequest({
env, path: `/workflowRules/${id}`,
method: "DELETE",
});
}
async delete(){
if(lib.isLocalEnv(this.remote)) return false;
return await this.deleteRemoteVersion(this.remote, this.id);
}
get localpath(){
return this._localpath || join(configObject.repodir, this.subproject || "", "silo-rules", this.name + ".json");
}
async resolve(){
let preset = await this.resolveField(Preset, "preset", false);
//log(preset);
let pNext = await this.resolveField(Rule, "passNext", false);
let eNext = await this.resolveField(Rule, "errorNext", false);
let proType = await this.resolveField(Provider, "providerType", false);
let proTag = await this.resolveField(Tag, "providerFilterTag", false);
if(proTag && this.data.attributes.providerFilter) {
delete this.data.attributes.providerFilter
}
//log("Dynamic nexts")
let dynamicNexts = await this.resolveField(Rule, "dynamicNexts", true);
//log(dynamicNexts);
let enterNotif = await this.resolveField(Notification, "enterNotifications", true);
let errorNotif = await this.resolveField(Notification, "errorNotifications", true);
let passNotif = await this.resolveField(Notification, "passNotifications", true);
//TODO Unsupported
delete this.relationships["enterMetadata"]
delete this.relationships["errorMetadata"]
this.isGeneric = true;
return {
preset, proType, proTag,
pNext, eNext,
dynamicNexts,
errorNotif, enterNotif, passNotif,
};
}
chalkPrint(pad=true){
let id = String("R-" + (this.remote && this.remote + "-" + this.id || "LOCAL"))
let sub = "";
if(this.subproject){
sub = chalk`{yellow ${this.subproject}}`;
}
if(pad) id = id.padStart(13);
try{
return chalk`{green ${id}}: ${sub}{blue ${this.name}}`;
}catch(e){
return this.data;
}
}
async constructWorkflowTransitions() {
let transitions = [];
let dynamicNexts = this.nexts?.data || [];
if(dynamicNexts.length == 0) return [];
write(chalk`transition mapping: `);
for(let transition of dynamicNexts) {
write(chalk`{green ${transition.meta.transition}}:`);
let filters = {
toWorkflowRuleId: transition.id,
name: transition.meta.transition,
fromWorkflowRuleId: this.id
};
let res = await lib.makeAPIRequest({
env: this.remote, path: `/workflowTransitions`, method: "GET",
qs: {
filter: JSON.stringify(filters),
},
});
let newTransitionId = 0;
if(res.data.length > 0){
write(chalk`{blue found} `);
let firstTransition = res.data[0];
newTransitionId = firstTransition.id;
}else{
write(chalk`{magenta create} `);
let newTransitionPayload = {
"attributes": {
"name": filters.name,
},
"relationships": {
"fromWorkflowRule": {
"data": {
"id": filters.fromWorkflowRuleId,
"type": "workflowRules"
}
},
"toWorkflowRule": {
"data": {
"id": filters.toWorkflowRuleId,
"type": "workflowRules"
}
}
},
"type": "workflowTransitions",
};
let newTransition = await lib.makeAPIRequest({
env: this.remote, path: `/workflowTransitions`, method: "POST",
payload: {
data: newTransitionPayload,
}
});
newTransitionId = newTransition.data.id;
}
transitions.push({
"id": newTransitionId,
"type": "workflowTransitions",
});
write(chalk`{yellow ${newTransitionId}}, `);
}
write(chalk`t. done, `);
return transitions;
}
}
defineAssoc(Rule, "name", "data.attributes.name");
defineAssoc(Rule, "description", "data.attributes.description");
defineAssoc(Rule, "id", "data.id");
defineAssoc(Rule, "relationships", "data.relationships");
defineAssoc(Rule, "isGeneric", "meta.isGeneric");
defineAssoc(Rule, "remote", "meta.remote");
defineAssoc(Rule, "subproject", "meta.project");
defineAssoc(Rule, "idMap", "meta.idMap");
defineAssoc(Rule, "nexts", "meta.nexts");
Rule.endpoint = "workflowRules";
export default Rule;