1
0
Fork 0

fix(resolve): split up the resolve labels

This commit is contained in:
bzlibby 2020-08-29 00:18:35 -05:00 committed by Sean Sube
parent c1b611ce38
commit 804c3bb133
8 changed files with 143 additions and 134 deletions

View File

@ -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) | |

View File

@ -1,15 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [cautious-journey](./cautious-journey.md) &gt; [resolveLabels](./cautious-journey.resolvelabels.md)
[Home](./index.md) &gt; [cautious-journey](./cautious-journey.md) &gt; [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

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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,

View File

@ -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);
});
}

View File

@ -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',