diff --git a/package-lock.json b/package-lock.json index 746cc3c6..b06adb5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "svelte-highlight": "^7.8.2" }, "devDependencies": { - "@nais/ds-svelte-community": "^1.0.0-next.16", + "@nais/ds-svelte-community": "^1.0.0-next.18", "@navikt/ds-css": "^7.9.1", "@sveltejs/adapter-node": "^5.2.11", "@sveltejs/kit": "^2.15.2", @@ -213,7 +213,6 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", - "extraneous": true, "inBundle": true, "license": "MIT", "engines": { @@ -1157,14 +1156,14 @@ } }, "node_modules/@nais/ds-svelte-community": { - "version": "1.0.0-next.16", - "resolved": "https://europe-north1-npm.pkg.dev/nais-io/nais-public-npm/@nais/ds-svelte-community/-/@nais/ds-svelte-community-1.0.0-next.16.tgz", - "integrity": "sha512-S8wUPzpX4TxdLIZOSBQ8YU3/65/lqwtWyYTmIGfT0luBTFXiMC0IMAvHWCCKdLnBdpqF+gSINxrUASvXZKHIGw==", + "version": "1.0.0-next.18", + "resolved": "https://europe-north1-npm.pkg.dev/nais-io/nais-public-npm/@nais/ds-svelte-community/-/@nais/ds-svelte-community-1.0.0-next.18.tgz", + "integrity": "sha512-eU5X6+gCXdvyyRRq7tvCbD1wI45uPw3Z0rO8dTL9BdcgER2N62f/YkzLoFelbvA1gxRBo8X/Hn7pASN/U7bgUQ==", "dev": true, "license": "MIT", "dependencies": { - "@navikt/ds-css": "^7.9.1", - "@navikt/ds-tokens": "^7.9.1" + "@navikt/ds-css": "^7.10.0", + "@navikt/ds-tokens": "^7.10.0" }, "optionalDependencies": { "svelte-floating-ui": "^1.5.9" @@ -1175,16 +1174,16 @@ } }, "node_modules/@navikt/ds-css": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@navikt/ds-css/-/ds-css-7.9.2.tgz", - "integrity": "sha512-UXJyL8WNlfqACJLfU/MZ0Atv0eqxvECQfJW2li2zTdTHRQzHkfTMjMj3lNx4hCPo27gct33UfwAGRG1m4P9Q0w==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@navikt/ds-css/-/ds-css-7.10.0.tgz", + "integrity": "sha512-Iayk13Nb9V9NkCWowtzphwVgpsnilSPRcIy3RzwKiSLZl5CSW/S7/Xvdi2jhLEiPZtXlC52rMt6mkwmjvuT9Kw==", "dev": true, "license": "MIT" }, "node_modules/@navikt/ds-tokens": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@navikt/ds-tokens/-/ds-tokens-7.9.2.tgz", - "integrity": "sha512-N+x8kpRmb5sNn08ht5+jwA7Q94w0OK4PqVXeXNWojGkyt0agTfmgAIr30RL/3+A3zEzooBG/qnfKnHusz+28dg==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@navikt/ds-tokens/-/ds-tokens-7.10.0.tgz", + "integrity": "sha512-yozkLGtD5VtK1p/0+cHR4qWvYl6B6YPOV5aY4E1naBppuS5ykKtWvYeu2lxVlM6sWd6hkV3znbanoloyGFmj+Q==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 56526fb4..a4e55f57 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "test": "vitest" }, "devDependencies": { - "@nais/ds-svelte-community": "^1.0.0-next.16", + "@nais/ds-svelte-community": "^1.0.0-next.18", "@navikt/ds-css": "^7.9.1", "@sveltejs/adapter-node": "^5.2.11", "@sveltejs/kit": "^2.15.2", diff --git a/src/lib/icons/SortAscendingIcon.svelte b/src/lib/icons/SortAscendingIcon.svelte new file mode 100644 index 00000000..8057ae7b --- /dev/null +++ b/src/lib/icons/SortAscendingIcon.svelte @@ -0,0 +1,30 @@ + + + + + + diff --git a/src/lib/icons/SortDescendingIcon.svelte b/src/lib/icons/SortDescendingIcon.svelte new file mode 100644 index 00000000..de1ea4b1 --- /dev/null +++ b/src/lib/icons/SortDescendingIcon.svelte @@ -0,0 +1,30 @@ + + + + + + diff --git a/src/lib/utils/searchparams.svelte.ts b/src/lib/utils/searchparams.svelte.ts index 8df8de7b..d86d3c3b 100644 --- a/src/lib/utils/searchparams.svelte.ts +++ b/src/lib/utils/searchparams.svelte.ts @@ -4,7 +4,7 @@ import { page } from '$app/state'; export const changeParams = (params: Record) => { const query = new URLSearchParams(page.url.searchParams); for (const [key, value] of Object.entries(params)) { - if (value === '') { + if (key !== 'environments' && value === '') { query.delete(key); continue; } diff --git a/src/routes/team/[team]/(teamPages)/jobs/+page.gql b/src/routes/team/[team]/(teamPages)/jobs/+page.gql index a25fccab..d1010dac 100644 --- a/src/routes/team/[team]/(teamPages)/jobs/+page.gql +++ b/src/routes/team/[team]/(teamPages)/jobs/+page.gql @@ -1,10 +1,10 @@ query Jobs($team: Slug!, $orderBy: JobOrder, $filter: TeamJobsFilter) @cache(policy: NetworkOnly) { - team(slug: $team) @loading { - environments @loading { + team(slug: $team) { + environments { name } - jobs(first: 20, orderBy: $orderBy, filter: $filter) @paginate(mode: SinglePage) @loading { - pageInfo @loading { + jobs(first: 20, orderBy: $orderBy, filter: $filter) @paginate(mode: SinglePage) { + pageInfo { totalCount endCursor startCursor @@ -13,11 +13,19 @@ query Jobs($team: Slug!, $orderBy: JobOrder, $filter: TeamJobsFilter) @cache(pol pageEnd pageStart } - nodes @loading(count: 20) { + nodes { name environment { name } + runs(first: 1) { + nodes { + startTime + status { + state + } + } + } status { state } diff --git a/src/routes/team/[team]/(teamPages)/jobs/+page.svelte b/src/routes/team/[team]/(teamPages)/jobs/+page.svelte index f3a5175d..a93cd8f1 100644 --- a/src/routes/team/[team]/(teamPages)/jobs/+page.svelte +++ b/src/routes/team/[team]/(teamPages)/jobs/+page.svelte @@ -1,21 +1,30 @@ @@ -101,126 +96,208 @@ {@const jobs = $Jobs.data.team.jobs}
-
+
-

Jobs

+

Jobs

-
- + + A job is used for one-off or scheduled tasks meant to complete and then exit. + Learn more about jobs. + + +
+
+
+ {jobs.nodes.length} jobs +
+
+ + {#snippet trigger(props)} + + {/snippet} + 0 && Object.values(views).every(Boolean) + ? true + : Object.values(views).some(Boolean) + ? 'indeterminate' + : false} + onchange={(checked) => handleCheckboxChange('*', checked)} + > + All environments + + {#each $Jobs.data.team.environments as env} + handleCheckboxChange(env.name, checked)} + > + {env.name} + + {/each} + + {#if jobOrderDirection === OrderDirection.ASC} + + {:else} + + {/if} + + {#snippet trigger(props)} + + {/snippet} + + handleSortField(value as string)}>Name + handleSortField(value as string)}>Status + handleSortField(value as string)} + >Environment + handleSortField(value as string)}>Deployed + + + + {#if jobOrderField === JobOrderField.DEPLOYMENT_TIME} + handleSortDirection(value as string)} + >Newest + handleSortDirection(value as string)} + >Oldest + {:else} + handleSortDirection(value as string)} + >Ascending + handleSortDirection(value as string)} + >Descending + {/if} + + +
+ {#each jobs.nodes as job} +
+
+ {job.name} + {job.environment.name} +
+
+ {#if job.runs.nodes[0]?.startTime} +
+ {#if job.runs.nodes[0].status.state === 'RUNNING'} + + {:else if job.runs.nodes[0].status.state === 'PENDING'} + + {:else if job.runs.nodes[0].status.state === 'SUCCEEDED'} + + {:else if job.runs.nodes[0].status.state === 'FAILED'} + + {:else} + + {/if} + +
+ {/if} + {#if job.deploymentInfo.timestamp} +
+ + +
+ {/if} +
+
+ {/each}
+ {#if jobs.pageInfo.hasPreviousPage || jobs.pageInfo.hasNextPage} + {/if} {/if} @@ -232,14 +309,56 @@ align-items: center; align-self: stretch; margin: 1rem 0; + .heading { + display: flex; + align-items: center; + width: 50%; + gap: 4px; + h3 { + margin: 0; + } + } } - - .status { + .search { display: flex; - align-items: center; - justify-content: center; - line-height: 0.6; + justify-content: flex-end; + margin-bottom: 1rem; } + .jobs-wrapper { + border: 1px solid var(--a-border-default); + border-radius: 4px; + overflow: hidden; + .jobs-header { + background-color: var(--a-surface-subtle); + border-bottom: 1px solid var(--a-border-default); + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + } + .jobs-count { + font-weight: bold; + } + .jobs-list { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--a-border-default); + padding: 8px 12px; + } + .job-info { + min-width: 110px; + display: flex; + gap: 4px; + flex-direction: column; + } + .job-detail { + display: flex; + gap: 4px; + align-items: center; + } + } + .pagination { text-align: right; padding: 0.5rem; diff --git a/src/routes/team/[team]/(teamPages)/jobs/+page.ts b/src/routes/team/[team]/(teamPages)/jobs/+page.ts index 8191d06f..32bad779 100644 --- a/src/routes/team/[team]/(teamPages)/jobs/+page.ts +++ b/src/routes/team/[team]/(teamPages)/jobs/+page.ts @@ -5,16 +5,24 @@ import { type OrderDirection$options, type TeamJobsFilter } from '$houdini'; -import type { JobsVariables } from './$houdini'; +import type { AfterLoadEvent, JobsVariables } from './$houdini'; export const _JobsVariables: JobsVariables = ({ url }) => { const filter: string = url.searchParams.get('filter') || ''; const environments: string[] = url.searchParams.get('environments')?.split(',') || []; - const field = (url.searchParams.get('field') || JobOrderField.STATUS) as JobOrderField$options; - const direction = (url.searchParams.get('direction') || 'DESC') as OrderDirection$options; + const field = (url.searchParams.get('field') || JobOrderField.NAME) as JobOrderField$options; + const direction = (url.searchParams.get('direction') || 'ASC') as OrderDirection$options; return { filter: { name: filter, environments } as TeamJobsFilter, orderBy: { field: field, direction: direction } as JobOrder }; }; + +export function _houdini_afterLoad({ data, event: { url } }: AfterLoadEvent) { + return { + data, + initialEnvironments: url.searchParams.get('environments') || '', + initialFilter: url.searchParams.get('filter') || '' + }; +} diff --git a/src/routes/team/[team]/(teamPages)/jobs_old/+page.gql b/src/routes/team/[team]/(teamPages)/jobs_old/+page.gql new file mode 100644 index 00000000..d18830c7 --- /dev/null +++ b/src/routes/team/[team]/(teamPages)/jobs_old/+page.gql @@ -0,0 +1,31 @@ +query Jobs_old($team: Slug!, $orderBy: JobOrder, $filter: TeamJobsFilter) +@cache(policy: NetworkOnly) { + team(slug: $team) @loading { + environments @loading { + name + } + jobs(first: 20, orderBy: $orderBy, filter: $filter) @paginate(mode: SinglePage) @loading { + pageInfo @loading { + totalCount + endCursor + startCursor + hasNextPage + hasPreviousPage + pageEnd + pageStart + } + nodes @loading(count: 20) { + name + environment { + name + } + status { + state + } + deploymentInfo { + timestamp + } + } + } + } +} diff --git a/src/routes/team/[team]/(teamPages)/jobs_old/+page.svelte b/src/routes/team/[team]/(teamPages)/jobs_old/+page.svelte new file mode 100644 index 00000000..523884a0 --- /dev/null +++ b/src/routes/team/[team]/(teamPages)/jobs_old/+page.svelte @@ -0,0 +1,247 @@ + + + + +{#if $Jobs_old.data} + {@const jobs = $Jobs_old.data.team.jobs} + +
+
+ +

Jobs

+
+
+ +
+
+ + + + + + + + + + + + {#if jobs !== undefined} + {#each jobs.nodes as job} + {#if job === PendingValue} + + + + + + + {:else} + + + + + + + + {/if} + {:else} + + + + {/each} + {/if} + +
NameEnvironmentDeployed
+ + + + + +
+
+ + + +
+
+ {job.name} + {job.environment.name} + {#if job.deploymentInfo.timestamp} +
No jobs found
+ {#if jobs.pageInfo !== PendingValue} + {#if jobs.pageInfo.hasPreviousPage || jobs.pageInfo.hasNextPage} + + {/if} + {/if} +
+{/if} + + diff --git a/src/routes/team/[team]/(teamPages)/jobs_old/+page.ts b/src/routes/team/[team]/(teamPages)/jobs_old/+page.ts new file mode 100644 index 00000000..226c6420 --- /dev/null +++ b/src/routes/team/[team]/(teamPages)/jobs_old/+page.ts @@ -0,0 +1,20 @@ +import { + JobOrderField, + type JobOrder, + type JobOrderField$options, + type OrderDirection$options, + type TeamJobsFilter +} from '$houdini'; +import type { Jobs_oldVariables } from './$houdini'; + +export const _JobsVariables: Jobs_oldVariables = ({ url }) => { + const filter: string = url.searchParams.get('filter') || ''; + const environments: string[] = url.searchParams.get('environments')?.split(',') || []; + const field = (url.searchParams.get('field') || JobOrderField.STATUS) as JobOrderField$options; + const direction = (url.searchParams.get('direction') || 'DESC') as OrderDirection$options; + + return { + filter: { name: filter, environments } as TeamJobsFilter, + orderBy: { field: field, direction: direction } as JobOrder + }; +};