Separate provisioning jobs from activity log

This commit is contained in:
2026-10-07 23:15:55 -04:00
parent a223180835
commit 58375e6db0
10 changed files with 64 additions and 21 deletions
+27 -2
View File
@@ -58,6 +58,29 @@ describe('Activity query parameters', () => {
});
describe('unified retained Activity', () => {
it('excludes provisioning jobs from log totals and pages regardless of action or trigger, while retaining contextual history', async () => {
const api = await setup();
for (const action of ['create', 'start', 'restart', 'rebuild', 'stop', 'destroy']) {
for (const source of ['ui', 'autoscaling:policy', 'maintenance:1', 'campaign:release', 'beta:1', 'watchdog:finding']) {
api.operation(`${action}-${source}`, { action, source });
}
}
api.remote('check', { kind: 'instance.inspect', routine: 1 });
api.incident('finding');
api.maintenance(2);
const query = { excludeProvisioning: true, limit: 2 };
const first = api.activityHistory(query);
const last = api.activityHistory({ ...query, offset: 2 });
expect(first.total).toBe(3);
expect(last.total).toBe(3);
expect(first.rows).toHaveLength(2);
expect(last.rows).toHaveLength(1);
expect([...first.rows, ...last.rows].map(row => row.id).sort()).toEqual(['host:check', 'maintenance:2', 'recovery:finding']);
expect(api.activityHistory({ ...query, type: 'create' }).total).toBe(0);
expect(api.activityHistory({ ...query, source: 'autoscaling' }).total).toBe(0);
expect(api.activityHistory({ server }).total).toBe(39);
});
it('paginates the full union with exact totals, stable ties and removed-server history', async () => {
const api = await setup();
api.handle.transaction(() => {
@@ -205,12 +228,14 @@ describe('unified retained Activity', () => {
it('uses the same query contract for API and page loaders with no-store API responses', async () => {
const api = await setup();
api.operation('a'); api.operation('b', { state: 'failed' });
const url = new URL('http://localhost/activity?state=failed&limit=1');
api.remote('failed-check', { kind: 'instance.inspect', routine: 1, state: 'failed' });
const url = new URL('http://localhost/activity?state=failed&limit=1&excludeProvisioning=true');
const { GET } = await import('../../src/routes/api/activity/+server.js');
const { load } = await import('../../src/routes/activity/+page.server.js');
const response = GET({ url });
expect(response.headers.get('cache-control')).toBe('no-store');
expect(await response.json()).toEqual(JSON.parse(JSON.stringify(load({ url }).activity)));
expect(load({ url }).activity.rows[0].id).toBe('provisioning:b');
expect(load({ url }).activity.rows[0].id).toBe('host:failed-check');
expect(load({ url: new URL('http://localhost/activity?excludeProvisioning=false') }).activity.rows.some(row => row.id.startsWith('provisioning:'))).toBe(false);
});
});
+4 -2
View File
@@ -7,7 +7,9 @@ describe('application navigation', () => {
['/collections/launches/42', 'fleet', '/collections'],
['/hosts/42', 'fleet', '/hosts'],
['/hosts/operations', 'activity', '/activity'],
['/provisioning', 'activity', '/activity'],
['/provisioning', 'activity', '/provisioning'],
['/provisioning/job-1', 'activity', '/provisioning'],
['/deployments/job-1', 'activity', '/provisioning'],
['/activity', 'activity', '/activity'],
['/betas/new', 'releases', '/betas'],
['/campaigns/42', 'releases', '/campaigns'],
@@ -21,7 +23,7 @@ describe('application navigation', () => {
});
it('keeps Operations reachable from Activity and redirects legacy overview links', async () => {
expect(navigationGroups.find(group => group.id === 'activity')?.items[0].href).toBe('/activity');
expect(navigationGroups.find(group => group.id === 'activity')?.items.map(item => item.label)).toEqual(['Activity log', 'Provisioning', 'Maintenance']);
const { load } = await import('../../src/routes/operations/+page.server.js');
expect(load).toThrow(expect.objectContaining({ status: 308, location: '/activity' }));
});
+12 -2
View File
@@ -37,6 +37,15 @@ it('resets pagination when filtering and preserves the containing page, hash, an
expect(new URL(activityHistoryHref(filtered, { offset: '25' }), current).searchParams.get('state')).toBe('failed');
});
it('keeps provisioning excluded from log polling after filtering or clearing filters', () => {
const url = new URL('http://localhost/activity?type=create&excludeProvisioning=false');
const scope = { excludeProvisioning: true };
expect(new URL(activityApiHref(url, scope), url).searchParams.get('excludeProvisioning')).toBe('true');
const cleared = new URL(activityHistoryHref(url, { type: '', source: '', offset: '' }), url);
expect(activityQuery(cleared, scope).excludeProvisioning).toBe(true);
expect(activityQuery(new URL('http://localhost/servers/town?tab=history')).excludeProvisioning).toBeUndefined();
});
it('shows relative recent timestamps and preserves accurate older dates and invalid evidence', () => {
const now = Date.parse('2026-10-07T18:00:00Z');
expect(activityTimeLabel('2026-10-07 17:59:45', 'America/New_York', now)).toBe('15 s ago');
@@ -47,9 +56,10 @@ it('shows relative recent timestamps and preserves accurate older dates and inva
expect(activityTimeLabel('2026-10-08T18:00:00Z', 'America/New_York', now)).toBe('Oct 8, 2026, 2:00 PM EDT');
});
it('renders one page heading and keeps management tools available only to operators', () => {
it('renders one page heading with provisioning management and filters removed', () => {
const body = render(ActivityPage, { props: { data: { activity: report(), user: { role: 'admin' }, setupRequired: false } as any } }).body;
expect(body.match(/<h1[ >]/g)).toHaveLength(1); expect(body).toContain('Manage servers'); expect(body).toContain('href="/provisioning"');
expect(body.match(/<h1[ >]/g)).toHaveLength(1); expect(body).not.toContain('Manage servers'); expect(body).not.toContain('href="/provisioning"');
expect(body).not.toContain('<option value="create"'); expect(body).not.toContain('<option value="provisioning"');
const viewer = render(ActivityPage, { props: { data: { activity: report(), user: { role: 'viewer' }, setupRequired: false } as any } }).body;
expect(viewer).not.toContain('Manage servers');
});