Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions client/src/app/components/pipeline/pipeline.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,80 @@ describe('PipelineComponent', () => {
expect(logsLink?.getAttribute('href')).toContain('/repo/7/ci-cd/runs/42/logs');
});

it('surfaces runs whose workflow is in no group under an "Ungrouped" fallback', async () => {
const groupedRun: WorkflowRunDto = {
id: 1,
workflowId: 5,
name: 'CI',
displayTitle: 'CI',
status: 'COMPLETED',
conclusion: 'SUCCESS',
htmlUrl: 'https://github.com/org/repo/actions/runs/1',
label: 'TEST',
createdAt: '2026-01-01T10:00:00Z',
updatedAt: '2026-01-01T10:01:00Z',
};
const ungroupedRun: WorkflowRunDto = {
id: 2,
workflowId: 999,
name: 'Orphan Workflow',
displayTitle: 'Orphan Workflow',
status: 'COMPLETED',
conclusion: 'SUCCESS',
htmlUrl: 'https://github.com/org/repo/actions/runs/2',
label: 'NONE',
createdAt: '2026-01-01T10:00:00Z',
updatedAt: '2026-01-01T10:01:00Z',
};

fixture.componentRef.setInput('selector', { repositoryId: 7, pullRequestId: 11 });
Object.defineProperty(component, 'branchQuery', { configurable: true, value: { isPending: () => false, data: () => [] } });
Object.defineProperty(component, 'pullRequestQuery', {
configurable: true,
value: { isPending: () => false, data: () => [groupedRun, ungroupedRun] },
});
Object.defineProperty(component, 'groupsQuery', {
configurable: true,
value: { data: () => [{ id: 1, name: 'CI Group', memberships: [{ workflowId: 5, orderIndex: 0 }] }] },
});

const groups = component.pipeline().groups;
const ciGroup = groups.find(g => g.name === 'CI Group');
const ungrouped = groups.find(g => g.name === 'Ungrouped');
expect(ciGroup?.workflows.map(w => w.id)).toEqual([1]);
expect(ungrouped).toBeTruthy();
expect(ungrouped?.workflows.map(w => w.id)).toEqual([2]);
});

it('does not add an "Ungrouped" fallback when every run belongs to a group', async () => {
const groupedRun: WorkflowRunDto = {
id: 1,
workflowId: 5,
name: 'CI',
displayTitle: 'CI',
status: 'COMPLETED',
conclusion: 'SUCCESS',
htmlUrl: 'https://github.com/org/repo/actions/runs/1',
label: 'TEST',
createdAt: '2026-01-01T10:00:00Z',
updatedAt: '2026-01-01T10:01:00Z',
};

fixture.componentRef.setInput('selector', { repositoryId: 7, pullRequestId: 11 });
Object.defineProperty(component, 'branchQuery', { configurable: true, value: { isPending: () => false, data: () => [] } });
Object.defineProperty(component, 'pullRequestQuery', {
configurable: true,
value: { isPending: () => false, data: () => [groupedRun] },
});
Object.defineProperty(component, 'groupsQuery', {
configurable: true,
value: { data: () => [{ id: 1, name: 'CI Group', memberships: [{ workflowId: 5, orderIndex: 0 }] }] },
});

const groups = component.pipeline().groups;
expect(groups.find(g => g.name === 'Ungrouped')).toBeUndefined();
});

it('does not render workflow logs icon link without write permission', async () => {
const workflowRun: WorkflowRunDto = {
id: 42,
Expand Down
15 changes: 15 additions & 0 deletions client/src/app/components/pipeline/pipeline.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ export class PipelineComponent {
};
});

// Fallback bucket: surface runs whose workflow is not a member of ANY configured
// group so a newly added or renamed workflow (e.g. after a CI refactor that
// introduces a new orchestrator workflow) stays visible in the pipeline instead of
// silently disappearing until someone manually adds it to a group.
const groupedWorkflowIds = new Set(workflowGroups.flatMap(group => group.memberships?.map(membership => membership.workflowId) ?? []));
const ungroupedRuns = workflowRuns.filter(run => run.workflowId === undefined || !groupedWorkflowIds.has(run.workflowId));
if (ungroupedRuns.length > 0) {
groupedWorkflowsRuns.push({
name: 'Ungrouped',
id: -1,
workflows: ungroupedRuns,
Comment on lines +117 to +120
isLastWithWorkflows: false,
});
}

let lastWithWorkflowsFound = false;
// For loop in reverse to set isLastWithWorkflows in the correct order
groupedWorkflowsRuns.reverse().forEach(group => {
Expand Down
Loading