2020-08-22 17:25:19 +00:00
|
|
|
import { doesExist, InvalidArgumentError, mustExist } from '@apextoaster/js-utils';
|
2020-08-14 04:07:47 +00:00
|
|
|
import { Logger } from 'noicejs';
|
2020-08-13 01:01:48 +00:00
|
|
|
|
2020-08-20 22:08:57 +00:00
|
|
|
import { ProjectConfig } from './config';
|
|
|
|
import { getLabelColor, getLabelNames, getValueName } from './labels';
|
2020-08-13 01:40:28 +00:00
|
|
|
import { LabelUpdate, Remote } from './remote';
|
2020-08-29 05:18:35 +00:00
|
|
|
import { resolveProject } from './resolve';
|
2021-08-01 23:51:35 +00:00
|
|
|
import { compareItems, defaultTo, defaultUntil, RandomGenerator } from './utils';
|
2020-08-12 01:39:53 +00:00
|
|
|
|
|
|
|
export interface SyncOptions {
|
2020-08-14 04:07:47 +00:00
|
|
|
logger: Logger;
|
2020-08-18 13:54:57 +00:00
|
|
|
project: ProjectConfig;
|
2021-08-01 23:51:35 +00:00
|
|
|
random: RandomGenerator;
|
2020-08-12 01:39:53 +00:00
|
|
|
remote: Remote;
|
|
|
|
}
|
|
|
|
|
2020-08-15 21:06:26 +00:00
|
|
|
/**
|
2020-08-15 21:10:23 +00:00
|
|
|
* goes through and resolves each issue in the project.
|
|
|
|
* if there are changes and no errors, then updates the issue.
|
2020-08-15 21:06:26 +00:00
|
|
|
*/
|
|
|
|
export async function syncIssueLabels(options: SyncOptions): Promise<unknown> {
|
2020-08-18 13:54:57 +00:00
|
|
|
const { logger, project, remote } = options;
|
2021-08-04 04:03:58 +00:00
|
|
|
|
|
|
|
logger.debug({ project }, 'syncing issue labels for project');
|
|
|
|
|
|
|
|
try {
|
|
|
|
const issues = await remote.listIssues({
|
|
|
|
project: project.name,
|
2020-08-14 03:36:30 +00:00
|
|
|
});
|
|
|
|
|
2021-08-04 04:03:58 +00:00
|
|
|
for (const issue of issues) {
|
|
|
|
logger.info({ issue }, 'project issue');
|
2020-08-15 23:50:39 +00:00
|
|
|
|
2021-08-04 04:03:58 +00:00
|
|
|
const { changes, errors, labels } = resolveProject({
|
|
|
|
flags: project.flags,
|
|
|
|
initial: project.initial,
|
|
|
|
labels: issue.labels,
|
|
|
|
states: project.states,
|
2020-08-14 03:36:30 +00:00
|
|
|
});
|
2020-08-18 13:54:57 +00:00
|
|
|
|
2021-08-04 04:03:58 +00:00
|
|
|
logger.debug({ changes, errors, issue, labels }, 'resolved labels');
|
|
|
|
|
|
|
|
// TODO: prompt user if they want to update this particular issue
|
|
|
|
const sameLabels = compareItems(issue.labels, labels) || changes.length === 0;
|
|
|
|
if (sameLabels === false && errors.length === 0) {
|
|
|
|
logger.info({ changes, errors, issue, labels }, 'updating issue');
|
|
|
|
await remote.updateIssue({
|
2020-08-18 13:54:57 +00:00
|
|
|
...issue,
|
2021-08-04 04:03:58 +00:00
|
|
|
labels,
|
2020-08-18 13:54:57 +00:00
|
|
|
});
|
2021-08-04 04:03:58 +00:00
|
|
|
|
|
|
|
if (project.comment) {
|
|
|
|
await remote.createComment({
|
|
|
|
...issue,
|
|
|
|
changes,
|
|
|
|
errors,
|
|
|
|
});
|
|
|
|
}
|
2020-08-18 13:54:57 +00:00
|
|
|
}
|
2020-08-14 03:36:30 +00:00
|
|
|
}
|
2021-08-04 04:03:58 +00:00
|
|
|
} catch (err) {
|
|
|
|
logger.error(err, 'error syncing issue labels');
|
2020-08-12 01:39:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
2020-08-15 21:41:48 +00:00
|
|
|
export async function syncProjectLabels(options: SyncOptions): Promise<unknown> {
|
2020-08-18 13:54:57 +00:00
|
|
|
const { logger, project, remote } = options;
|
|
|
|
|
2021-08-04 04:03:58 +00:00
|
|
|
logger.debug({ project }, 'syncing project labels');
|
2020-08-13 00:24:05 +00:00
|
|
|
|
2021-08-04 04:03:58 +00:00
|
|
|
try {
|
|
|
|
const labels = await remote.listLabels({
|
|
|
|
project: project.name,
|
|
|
|
});
|
2020-08-13 00:24:05 +00:00
|
|
|
|
2021-08-04 04:03:58 +00:00
|
|
|
const present = new Set(labels.map((l) => l.name));
|
|
|
|
const desired = getLabelNames(project.flags, project.states);
|
|
|
|
const combined = new Set([...desired, ...present]);
|
|
|
|
|
|
|
|
for (const label of combined) {
|
|
|
|
const exists = present.has(label);
|
|
|
|
const expected = desired.has(label);
|
|
|
|
|
|
|
|
logger.info({
|
|
|
|
exists,
|
|
|
|
expected,
|
|
|
|
label,
|
|
|
|
}, 'label');
|
|
|
|
|
|
|
|
if (exists) {
|
|
|
|
if (expected) {
|
|
|
|
const data = mustExist(labels.find((l) => l.name === label));
|
2021-08-06 00:39:39 +00:00
|
|
|
logger.info({ data, label }, 'update label');
|
2021-08-04 04:03:58 +00:00
|
|
|
await updateLabel(options, data);
|
|
|
|
} else {
|
|
|
|
logger.warn({ label }, 'remove label');
|
|
|
|
await deleteLabel(options, label);
|
|
|
|
}
|
2020-08-13 00:24:05 +00:00
|
|
|
} else {
|
2021-08-04 04:03:58 +00:00
|
|
|
if (expected) {
|
|
|
|
logger.info({ label }, 'create label');
|
|
|
|
await createLabel(options, label);
|
|
|
|
} else {
|
|
|
|
// skip
|
2021-08-06 00:39:39 +00:00
|
|
|
logger.debug({ label }, 'label exists');
|
2021-08-04 04:03:58 +00:00
|
|
|
}
|
2020-08-13 00:24:05 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-04 04:03:58 +00:00
|
|
|
} catch (err) {
|
|
|
|
logger.error(err, 'error syncing project labels');
|
2020-08-12 01:39:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
2020-08-13 01:01:48 +00:00
|
|
|
|
2020-08-13 23:44:13 +00:00
|
|
|
export async function createLabel(options: SyncOptions, name: string) {
|
2020-08-18 13:54:57 +00:00
|
|
|
const { project, remote } = options;
|
|
|
|
|
|
|
|
const flag = project.flags.find((it) => name === it.name);
|
2020-08-13 23:44:13 +00:00
|
|
|
if (doesExist(flag)) {
|
2020-08-18 13:54:57 +00:00
|
|
|
await remote.createLabel({
|
|
|
|
color: getLabelColor(project.colors, options.random, flag),
|
2020-08-13 23:44:13 +00:00
|
|
|
desc: mustExist(flag.desc),
|
|
|
|
name,
|
2020-08-18 13:54:57 +00:00
|
|
|
project: project.name,
|
2020-08-13 23:44:13 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-18 13:54:57 +00:00
|
|
|
const state = project.states.find((it) => name.startsWith(it.name));
|
2020-08-13 23:44:13 +00:00
|
|
|
if (doesExist(state)) {
|
2020-08-15 17:38:33 +00:00
|
|
|
const value = state.values.find((it) => getValueName(state, it) === name);
|
2020-08-13 23:44:13 +00:00
|
|
|
if (doesExist(value)) {
|
2020-08-18 13:54:57 +00:00
|
|
|
await remote.createLabel({
|
|
|
|
color: getLabelColor(project.colors, options.random, state, value),
|
2020-08-15 15:12:41 +00:00
|
|
|
desc: defaultUntil(value.desc, state.desc, ''),
|
2020-08-15 17:38:33 +00:00
|
|
|
name: getValueName(state, value),
|
2020-08-18 13:54:57 +00:00
|
|
|
project: project.name,
|
2020-08-13 23:44:13 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2020-08-13 01:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 17:25:19 +00:00
|
|
|
export async function deleteLabel(options: SyncOptions, name: string) {
|
|
|
|
const { project, remote } = options;
|
|
|
|
|
|
|
|
// TODO: check if label is in use, prompt user if they want to remove it
|
|
|
|
await remote.deleteLabel({
|
|
|
|
name,
|
|
|
|
project: project.name,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function diffUpdateLabel(options: SyncOptions, prevLabel: LabelUpdate, newLabel: LabelUpdate) {
|
2020-08-18 13:54:57 +00:00
|
|
|
const { logger, project } = options;
|
|
|
|
|
2020-08-13 01:01:48 +00:00
|
|
|
const dirty =
|
2020-08-22 17:25:19 +00:00
|
|
|
prevLabel.color !== mustExist(newLabel.color) ||
|
|
|
|
prevLabel.desc !== mustExist(newLabel.desc);
|
2020-08-13 01:01:48 +00:00
|
|
|
|
|
|
|
if (dirty) {
|
2020-08-13 01:40:28 +00:00
|
|
|
const body = {
|
2020-08-22 17:25:19 +00:00
|
|
|
color: defaultTo(newLabel.color, prevLabel.color),
|
|
|
|
desc: defaultTo(newLabel.desc, prevLabel.desc),
|
|
|
|
name: prevLabel.name,
|
2020-08-18 13:54:57 +00:00
|
|
|
project: project.name,
|
2020-08-13 01:40:28 +00:00
|
|
|
};
|
|
|
|
|
2020-08-22 17:25:19 +00:00
|
|
|
logger.debug({ body, newLabel, oldLabel: prevLabel }, 'updating label');
|
2020-08-13 01:40:28 +00:00
|
|
|
const resp = await options.remote.updateLabel(body);
|
2020-08-18 13:54:57 +00:00
|
|
|
logger.debug({ resp }, 'update response');
|
2020-08-13 01:40:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-22 17:25:19 +00:00
|
|
|
export async function updateLabel(options: SyncOptions, label: LabelUpdate): Promise<void> {
|
2020-08-18 13:54:57 +00:00
|
|
|
const { project } = options;
|
2020-08-22 17:25:19 +00:00
|
|
|
|
2020-08-18 13:54:57 +00:00
|
|
|
const flag = project.flags.find((it) => label.name === it.name);
|
2020-08-13 01:40:28 +00:00
|
|
|
if (doesExist(flag)) {
|
2020-08-18 13:54:57 +00:00
|
|
|
const color = getLabelColor(project.colors, options.random, flag);
|
2020-08-22 17:25:19 +00:00
|
|
|
return diffUpdateLabel(options, label, {
|
2020-08-15 21:34:28 +00:00
|
|
|
color,
|
2020-08-13 01:40:28 +00:00
|
|
|
desc: defaultTo(flag.desc, label.desc),
|
|
|
|
name: flag.name,
|
2020-08-18 13:54:57 +00:00
|
|
|
project: project.name,
|
2020-08-13 01:40:28 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-08-18 13:54:57 +00:00
|
|
|
const state = project.states.find((it) => label.name.startsWith(it.name));
|
2020-08-13 01:40:28 +00:00
|
|
|
if (doesExist(state)) {
|
2020-08-15 17:38:33 +00:00
|
|
|
const value = state.values.find((it) => getValueName(state, it) === label.name);
|
2020-08-13 01:40:28 +00:00
|
|
|
if (doesExist(value)) {
|
2020-08-18 13:54:57 +00:00
|
|
|
const color = mustExist(getLabelColor(project.colors, options.random, state, value));
|
2020-08-22 17:25:19 +00:00
|
|
|
return diffUpdateLabel(options, label, {
|
2020-08-15 21:34:28 +00:00
|
|
|
color,
|
2020-08-13 01:40:28 +00:00
|
|
|
desc: defaultTo(value.desc, label.desc),
|
2020-08-15 17:38:33 +00:00
|
|
|
name: getValueName(state, value),
|
2020-08-18 13:54:57 +00:00
|
|
|
project: project.name,
|
2020-08-13 01:40:28 +00:00
|
|
|
});
|
|
|
|
}
|
2020-08-13 01:01:48 +00:00
|
|
|
}
|
2020-08-22 17:25:19 +00:00
|
|
|
|
|
|
|
throw new InvalidArgumentError('label is not present in options');
|
2020-08-13 01:01:48 +00:00
|
|
|
}
|