Merge pull request #1159 from appwrite/feat-deployments-filters

feat: deployments filters
This commit is contained in:
Arman
2024-07-02 17:02:43 +02:00
committed by GitHub
13 changed files with 323 additions and 180 deletions
+5 -4
View File
@@ -6,7 +6,7 @@
"": {
"name": "@appwrite/console",
"dependencies": {
"@appwrite.io/console": "^0.6.2",
"@appwrite.io/console": "npm:khushboo-console@0.0.3",
"@appwrite.io/pink": "0.23.0",
"@appwrite.io/pink-icons": "0.23.0",
"@popperjs/core": "^2.11.8",
@@ -149,9 +149,10 @@
"integrity": "sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg=="
},
"node_modules/@appwrite.io/console": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-0.6.2.tgz",
"integrity": "sha512-3qYknuFwhvTN2GnPB8G1w5DgxfVorGtfj4uuEaXbTmMUmjA8fHaW5VkJriuUxCi6/TpxDxqV/hhEkdoXL+5H4w=="
"name": "khushboo-console",
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/khushboo-console/-/khushboo-console-0.0.3.tgz",
"integrity": "sha512-9NzonFdptH/UF65ZNg/zmX1fWvONLUvXNIDKWpvjcwNlhlHoAlT2+uQIBxDuAzECTuXlwpszPeHOgUnhgp9fFQ=="
},
"node_modules/@appwrite.io/pink": {
"version": "0.23.0",
+8 -12
View File
@@ -64,13 +64,6 @@
apply: { applied: number };
}>();
dispatch('apply', { applied: $tags.length });
// $: if (column?.type === 'datetime' && !value) {
// const today = new Date();
// console.log(today.toISOString());
// value = today.toISOString();
// console.log('value', value);
// }
</script>
<div>
@@ -101,10 +94,10 @@
name="value"
bind:tags={arrayValues}
placeholder="Select value"
options={column?.elements?.map((value) => ({
label: value,
value,
checked: arrayValues.includes(value)
options={column?.elements?.map((e) => ({
label: e?.label ?? e,
value: e?.value ?? e,
checked: arrayValues.includes(e?.value ?? e)
}))}>
</InputSelectCheckbox>
{:else}
@@ -123,7 +116,10 @@
id="value"
bind:value
placeholder="Select value"
options={column?.elements?.map((value) => ({ label: value, value }))}
options={column?.elements?.map((e) => ({
label: e?.label ?? e,
value: e?.value ?? e
}))}
label="Value"
showLabel={false} />
{:else if column.type === 'integer' || column.type === 'double'}
+7 -1
View File
@@ -191,7 +191,13 @@ const operatorsDefault = new Map<
ValidOperators.NotEqual,
{
query: Query.notEqual,
types: [ValidTypes.String, ValidTypes.Integer, ValidTypes.Double, ValidTypes.Boolean]
types: [
ValidTypes.String,
ValidTypes.Integer,
ValidTypes.Double,
ValidTypes.Boolean,
ValidTypes.Enum
]
}
],
[
+1 -1
View File
@@ -38,7 +38,7 @@ export type Column = {
filter?: boolean;
array?: boolean;
format?: string;
elements?: string[];
elements?: string[] | { value: string; label: string }[];
};
export function isValueOfStringEnum<T extends Record<string, string>>(
@@ -1,70 +1,101 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { timer } from '$lib/actions/timer';
import {
CardGrid,
DropList,
DropListItem,
DropListLink,
Empty,
Heading,
Id,
PaginationWithLimit,
Alert
Alert,
ViewSelector
} from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import {
TableBody,
TableCell,
TableCellHead,
TableCellText,
TableHeader,
TableRow,
TableScroll
} from '$lib/elements/table';
import { deploymentList, func, proxyRuleList } from './store';
import { Container, ContainerHeader } from '$lib/layout';
import { app } from '$lib/stores/app';
import { calculateSize, humanFileSize } from '$lib/helpers/sizeConvertion';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import type { Models } from '@appwrite.io/console';
import Delete from './delete.svelte';
import Create from './create.svelte';
import Activate from './activate.svelte';
import Cancel from './cancel.svelte';
import { calculateTime } from '$lib/helpers/timeConversion';
import { Pill } from '$lib/elements';
import RedeployModal from './redeployModal.svelte';
import DeploymentSource from './deploymentSource.svelte';
import DeploymentCreatedBy from './deploymentCreatedBy.svelte';
import DeploymentDomains from './deploymentDomains.svelte';
import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system';
import { readOnly } from '$lib/stores/billing';
import { project } from '../../store';
import { writable } from 'svelte/store';
import type { Column } from '$lib/helpers/types';
import Table from './table.svelte';
import { Filters, TagList } from '$lib/components/filters';
import { queries, tags } from '$lib/components/filters/store';
import { View } from '$lib/helpers/load';
export let data;
let showDropdown = [];
let showDelete = false;
let showCancel = false;
let showActivate = false;
let showRedeploy = false;
let showAlert = true;
let selectedDeployment: Models.Deployment = null;
function handleActivate() {
invalidate(Dependencies.DEPLOYMENTS);
}
const columns = writable<Column[]>([
{ id: '$id', title: 'Deployment ID', type: 'string', show: true, width: 150 },
{
id: 'status',
title: 'Status',
type: 'enum',
show: true,
width: 110,
array: true,
format: 'enum',
elements: ['ready', 'processing', 'building', 'waiting', 'cancelled', 'failed']
},
{
id: 'type',
title: 'Source',
type: 'string',
show: true,
width: 90,
array: true,
format: 'enum',
elements: [
{ value: 'manual', label: 'Manual' },
{ value: 'cli', label: 'CLI' },
{ value: 'vcs', label: 'Git' }
]
},
{
id: '$updatedAt',
title: 'Updated',
type: 'datetime',
show: true,
width: 150,
format: 'datetime'
},
{
id: 'buildTime',
title: 'Build time',
type: 'integer',
show: true,
width: 80
},
{
id: 'size',
title: 'Size',
type: 'integer',
show: true,
width: 80
}
]);
</script>
<Container>
<ContainerHeader title="Deployments">
<Create main />
</ContainerHeader>
{#if $deploymentList?.total}
{#if data?.activeDeployment}
{@const activeDeployment = data.activeDeployment}
<div class="common-section">
<Heading tag="h3" size="7">Active</Heading>
@@ -218,125 +249,61 @@
{/if}
<div class="common-section">
<Heading tag="h3" size="7">All</Heading>
<div class="u-flex u-main-space-between is-not-mobile u-margin-block-start-16">
<div class="u-flex u-gap-8 u-cross-center u-flex-wrap">
<TagList />
<Filters query={data.query} {columns} let:disabled let:toggle singleCondition>
<div class="u-flex u-gap-4">
<Button text on:click={toggle} {disabled} ariaLabel="open filter">
<span class="icon-filter-line" />
{#if !$tags?.length}
<span class="text">Filters</span>
{/if}
</Button>
{#if $tags?.length}
<div
style="flex-basis:1px; background-color:hsl(var(--color-border)); width: 1px">
</div>
<Button
text
on:click={() => {
queries.clearAll();
queries.apply();
}}>
Clear all
</Button>
{/if}
</div>
</Filters>
</div>
<div class="u-flex u-gap-16">
<ViewSelector
view={View.Table}
{columns}
hideView
allowNoColumns
showColsTextMobile />
</div>
</div>
<div class="u-flex u-main-space-between u-margin-block-start-16 is-only-mobile">
<Filters query={data.query} {columns}>
<svelte:fragment slot="mobile" let:disabled let:toggle>
<Button text on:click={toggle} {disabled} ariaLabel="open filter" noMargin>
<span class="icon-filter-line" />
<span class="text">Filters</span>
{#if $tags?.length}
<span class="inline-tag">{$tags?.length}</span>
{/if}
</Button>
</svelte:fragment>
</Filters>
<ViewSelector view={View.Table} {columns} hideView allowNoColumns />
</div>
</div>
{#if $deploymentList.total}
<div class="u-margin-block-start-24">
<TableScroll noMargin>
<TableHeader>
<TableCellHead width={150}>Deployment ID</TableCellHead>
<TableCellHead width={100}>Status</TableCellHead>
<TableCellHead width={80}>Source</TableCellHead>
<TableCellHead width={180}>Updated</TableCellHead>
<TableCellHead width={80}>Build Time</TableCellHead>
<TableCellHead width={80}>Size</TableCellHead>
<TableCellHead width={40} />
</TableHeader>
<TableBody>
{#each $deploymentList.deployments as deployment, index (deployment.$id)}
{@const status = deployment.status}
<TableRow>
<TableCell width={150} title="Deployment ID">
<Id value={deployment.$id}>{deployment.$id}</Id>
</TableCell>
<TableCell title="Status">
{#if activeDeployment?.$id === deployment?.$id}
<Pill success>
<span class="icon-lightning-bolt" aria-hidden="true" />
<span class="text u-trim">active</span>
</Pill>
{:else}
<Pill
danger={status === 'failed'}
warning={status === 'building'}
info={status === 'ready'}>
{status}
</Pill>
{/if}
</TableCell>
<TableCellText title="Source">
<DeploymentSource {deployment} /></TableCellText>
<TableCellText width={180} title="Updated">
<DeploymentCreatedBy {deployment} />
</TableCellText>
<TableCellText title="Build Time">
{#if ['processing', 'building'].includes(deployment.status)}
<span use:timer={{ start: deployment.$createdAt }} />
{:else}
{calculateTime(deployment.buildTime)}
{/if}
</TableCellText>
<TableCellText title="Size">
{calculateSize(deployment.size)}
</TableCellText>
<TableCell width={40} showOverflow>
<DropList
bind:show={showDropdown[index]}
placement="bottom-start"
noArrow>
<button
class="button is-only-icon is-text"
aria-label="More options"
on:click|preventDefault={() => {
showDropdown[index] = !showDropdown[index];
}}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</button>
<svelte:fragment slot="list">
<DropListItem
icon="refresh"
on:click={() => {
selectedDeployment = deployment;
showRedeploy = true;
showDropdown = [];
}}>
Redeploy
</DropListItem>
{#if deployment.status === 'ready' && deployment.$id !== $func.deployment}
<DropListItem
icon="lightning-bolt"
on:click={() => {
selectedDeployment = deployment;
showActivate = true;
showDropdown = [];
}}>
Activate
</DropListItem>
{/if}
<DropListLink
icon="terminal"
href={`/console/project-${$page.params.project}/functions/function-${$page.params.function}/deployment-${deployment.$id}`}>
Logs
</DropListLink>
{#if deployment.status === 'processing'}
<DropListItem
icon="x-circle"
on:click={() => {
selectedDeployment = deployment;
showDropdown = [];
showCancel = true;
}}>
Cancel
</DropListItem>
{/if}
<DropListItem
icon="trash"
on:click={() => {
selectedDeployment = deployment;
showDropdown = [];
showDelete = true;
}}>
Delete
</DropListItem>
</svelte:fragment>
</DropList>
</TableCell>
</TableRow>
{/each}
</TableBody>
</TableScroll>
<Table columns={$columns} {data} />
</div>
{:else}
<Empty single target="deployment" on:click>
@@ -386,13 +353,6 @@
total={$deploymentList?.total} />
</Container>
{#if selectedDeployment}
<Delete {selectedDeployment} bind:showDelete />
<Cancel {selectedDeployment} bind:showCancel />
<Activate {selectedDeployment} bind:showActivate on:activated={handleActivate} />
<RedeployModal {selectedDeployment} bind:show={showRedeploy} />
{/if}
<style lang="scss">
@import '@appwrite.io/pink/src/abstract/variables/_devices.scss';
@@ -1,8 +1,9 @@
import { Query } from '@appwrite.io/console';
import { sdk } from '$lib/stores/sdk';
import { getLimit, getPage, pageToOffset } from '$lib/helpers/load';
import { getLimit, getPage, getQuery, pageToOffset } from '$lib/helpers/load';
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
import type { PageLoad } from './$types';
import { queries, queryParamToMap } from '$lib/components/filters';
export const load: PageLoad = async ({ params, depends, url, route, parent }) => {
const data = await parent();
@@ -10,10 +11,14 @@ export const load: PageLoad = async ({ params, depends, url, route, parent }) =>
const page = getPage(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
const query = getQuery(url);
const parsedQueries = queryParamToMap(query || '[]');
queries.set(parsedQueries);
return {
offset,
limit,
query,
activeDeployment: data.function.deployment
? await sdk.forProject.functions.getDeployment(
params.function,
@@ -23,7 +28,8 @@ export const load: PageLoad = async ({ params, depends, url, route, parent }) =>
deploymentList: await sdk.forProject.functions.listDeployments(params.function, [
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('')
Query.orderDesc(''),
...parsedQueries.values()
])
};
};
@@ -9,7 +9,7 @@
import type { Models } from '@appwrite.io/console';
export let showCancel = false;
export let selectedDeployment: Models.Deployment = null;
export let selectedDeployment: Models.Deployment;
async function handleSubmit() {
try {
@@ -77,7 +77,7 @@
bind:files
required={true} />
{#if $func.version !== 'v3'}
{#if $func.version === 'v2'}
<Alert type="info">
<svelte:fragment slot="title">
Build commands now available for functions v3.0
@@ -99,7 +99,7 @@
</p>
<FormList>
{#if $execute?.version !== 'v3'}
{#if $execute?.version === 'v2'}
<Alert type="info">
<svelte:fragment slot="title">
Customizable execution data now available for functions v3.0
@@ -68,10 +68,9 @@
{
id: 'responseStatusCode',
title: 'Status code',
type: 'string',
type: 'integer',
show: true,
width: 100,
array: true,
format: 'integer'
},
{
@@ -85,7 +84,7 @@
{
id: 'duration',
title: 'Duration',
type: 'string',
type: 'integer',
show: true,
width: 80,
format: 'integer'
@@ -142,7 +142,7 @@
<WizardSecondaryContent>
<Form bind:this={formComponent} onSubmit={handleSubmit} bind:isSubmitting>
<FormList>
{#if func?.version !== 'v3'}
{#if func?.version === 'v2'}
<Alert type="info">
<svelte:fragment slot="title">
Customizable execution data now available for functions v3.0
@@ -42,7 +42,7 @@
<Container>
<Heading tag="h2" size="5">Settings</Heading>
{#if $func.version !== 'v3' && showAlert}
{#if $func.version === 'v2' && showAlert}
<Alert
type="warning"
dismissible
@@ -0,0 +1,175 @@
<script lang="ts">
import { DropList, DropListItem, DropListLink, Id } from '$lib/components';
import {
TableBody,
TableCell,
TableCellHead,
TableCellText,
TableHeader,
TableRow,
TableScroll
} from '$lib/elements/table';
import type { PageData } from './$types';
import { type Models } from '@appwrite.io/console';
import type { Column } from '$lib/helpers/types';
import { Pill } from '$lib/elements';
import { calculateTime } from '$lib/helpers/timeConversion';
import DeploymentSource from './deploymentSource.svelte';
import DeploymentCreatedBy from './deploymentCreatedBy.svelte';
import { timer } from '$lib/actions/timer';
import { calculateSize } from '$lib/helpers/sizeConvertion';
import { func } from './store';
import { page } from '$app/stores';
import Delete from './delete.svelte';
import Activate from './activate.svelte';
import RedeployModal from './redeployModal.svelte';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import Cancel from './cancel.svelte';
export let columns: Column[];
export let data: PageData;
let showDropdown = [];
let showDelete = false;
let showActivate = false;
let showRedeploy = false;
let showCancel = false;
let selectedDeployment: Models.Deployment = null;
function handleActivate() {
invalidate(Dependencies.DEPLOYMENTS);
}
</script>
<TableScroll>
<TableHeader>
{#each columns as column}
{#if column.show}
<TableCellHead width={column.width}>{column.title}</TableCellHead>
{/if}
{/each}
<TableCellHead width={40} />
</TableHeader>
<TableBody>
{#each data.deploymentList.deployments as deployment, index (deployment.$id)}
<TableRow>
{#each columns as column}
{#if column.show}
{#if column.id === '$id'}
{#key column.id}
<TableCell width={column.width} title="Deployment ID">
<Id value={deployment.$id}>{deployment.$id}</Id>
</TableCell>
{/key}
{:else if column.id === 'status'}
<TableCell width={column.width} title={column.title}>
{@const status = deployment.status}
{#if data?.activeDeployment?.$id === deployment?.$id}
<Pill success>
<span class="icon-lightning-bolt" aria-hidden="true" />
<span class="text u-trim">active</span>
</Pill>
{:else}
<Pill
danger={status === 'failed'}
warning={status === 'building'}
info={status === 'ready'}>
{status}
</Pill>
{/if}
</TableCell>
{:else if column.id === 'type'}
<TableCell width={column.width} title={column.title}>
<DeploymentSource {deployment} />
</TableCell>
{:else if column.id === '$updatedAt'}
<TableCellText width={column.width} title={column.title}>
<DeploymentCreatedBy {deployment} />
</TableCellText>
{:else if column.id === 'buildTime'}
<TableCellText width={column.width} title={column.title}>
{#if ['processing', 'building'].includes(deployment.status)}
<span use:timer={{ start: deployment.$createdAt }} />
{:else}
{calculateTime(deployment.buildTime)}
{/if}
</TableCellText>
{:else if column.id === 'size'}
<TableCellText width={column.width} title={column.title}>
{calculateSize(deployment.size)}
</TableCellText>
{/if}
{/if}
{/each}
<TableCell width={40} showOverflow>
<DropList bind:show={showDropdown[index]} placement="bottom-start" noArrow>
<button
class="button is-only-icon is-text"
aria-label="More options"
on:click|preventDefault={() => {
showDropdown[index] = !showDropdown[index];
}}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</button>
<svelte:fragment slot="list">
<DropListItem
icon="refresh"
on:click={() => {
selectedDeployment = deployment;
showRedeploy = true;
showDropdown = [];
}}>
Redeploy
</DropListItem>
{#if deployment.status === 'ready' && deployment.$id !== $func.deployment}
<DropListItem
icon="lightning-bolt"
on:click={() => {
selectedDeployment = deployment;
showActivate = true;
showDropdown = [];
}}>
Activate
</DropListItem>
{/if}
<DropListLink
icon="terminal"
href={`/console/project-${$page.params.project}/functions/function-${$page.params.function}/deployment-${deployment.$id}`}>
Logs
</DropListLink>
{#if deployment.status === 'processing'}
<DropListItem
icon="x-circle"
on:click={() => {
selectedDeployment = deployment;
showDropdown = [];
showCancel = true;
}}>
Cancel
</DropListItem>
{/if}
<DropListItem
icon="trash"
on:click={() => {
selectedDeployment = deployment;
showDropdown = [];
showDelete = true;
}}>
Delete
</DropListItem>
</svelte:fragment>
</DropList>
</TableCell>
</TableRow>
{/each}
</TableBody>
</TableScroll>
{#if selectedDeployment}
<Delete {selectedDeployment} bind:showDelete />
<Activate {selectedDeployment} bind:showActivate on:activated={handleActivate} />
<Cancel {selectedDeployment} bind:showCancel />
<RedeployModal {selectedDeployment} bind:show={showRedeploy} />
{/if}