fix(resolve): split up the resolve labels
This commit is contained in:
parent
c1b611ce38
commit
804c3bb133
|
@ -15,7 +15,7 @@
|
|||
|
||||
| Function | Description |
|
||||
| --- | --- |
|
||||
| [resolveLabels(options)](./cautious-journey.resolvelabels.md) | Resolve the desired set of labels, given a starting set and the flags/states to be applied. |
|
||||
| [resolveProject(options)](./cautious-journey.resolveproject.md) | |
|
||||
| [syncIssueLabels(options)](./cautious-journey.syncissuelabels.md) | goes through and resolves each issue in the project. if there are changes and no errors, then updates the issue. |
|
||||
| [syncProjectLabels(options)](./cautious-journey.syncprojectlabels.md) | |
|
||||
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [resolveLabels](./cautious-journey.resolvelabels.md)
|
||||
[Home](./index.md) > [cautious-journey](./cautious-journey.md) > [resolveProject](./cautious-journey.resolveproject.md)
|
||||
|
||||
## resolveLabels() function
|
||||
|
||||
Resolve the desired set of labels, given a starting set and the flags/states to be applied.
|
||||
## resolveProject() function
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare function resolveLabels(options: ResolveInput): ResolveResult;
|
||||
export declare function resolveProject(options: ResolveInput): ResolveResult;
|
||||
```
|
||||
|
||||
## Parameters
|
|
@ -4,7 +4,7 @@ export { ChangeSet, FlagLabel, StateLabel, StateValue } from './labels';
|
|||
export { Remote, RemoteOptions } from './remote';
|
||||
export { GithubRemote } from './remote/github';
|
||||
export { GitlabRemote } from './remote/gitlab';
|
||||
export { ResolveInput, ResolveResult, resolveLabels } from './resolve';
|
||||
export { ResolveInput, ResolveResult, resolveProject } from './resolve';
|
||||
export { syncIssueLabels, SyncOptions, syncProjectLabels } from './sync';
|
||||
|
||||
const STATUS_ERROR = 1;
|
||||
|
|
|
@ -16,7 +16,7 @@ export { FlagLabel, StateLabel } from './labels';
|
|||
export { Remote, RemoteOptions } from './remote';
|
||||
export { GithubRemote } from './remote/github';
|
||||
export { GitlabRemote } from './remote/gitlab';
|
||||
export { resolveLabels } from './resolve';
|
||||
export { resolveProject } from './resolve';
|
||||
export { syncIssueLabels, syncProjectLabels } from './sync';
|
||||
|
||||
const ARGS_START = 2;
|
||||
|
|
241
src/resolve.ts
241
src/resolve.ts
|
@ -1,6 +1,6 @@
|
|||
import { doesExist } from '@apextoaster/js-utils';
|
||||
|
||||
import { BaseLabel, FlagLabel, getValueName, prioritySort, StateLabel } from './labels';
|
||||
import { BaseLabel, FlagLabel, getValueName, prioritySort, StateLabel, StateValue } from './labels';
|
||||
import { defaultUntil } from './utils';
|
||||
|
||||
/**
|
||||
|
@ -61,131 +61,142 @@ export interface ResolveResult {
|
|||
* Resolve the desired set of labels, given a starting set and the flags/states to be
|
||||
* applied.
|
||||
*/
|
||||
/* eslint-disable-next-line sonarjs/cognitive-complexity */
|
||||
export function resolveLabels(options: ResolveInput): ResolveResult {
|
||||
const activeLabels = new Set(options.labels);
|
||||
const changes: Array<ChangeRecord> = [];
|
||||
const errors: Array<ErrorRecord> = [];
|
||||
function resolveBaseLabel(label: BaseLabel, anticipatedResult: ResolveResult, activeLabels: Set<string>) {
|
||||
if (activeLabels.has(label.name) === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const requiredLabel of label.requires) {
|
||||
if (!activeLabels.has(requiredLabel.name)) {
|
||||
if (activeLabels.delete(label.name)) {
|
||||
anticipatedResult.changes.push({
|
||||
cause: requiredLabel.name,
|
||||
effect: ChangeVerb.REQUIRED,
|
||||
label: label.name,
|
||||
});
|
||||
}
|
||||
|
||||
function checkLabelRules(label: BaseLabel) {
|
||||
if (activeLabels.has(label.name) === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const requiredLabel of label.requires) {
|
||||
if (!activeLabels.has(requiredLabel.name)) {
|
||||
if (activeLabels.delete(label.name)) {
|
||||
changes.push({
|
||||
cause: requiredLabel.name,
|
||||
effect: ChangeVerb.REQUIRED,
|
||||
label: label.name,
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const addedLabel of label.adds) {
|
||||
// Set.add does not return a boolean, unlike the other methods
|
||||
if (!activeLabels.has(addedLabel.name)) {
|
||||
activeLabels.add(addedLabel.name);
|
||||
changes.push({
|
||||
cause: label.name,
|
||||
effect: ChangeVerb.CREATED,
|
||||
label: addedLabel.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const removedLabel of label.removes) {
|
||||
if (activeLabels.delete(removedLabel.name)) {
|
||||
changes.push({
|
||||
cause: label.name,
|
||||
effect: ChangeVerb.REMOVED,
|
||||
label: removedLabel.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const addedLabel of label.adds) {
|
||||
// Set.add does not return a boolean, unlike the other methods
|
||||
if (!activeLabels.has(addedLabel.name)) {
|
||||
activeLabels.add(addedLabel.name);
|
||||
anticipatedResult.changes.push({
|
||||
cause: label.name,
|
||||
effect: ChangeVerb.CREATED,
|
||||
label: addedLabel.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const removedLabel of label.removes) {
|
||||
if (activeLabels.delete(removedLabel.name)) {
|
||||
anticipatedResult.changes.push({
|
||||
cause: label.name,
|
||||
effect: ChangeVerb.REMOVED,
|
||||
label: removedLabel.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolveBecomes(label: BaseLabel, anticipatedResult: ResolveResult, activeLabels: Set<string>, value: StateValue): boolean {
|
||||
for (const become of value.becomes) {
|
||||
const matches = become.matches.every((l) => activeLabels.has(l.name));
|
||||
|
||||
if (matches) {
|
||||
resolveBaseLabel({
|
||||
...label,
|
||||
adds: become.adds,
|
||||
removes: [...become.matches, ...become.removes],
|
||||
requires: [],
|
||||
}, anticipatedResult, activeLabels);
|
||||
|
||||
if (activeLabels.delete(name)) {
|
||||
anticipatedResult.changes.push({
|
||||
cause: name,
|
||||
effect: ChangeVerb.REMOVED,
|
||||
label: name,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to ensure that there is only 1 active value for the state
|
||||
* If no, remove any lower priority active values for the state
|
||||
* Need to run the normal (add, remove) rules
|
||||
* Need to run the becomes rules
|
||||
*/
|
||||
function resolveState(state: StateLabel, anticipatedResult: ResolveResult, activeLabels: Set<string>) {
|
||||
let activeValue;
|
||||
|
||||
const sortedValues = prioritySort(state.values);
|
||||
for (const value of sortedValues) {
|
||||
const name = getValueName(state, value);
|
||||
if (!activeLabels.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (doesExist(activeValue)) { // there is already an active value
|
||||
if (activeLabels.delete(name)) {
|
||||
anticipatedResult.changes.push({
|
||||
cause: name,
|
||||
effect: ChangeVerb.CONFLICTED,
|
||||
label: name,
|
||||
});
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const combinedValue: BaseLabel = {
|
||||
adds: [...state.adds, ...value.adds],
|
||||
name,
|
||||
priority: defaultUntil(value.priority, state.priority, 0),
|
||||
removes: [...state.removes, ...value.removes],
|
||||
requires: [...state.requires, ...value.requires],
|
||||
};
|
||||
|
||||
if (resolveBaseLabel(combinedValue, anticipatedResult, activeLabels)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resolveBecomes(combinedValue, anticipatedResult, activeLabels, value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
activeValue = name;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveProject(options: ResolveInput): ResolveResult {
|
||||
const result: ResolveResult = {
|
||||
changes: [],
|
||||
errors: [],
|
||||
labels: [],
|
||||
};
|
||||
const activeLabels = new Set(options.labels);
|
||||
|
||||
const sortedFlags = prioritySort(options.flags);
|
||||
for (const flag of sortedFlags) {
|
||||
checkLabelRules(flag);
|
||||
resolveBaseLabel(flag, result, activeLabels);
|
||||
}
|
||||
|
||||
const sortedStates = prioritySort(options.states);
|
||||
for (const state of sortedStates) {
|
||||
let activeValue;
|
||||
|
||||
const sortedValues = prioritySort(state.values);
|
||||
for (const value of sortedValues) {
|
||||
const name = getValueName(state, value);
|
||||
if (activeLabels.has(name)) {
|
||||
if (doesExist(activeValue)) {
|
||||
if (activeLabels.delete(name)) {
|
||||
changes.push({
|
||||
cause: name,
|
||||
effect: ChangeVerb.CONFLICTED,
|
||||
label: name,
|
||||
});
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const combinedValue: BaseLabel = {
|
||||
adds: [...state.adds, ...value.adds],
|
||||
name,
|
||||
priority: defaultUntil(value.priority, state.priority, 0),
|
||||
removes: [...state.removes, ...value.removes],
|
||||
requires: [...state.requires, ...value.requires],
|
||||
};
|
||||
|
||||
if (checkLabelRules(combinedValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: flatten this bit and remove the mutable boolean
|
||||
let removed = false;
|
||||
for (const become of value.becomes) {
|
||||
const matches = become.matches.every((l) => activeLabels.has(l.name));
|
||||
|
||||
if (matches) {
|
||||
checkLabelRules({
|
||||
...combinedValue,
|
||||
adds: become.adds,
|
||||
removes: [...become.matches, ...become.removes],
|
||||
requires: [],
|
||||
});
|
||||
|
||||
if (activeLabels.delete(name)) {
|
||||
changes.push({
|
||||
cause: name,
|
||||
effect: ChangeVerb.REMOVED,
|
||||
label: name,
|
||||
});
|
||||
removed = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
activeValue = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
resolveState(state, result, activeLabels);
|
||||
}
|
||||
|
||||
return {
|
||||
changes,
|
||||
errors,
|
||||
labels: Array.from(activeLabels),
|
||||
};
|
||||
result.labels = Array.from(activeLabels);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { prng } from 'seedrandom';
|
|||
import { ProjectConfig } from './config';
|
||||
import { getLabelColor, getLabelNames, getValueName } from './labels';
|
||||
import { LabelUpdate, Remote } from './remote';
|
||||
import { resolveLabels } from './resolve';
|
||||
import { resolveProject } from './resolve';
|
||||
import { compareItems, defaultTo, defaultUntil } from './utils';
|
||||
|
||||
export interface SyncOptions {
|
||||
|
@ -28,7 +28,7 @@ export async function syncIssueLabels(options: SyncOptions): Promise<unknown> {
|
|||
for (const issue of issues) {
|
||||
logger.info({ issue }, 'project issue');
|
||||
|
||||
const { changes, errors, labels } = resolveLabels({
|
||||
const { changes, errors, labels } = resolveProject({
|
||||
flags: project.flags,
|
||||
labels: issue.labels,
|
||||
states: project.states,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { expect } from 'chai';
|
||||
|
||||
import { resolveLabels } from '../src/resolve';
|
||||
import { resolveProject } from '../src/resolve';
|
||||
import { TEST_CASES } from './resolve/cases';
|
||||
|
||||
const TEST_LABELS = ['foo', 'bar'];
|
||||
|
@ -8,7 +8,7 @@ const TEST_LABELS = ['foo', 'bar'];
|
|||
describe('resolve labels', () => {
|
||||
describe('with empty rule set', () => {
|
||||
it('should return the existing labels', () => {
|
||||
const result = resolveLabels({
|
||||
const result = resolveProject({
|
||||
flags: [],
|
||||
labels: TEST_LABELS,
|
||||
states: [],
|
||||
|
@ -18,7 +18,7 @@ describe('resolve labels', () => {
|
|||
});
|
||||
|
||||
it('should not make any changes', () => {
|
||||
const result = resolveLabels({
|
||||
const result = resolveProject({
|
||||
flags: [],
|
||||
labels: TEST_LABELS,
|
||||
states: [],
|
||||
|
@ -32,7 +32,7 @@ describe('resolve labels', () => {
|
|||
describe('resolver test cases', () => {
|
||||
for (const test of TEST_CASES) {
|
||||
it(`should resolve ${test.name}`, () => {
|
||||
const actualResult = resolveLabels(test.input);
|
||||
const actualResult = resolveProject(test.input);
|
||||
expect(actualResult).to.deep.equal(test.result);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { expect } from 'chai';
|
||||
|
||||
import { resolveLabels } from '../../src/resolve';
|
||||
import { resolveProject } from '../../src/resolve';
|
||||
|
||||
const TEST_LABELS = ['foo', 'bar'];
|
||||
|
||||
describe('resolve labels', () => {
|
||||
describe('flags with unfulfilled requires rule', () => {
|
||||
it('should be removed when required label is missing', () => {
|
||||
const result = resolveLabels({
|
||||
const result = resolveProject({
|
||||
flags: [{
|
||||
adds: [],
|
||||
name: 'gayle',
|
||||
|
@ -27,7 +27,7 @@ describe('resolve labels', () => {
|
|||
|
||||
describe('flags with fulfilled requires rule', () => {
|
||||
it('should make no changes', () => {
|
||||
const result = resolveLabels({
|
||||
const result = resolveProject({
|
||||
flags: [{
|
||||
adds: [],
|
||||
name: 'gayle',
|
||||
|
@ -47,7 +47,7 @@ describe('resolve labels', () => {
|
|||
|
||||
describe('flags with add rules', () => {
|
||||
it('should add the labels', () => {
|
||||
const result = resolveLabels({
|
||||
const result = resolveProject({
|
||||
flags: [{
|
||||
adds: [{
|
||||
name: 'linda',
|
||||
|
@ -67,7 +67,7 @@ describe('resolve labels', () => {
|
|||
|
||||
describe('flags with remove rules', () => {
|
||||
it('should remove labels', () => {
|
||||
const result = resolveLabels({
|
||||
const result = resolveProject({
|
||||
flags: [{
|
||||
adds: [],
|
||||
name: 'bob',
|
||||
|
|
Loading…
Reference in New Issue