Skip to content
This repository was archived by the owner on Jul 7, 2025. It is now read-only.

Commit 2567240

Browse files
Dominic Scheirlinckdominics
authored andcommitted
fix: read group sub-step IDs as excluded as well, when a pipeline is skipped
1 parent 9e793f8 commit 2567240

File tree

9 files changed

+73
-15
lines changed

9 files changed

+73
-15
lines changed

src/merge.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import debug from 'debug';
33
import _ from 'lodash';
44
import sendBuildkiteAnnotation from './annotate';
55
import { updateDecisions } from './decide';
6-
import Config from './models/config';
7-
import { isGroupStep, mergeGroups } from './models/group-step';
6+
import Config, { keysInConfigs } from './models/config';
7+
import { mergeGroups } from './models/group-step';
88
import { Pipeline } from './models/pipeline';
9-
import { Step } from './models/step';
9+
import { isGroupStep, Step } from './models/step';
1010
import { ARTIFACT_INJECTION_STEP_KEY, artifactInjectionSteps } from './steps/artifact-injection';
1111
import { nothingToDoSteps } from './steps/nothing-to-do';
1212
import { recordSuccessSteps } from './steps/record-success';
@@ -23,9 +23,7 @@ const log = debug('monofo:merge');
2323
* This method also mutates the steps of the passed-in configs directly
2424
*/
2525
export function replaceExcludedKeys(configs: Config[], hasArtifactStep: boolean): void {
26-
const excludedKeys: string[] = configs
27-
.filter((c) => !c.included)
28-
.flatMap((c) => c.steps.map((s) => (typeof s.key === 'string' ? s.key : '')).filter((v) => v));
26+
const excludedKeys: string[] = keysInConfigs(configs.filter((c: Config) => !c.included));
2927

3028
// If there's no artifact dependencies for the whole build, no need to wait for this step (it won't be added either)
3129
const replaceWith = hasArtifactStep ? ARTIFACT_INJECTION_STEP_KEY : '';

src/models/config.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import { FileHasher } from '../hash';
1313
import Reason, { ExcludeReasonType } from '../reason';
1414
import { globSet } from '../util/glob';
1515
import { strings } from '../util/helper';
16-
import { isGroupStep } from './group-step';
17-
import { Step } from './step';
16+
import { isGroupStep, keysInSteps, Step } from './step';
1817

1918
const log = debug('monofo:config');
2019

@@ -363,3 +362,7 @@ export default class Config {
363362
configs.forEach((c) => c.useFallback());
364363
}
365364
}
365+
366+
export function keysInConfigs(configs: Config[]): string[] {
367+
return configs.flatMap((config) => keysInSteps(config.steps));
368+
}

src/models/group-step.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import _ from 'lodash';
22
import { Pipeline } from './pipeline';
3-
import { GroupStep, Step } from './step';
3+
import { GroupStep, isGroupStep, Step } from './step';
44

55
/**
66
* Given a group step, returns the key that will be used for grouping on that step
@@ -37,10 +37,6 @@ function checkSimilarEnoughToMerge(step1: GroupStep, step2: GroupStep): void {
3737
}
3838
}
3939

40-
export function isGroupStep(step: Step): step is GroupStep {
41-
return step?.group === null || Boolean(step?.group);
42-
}
43-
4440
/**
4541
* Merges groups within a merged pipeline
4642
*

src/models/step.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,24 @@ export interface GroupStep extends AbstractStep {
2525
}
2626

2727
export type Step = CommandStep | GroupStep | TriggerStep | BlockStep | InputStep;
28+
29+
export function isGroupStep(step: Step): step is GroupStep {
30+
return step?.group === null || Boolean(step?.group);
31+
}
32+
33+
/**
34+
* Returns all step keys within the given set of steps
35+
*
36+
* Note that group steps con contain sub-steps, so it's not a simple map
37+
*/
38+
export function keysInSteps(steps: Step[]): string[] {
39+
return steps.flatMap((step: Step) => {
40+
const self = typeof step.key === 'string' ? [step.key] : [];
41+
42+
if (isGroupStep(step)) {
43+
return [...self, ...keysInSteps(step.steps)];
44+
}
45+
46+
return self;
47+
});
48+
}

src/reason.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export enum ExcludeReasonType {
2020
NEVER_INCLUDED = 'been always excluded by monorepo.matches === false',
2121
}
2222

23+
// TODO: should move to models, encompass decision state as well as reason (included/excluded should be modeled)
2324
export default class Reason {
2425
previousBuild?: string;
2526

test/merge.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { replaceExcludedKeys } from '../src/merge';
2+
import Config from '../src/models/config';
3+
import Reason, { ExcludeReasonType } from '../src/reason';
4+
import { ARTIFACT_INJECTION_STEP_KEY } from '../src/steps/artifact-injection';
5+
import { getProjectFixturePath } from './fixtures';
6+
7+
describe('merge', () => {
8+
describe('replaceExcludedKeys', () => {
9+
it('replaces depends_on inside group steps', async () => {
10+
const configs = await Config.getAll(getProjectFixturePath('groups'));
11+
12+
// decide the first two configs aren't being included
13+
configs[0].decide(false, new Reason(ExcludeReasonType.FORCED));
14+
configs[1].decide(false, new Reason(ExcludeReasonType.FORCED));
15+
16+
// the other two configs should be mutated by this operation
17+
replaceExcludedKeys(configs, true);
18+
19+
expect(configs[2].steps[0].depends_on).toStrictEqual([ARTIFACT_INJECTION_STEP_KEY]);
20+
expect(configs[3].steps[0].depends_on).toStrictEqual([ARTIFACT_INJECTION_STEP_KEY]);
21+
});
22+
});
23+
});

test/models/step.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Config from '../../src/models/config';
2+
import { keysInSteps } from '../../src/models/step';
3+
import { getProjectFixturePath } from '../fixtures';
4+
5+
describe('keysInSteps', () => {
6+
it('gets recursive keys', async () => {
7+
const configs = await Config.getAll(getProjectFixturePath('groups'));
8+
const keys = configs.map((config) => keysInSteps(config.steps));
9+
10+
expect(keys).toStrictEqual([
11+
['foo1-group', 'foo1'],
12+
['foo1-group', 'foo2'],
13+
['foo3-group', 'foo3'],
14+
['foo4-group', 'foo4'],
15+
]);
16+
});
17+
});

test/projects/groups/pipeline.foo3.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ steps:
99
key: "foo3-group"
1010
depends_on:
1111
- foo1-group
12+
- foo1
1213
steps:
1314
- command: echo "foo3" > foo3
1415
key: foo3

test/projects/groups/pipeline.foo4.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ monorepo:
22
matches: "foo/**/README.md"
33
expects: foo1
44

5-
# This tests our ability to stitch together groups, because otherwise Buildkite only supports appending to groups from later uploads
6-
75
steps:
86
- group: "foo4 group"
97
key: "foo4-group"

0 commit comments

Comments
 (0)