From 3472e9907ecf7e1fc47caa8485ddea5de4dafba2 Mon Sep 17 00:00:00 2001 From: Arman Date: Tue, 11 Mar 2025 18:42:42 +0100 Subject: [PATCH] refactor: quick filters, centrilize logic, remove duplicated code --- src/lib/components/filters/index.ts | 1 + .../components/filters/quickFilters.svelte | 67 +++++ src/lib/components/filters/quickFilters.ts | 83 ++++++ src/lib/components/filters/setFilters.ts | 133 +++++++++ src/lib/components/filters/store.ts | 1 - src/lib/helpers/types.ts | 3 + .../function-[function]/+page.svelte | 12 +- .../executions/+page.svelte | 4 +- .../executions/quickFilters.svelte | 233 --------------- .../function-[function]/quickFilters.svelte | 231 --------------- .../site-[site]/deployments/+page.svelte | 158 ++++------ .../deployments/quickFilters.svelte | 276 ------------------ .../sites/site-[site]/deployments/store.ts | 3 +- .../settings/updateRuntimeSettings.svelte | 3 +- 14 files changed, 343 insertions(+), 865 deletions(-) create mode 100644 src/lib/components/filters/quickFilters.svelte create mode 100644 src/lib/components/filters/quickFilters.ts create mode 100644 src/lib/components/filters/setFilters.ts delete mode 100644 src/routes/(console)/project-[project]/functions/function-[function]/executions/quickFilters.svelte delete mode 100644 src/routes/(console)/project-[project]/functions/function-[function]/quickFilters.svelte delete mode 100644 src/routes/(console)/project-[project]/sites/site-[site]/deployments/quickFilters.svelte diff --git a/src/lib/components/filters/index.ts b/src/lib/components/filters/index.ts index b2747057b..2aeeebec2 100644 --- a/src/lib/components/filters/index.ts +++ b/src/lib/components/filters/index.ts @@ -3,4 +3,5 @@ export { default as TagList } from './tagList.svelte'; export { default as FilterMenu } from './menu.svelte'; export { default as FilterSubMenu } from './subMenu.svelte'; export { default as CustomFilters } from './customFilters.svelte'; +export { default as QuickFilters } from './quickFilters.svelte'; export { hasPageQueries, queryParamToMap, queries } from '$lib/components/filters/store'; diff --git a/src/lib/components/filters/quickFilters.svelte b/src/lib/components/filters/quickFilters.svelte new file mode 100644 index 000000000..7cdff80a7 --- /dev/null +++ b/src/lib/components/filters/quickFilters.svelte @@ -0,0 +1,67 @@ + + + + {#each filterCols as filter} + {#if filter.options} + { + addFilterAndApply( + filter.id, + filter.title, + filter.operator, + e.detail.value, + filter?.array + ? (filter.options + .filter((opt) => opt.checked) + .map((opt) => opt.value) ?? []) + : [], + $columns, + analyticsSource + ); + }} + on:clear={() => { + filter.tag = null; + addFilterAndApply( + filter.id, + filter.title, + filter.operator, + null, + [], + $columns, + analyticsSource + ); + }} /> + {/if} + {/each} + + + + + diff --git a/src/lib/components/filters/quickFilters.ts b/src/lib/components/filters/quickFilters.ts new file mode 100644 index 000000000..0bbe84fe3 --- /dev/null +++ b/src/lib/components/filters/quickFilters.ts @@ -0,0 +1,83 @@ +import type { Column } from '$lib/helpers/types'; +import { get } from 'svelte/store'; +import { addFilter, queries, tags, ValidOperators } from './store'; +import { Submit, trackEvent } from '$lib/actions/analytics'; + +export type FilterData = { + title: string; + id: string; + array: boolean; + show: boolean; + tag: string; + operator: ValidOperators; + options: { value: string; label: string; checked: boolean }[]; +}; + +export function buildFilterCol(col: Column, customOperator = null): FilterData { + return { + title: col.title, + id: col.id, + show: false, + array: col?.array, + tag: null, + operator: customOperator ?? ValidOperators.Equal, + options: col?.elements?.map((element) => { + return { + value: (element?.value ?? element) as string, + label: (element?.label ?? element) as string, + checked: false + }; + }) + }; +} + +export function addFilterAndApply( + colId: string, + colTitle: string, + operator: ValidOperators, + value: string, + arrayValues: string[] = [], + columns: Column[], + analyticsSource: string +) { + const tagList = get(tags).filter((tag) => tag.tag.includes(colTitle)); + tagList.forEach((tag) => queries.removeFilter(tag)); + if (value || arrayValues?.length) { + if (colId === 'sourceSize' || colId === 'buildSize') { + addSizeFilter(value, colId, columns); + } else if (colId === 'statusCode') { + addStatusCodeFilter(value, colId, columns); + } else if (colId === '$createdAt' || colId === '$updatedAt' || colId === 'buildDuration') { + addDateFilter(value, colId, columns); + } else { + addFilter(columns, colId, operator, value, arrayValues); + } + } + queries.apply(); + trackEvent(Submit.ApplyQuickFilter, { + source: analyticsSource, + column: colId, + value: value || arrayValues.join(', ') + }); +} + +export function resetOptions(filter: FilterData) { + filter.options.forEach((option) => { + option.checked = false; + }); +} + +export function addStatusCodeFilter(value: string, colId: string, columns: Column[]) { + addFilter(columns, colId, ValidOperators.LessThanOrEqual, parseInt(value)); + addFilter(columns, colId, ValidOperators.GreaterThanOrEqual, parseInt(value) - 99); +} +export function addDateFilter(value: string, colId: string, columns: Column[]) { + const now = new Date(); + const isoValue = new Date(now.getTime() - parseInt(value)); + addFilter(columns, colId, ValidOperators.GreaterThanOrEqual, isoValue.toISOString()); + addFilter(columns, colId, ValidOperators.LessThanOrEqual, now.toISOString()); +} + +export function addSizeFilter(value: string, colId: string, columns: Column[]) { + addFilter(columns, colId, ValidOperators.GreaterThanOrEqual, value); +} diff --git a/src/lib/components/filters/setFilters.ts b/src/lib/components/filters/setFilters.ts new file mode 100644 index 000000000..07611409b --- /dev/null +++ b/src/lib/components/filters/setFilters.ts @@ -0,0 +1,133 @@ +import type { Column } from '$lib/helpers/types'; +import { resetOptions, type FilterData } from './quickFilters'; +import type { TagValue } from './store'; + +export function setFiltersOnNavigate( + tags: TagValue[], + filterCols: FilterData[], + $columns: Column[] +) { + if (!tags?.length) { + filterCols.forEach((filter) => { + resetOptions(filter); + }); + } else { + filterCols.forEach((filter) => { + if (filter.id === 'buildDuration') { + setTimeFilter(tags, filter, $columns); + } else if (filter.id.includes('size')) { + setSizeFilter(tags, filter, $columns); + } else if (filter.id === 'statusCode') { + setStatusCodeFilter(tags, filter, $columns); + } else if (filter.id === '$createdAt' || filter.id === '$updatedAt') { + setDateFilter(tags, filter, $columns); + } else { + setFilterData(filter, tags); + } + }); + + // Reasinging the filters to trigger reactivity + filterCols = filterCols; + } +} + +export function setFilterData(filter: FilterData, list: TagValue[]) { + const tagData = list.find((tag) => tag.tag.includes(`**${filter.title}**`)); + if (tagData) { + filter.tag = tagData.tag; + if (Array.isArray(tagData.value) && tagData.value?.length) { + const values = tagData.value as string[]; + filter.options.forEach((option) => { + option.checked = values.includes(option.value); + }); + } + } else { + filter.tag = null; + resetOptions(filter); + } +} + +export function setTimeFilter(localTags: TagValue[], filter: FilterData, columns: Column[]) { + const col = columns.find((c) => c.id === filter.id); + const timeTag = localTags.find((tag) => tag.tag.includes(`**${filter.title}**`)); + if (timeTag) { + const now = new Date(); + + const diff = now.getTime() - new Date(timeTag.value as string).getTime(); + const ranges = col.elements as { value: string; label: string }[]; + const dateRange = ranges.reduce((prev, curr) => { + if (parseInt(curr.value) < diff && curr.value > prev.value) { + return curr; + } + return prev; + }); + if (dateRange) { + filter.tag = `**${filter.title}** is **${dateRange.label}**`; + filter = filter; + } + } else { + filter.tag = null; + } +} + +export function setSizeFilter(localTags: TagValue[], filter: FilterData, columns: Column[]) { + const sizeTag = localTags.find((tag) => tag.tag.includes(`**${filter.title}**`)); + const col = columns.find((c) => c.id === filter.id); + if (sizeTag) { + const size = sizeTag.value as string; + const ranges = col.elements as { value: string; label: string }[]; + // find smallest range that is bigger than size + const sizeRange = ranges.reduce((prev, curr) => { + if (parseInt(size) >= parseInt(curr.value)) { + return curr; + } + return prev; + }); + if (sizeRange) { + filter.tag = `**${filter.title}** is **${sizeRange.label}**`; + filter = filter; + } + } else { + filter.tag = null; + } +} + +export function setStatusCodeFilter(localTags: TagValue[], filter: FilterData, columns: Column[]) { + const statusCodeTag = localTags.find((tag) => tag.tag.includes(`**${filter.title}**`)); + const col = columns.find((c) => c.id === filter.id); + + if (statusCodeTag) { + const ranges = col.elements as { value: number; label: string }[]; + + const codeRange = ranges.find((c) => c?.value && c.value === statusCodeTag.value); + if (codeRange) { + filter.tag = `**${filter.title}** is **${codeRange.label}**`; + filter = filter; + } + } else { + filter.tag = null; + } +} + +export function setDateFilter(localTags: TagValue[], filter: FilterData, columns: Column[]) { + const dateTeag = localTags.find((tag) => tag.tag.includes(`**${filter.title}**`)); + const col = columns.find((c) => c.id === filter.id); + if (dateTeag) { + const now = new Date(); + + const diff = now.getTime() - new Date(dateTeag.value as string).getTime(); + const ranges = col.elements as { value: string; label: string }[]; + const dateRange = ranges.reduce((prev, curr) => { + if (parseInt(curr.value) < diff && curr.value > prev.value) { + return curr; + } + return prev; + }); + if (dateRange) { + filter.tag = `**${filter.title}** is **${dateRange.label}**`; + filter = filter; + } + } else { + filter.tag = null; + } +} diff --git a/src/lib/components/filters/store.ts b/src/lib/components/filters/store.ts index 10cd6f183..bf8400910 100644 --- a/src/lib/components/filters/store.ts +++ b/src/lib/components/filters/store.ts @@ -1,5 +1,4 @@ import { goto } from '$app/navigation'; - import { derived, get, writable } from 'svelte/store'; import { page } from '$app/stores'; import deepEqual from 'deep-equal'; diff --git a/src/lib/helpers/types.ts b/src/lib/helpers/types.ts index f724ec101..ad99d94fe 100644 --- a/src/lib/helpers/types.ts +++ b/src/lib/helpers/types.ts @@ -39,6 +39,9 @@ export type Column = { array?: boolean; format?: string; elements?: string[] | { value: string | number; label: string }[]; + /** + * Set to true to hide this column by default + */ hide?: boolean; }; diff --git a/src/routes/(console)/project-[project]/functions/function-[function]/+page.svelte b/src/routes/(console)/project-[project]/functions/function-[function]/+page.svelte index b7f2ac859..025d61ed6 100644 --- a/src/routes/(console)/project-[project]/functions/function-[function]/+page.svelte +++ b/src/routes/(console)/project-[project]/functions/function-[function]/+page.svelte @@ -9,12 +9,9 @@ import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system'; import { readOnly } from '$lib/stores/billing'; import Table from './table.svelte'; - import { Filters } from '$lib/components/filters'; - import { queries, tags } from '$lib/components/filters/store'; import { View } from '$lib/helpers/load'; import DeploymentCard from './(components)/deploymentCard.svelte'; import RedeployModal from './(modals)/redeployModal.svelte'; - import QuickFilters from './quickFilters.svelte'; import { canWriteFunctions } from '$lib/stores/roles'; import { ActionMenu, @@ -28,13 +25,13 @@ import { Click, trackEvent } from '$lib/actions/analytics'; import { IconDotsHorizontal, - IconFilterLine, IconPlus, IconRefresh, IconTerminal } from '@appwrite.io/pink-icons-svelte'; import { app } from '$lib/stores/app'; import CreateActionMenu from './(components)/createActionMenu.svelte'; + import { QuickFilters } from '$lib/components/filters'; export let data; @@ -42,11 +39,6 @@ let showAlert = true; let selectedDeployment: Models.Deployment = null; - - function clearAll() { - queries.clearAll(); - queries.apply(); - } @@ -182,7 +174,7 @@ {#if data.deploymentList.total} - + {/if} diff --git a/src/routes/(console)/project-[project]/functions/function-[function]/executions/+page.svelte b/src/routes/(console)/project-[project]/functions/function-[function]/executions/+page.svelte index 09c7544cd..29c5532cb 100644 --- a/src/routes/(console)/project-[project]/functions/function-[function]/executions/+page.svelte +++ b/src/routes/(console)/project-[project]/functions/function-[function]/executions/+page.svelte @@ -9,11 +9,11 @@ import { project } from '$routes/(console)/project-[project]/store'; import { base } from '$app/paths'; import { View } from '$lib/helpers/load'; - import QuickFilters from './quickFilters.svelte'; import { Icon, Layout } from '@appwrite.io/pink-svelte'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; import Table from './table.svelte'; import { columns } from './store'; + import { QuickFilters } from '$lib/components/filters'; export let data; @@ -30,7 +30,7 @@ - + {#if data?.executions?.total} diff --git a/src/routes/(console)/project-[project]/functions/function-[function]/executions/quickFilters.svelte b/src/routes/(console)/project-[project]/functions/function-[function]/executions/quickFilters.svelte deleted file mode 100644 index f9bb43bf0..000000000 --- a/src/routes/(console)/project-[project]/functions/function-[function]/executions/quickFilters.svelte +++ /dev/null @@ -1,233 +0,0 @@ - - - - {#each [statusFilter, triggerFilter, methodFilter] as filter} - { - console.log('test'); - addFilterAndApply( - filter.id, - filter.title, - filter.operator, - e.detail.value, - filter?.array - ? (filter.options.filter((opt) => opt.checked).map((opt) => opt.value) ?? - []) - : [] - ); - }} - on:clear={() => { - filter.tag = null; - addFilterAndApply(filter.id, filter.title, filter.operator, null, []); - }} /> - {/each} - {#each [statusCodeFilter, createdAtFilter] as filter} - { - console.log('test'); - addFilterAndApply( - filter.id, - filter.title, - filter.operator, - e.detail.value, - filter?.array - ? (filter.options.filter((opt) => opt.checked).map((opt) => opt.value) ?? - []) - : [] - ); - }} - on:clear={() => { - filter.tag = null; - addFilterAndApply(filter.id, filter.title, filter.operator, null, []); - }} /> - {/each} - - - - diff --git a/src/routes/(console)/project-[project]/functions/function-[function]/quickFilters.svelte b/src/routes/(console)/project-[project]/functions/function-[function]/quickFilters.svelte deleted file mode 100644 index 9286f84d0..000000000 --- a/src/routes/(console)/project-[project]/functions/function-[function]/quickFilters.svelte +++ /dev/null @@ -1,231 +0,0 @@ - - - - {#each [typeFilter] as filter} - { - console.log('test'); - addFilterAndApply( - filter.id, - filter.title, - filter.operator, - e.detail.value, - filter?.array - ? (filter.options.filter((opt) => opt.checked).map((opt) => opt.value) ?? - []) - : [] - ); - }} - on:clear={() => { - filter.tag = null; - addFilterAndApply(filter.id, filter.title, filter.operator, null, []); - }} /> - {/each} - {#each [sizeFilter] as filter} - { - console.log('test'); - addFilterAndApply( - filter.id, - filter.title, - filter.operator, - e.detail.value, - filter?.array - ? (filter.options.filter((opt) => opt.checked).map((opt) => opt.value) ?? - []) - : [] - ); - }} - on:clear={() => { - filter.tag = null; - addFilterAndApply(filter.id, filter.title, filter.operator, null, []); - }} /> - {/each} - - - - diff --git a/src/routes/(console)/project-[project]/sites/site-[site]/deployments/+page.svelte b/src/routes/(console)/project-[project]/sites/site-[site]/deployments/+page.svelte index 35a10ebb7..22497ec3a 100644 --- a/src/routes/(console)/project-[project]/sites/site-[site]/deployments/+page.svelte +++ b/src/routes/(console)/project-[project]/sites/site-[site]/deployments/+page.svelte @@ -3,24 +3,22 @@ import { Button } from '$lib/elements/forms'; import { Container } from '$lib/layout'; import { type Models } from '@appwrite.io/console'; - import { Filters } from '$lib/components/filters'; - import { queries, tags } from '$lib/components/filters/store'; import { View } from '$lib/helpers/load'; - import { ActionMenu, Icon, Layout, Popover, Tag } from '@appwrite.io/pink-svelte'; + import { ActionMenu, Icon, Layout, Popover } from '@appwrite.io/pink-svelte'; import Table from './table.svelte'; - import QuickFilters from './quickFilters.svelte'; import RedeployModal from '../../redeployModal.svelte'; import CreateGitDeploymentModal from './createGitDeploymentModal.svelte'; import ConnectRepoModal from '../../(components)/connectRepoModal.svelte'; import { columns } from './store'; import CreateManualDeploymentModal from './createManualDeploymentModal.svelte'; import DeploymentMetrics from './deploymentMetrics.svelte'; - import { IconFilterLine, IconPlus } from '@appwrite.io/pink-icons-svelte'; + import { IconPlus } from '@appwrite.io/pink-icons-svelte'; import { onMount } from 'svelte'; import { sdk } from '$lib/stores/sdk'; import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; import CreateCliModal from './createCliModal.svelte'; + import { QuickFilters } from '$lib/components/filters'; export let data; @@ -33,11 +31,6 @@ let showConnectManual = false; let showMobileFilters = false; - function clearAll() { - queries.clearAll(); - queries.apply(); - } - onMount(() => { data?.query ? (showMobileFilters = true) : (showMobileFilters = false); return sdk.forConsole.client.subscribe('console', (response) => { @@ -53,106 +46,53 @@ -
- - {#if data.deploymentList.total} - - - - - {#if $tags?.length} - -
-
- - {/if} -
-
- {/if} -
-
-
- -
- + + {#if data.deploymentList.total || data?.query} + + {/if} + - - - - More filters - - - - -
-
-
- - {#if data.deploymentList.total} - - {/if} - - - - - { - toggle(e); - if (!hasInstallation) { - showConnectRepo = true; - } else { - showCreateDeployment = true; - } - }}> - Git - - { - toggle(e); - showConnectCLI = true; - }}> - CLI - - { - toggle(e); - showConnectManual = true; - }}> - Manual - - - - - -
+ + {#if data.deploymentList.total} + + {/if} + + + + + { + toggle(e); + if (!hasInstallation) { + showConnectRepo = true; + } else { + showCreateDeployment = true; + } + }}> + Git + + { + toggle(e); + showConnectCLI = true; + }}> + CLI + + { + toggle(e); + showConnectManual = true; + }}> + Manual + + + + +
{#if data.deploymentList.total} diff --git a/src/routes/(console)/project-[project]/sites/site-[site]/deployments/quickFilters.svelte b/src/routes/(console)/project-[project]/sites/site-[site]/deployments/quickFilters.svelte deleted file mode 100644 index a6b357e3c..000000000 --- a/src/routes/(console)/project-[project]/sites/site-[site]/deployments/quickFilters.svelte +++ /dev/null @@ -1,276 +0,0 @@ - - -{#each [typeFilter] as filter} - - - - {#key filter.tag} - - {filter?.tag ?? filter.title} - - {/key} - - - - - {#each filter.options as option (option.value + option.checked)} - { - option.checked = !option.checked; - addFilterAndApply( - filter.id, - filter.title, - filter.operator, - filter?.array ? null : option.checked ? option.value : null, - filter?.array - ? (filter.options - .filter((opt) => opt.checked) - .map((opt) => opt.value) ?? []) - : [] - ); - }}> - {option.label} - - {/each} - {#if filter.options.some((option) => option.checked)} - { - filter.tag = null; - addFilterAndApply(filter.id, filter.title, filter.operator, null, []); - toggle(e); - }}> - Clear selection - - {/if} - - - -{/each} -{#each [sizeFilter] as filter} - - - - - {#key filter.tag} - - {filter?.tag ?? filter.title} - - {/key} - - - - - - {#each filter.options as option (option.value + option.checked)} - { - addFilterAndApply( - filter.id, - filter.title, - filter.operator, - filter?.array ? null : option.value, - [] - ); - toggle(e); - }}> - {option.label} - - {/each} - {#if filter?.tag} - { - filter.show = false; - filter.tag = null; - addFilterAndApply(filter.id, filter.title, filter.operator, null, []); - }}> - Clear selection - - {/if} - - - -{/each} diff --git a/src/routes/(console)/project-[project]/sites/site-[site]/deployments/store.ts b/src/routes/(console)/project-[project]/sites/site-[site]/deployments/store.ts index 1fba88f94..166b666dd 100644 --- a/src/routes/(console)/project-[project]/sites/site-[site]/deployments/store.ts +++ b/src/routes/(console)/project-[project]/sites/site-[site]/deployments/store.ts @@ -13,8 +13,7 @@ export const columns = writable([ width: 110, array: true, format: 'enum', - elements: ['ready', 'processing', 'building', 'waiting', 'cancelled', 'failed'], - filter: false + elements: ['ready', 'processing', 'building', 'waiting', 'cancelled', 'failed'] }, { diff --git a/src/routes/(console)/project-[project]/sites/site-[site]/settings/updateRuntimeSettings.svelte b/src/routes/(console)/project-[project]/sites/site-[site]/settings/updateRuntimeSettings.svelte index d9d18f50c..88fde4c1c 100644 --- a/src/routes/(console)/project-[project]/sites/site-[site]/settings/updateRuntimeSettings.svelte +++ b/src/routes/(console)/project-[project]/sites/site-[site]/settings/updateRuntimeSettings.svelte @@ -4,6 +4,7 @@ import { CardGrid } from '$lib/components'; import { Dependencies } from '$lib/constants'; import { Button, Form, InputSelect } from '$lib/elements/forms'; + import { capitalize } from '$lib/helpers/string'; import { iconPath } from '$lib/stores/app'; import { addNotification } from '$lib/stores/notifications'; import { getIconFromRuntime } from '$lib/stores/runtimes'; @@ -16,7 +17,7 @@ let buildRuntime = site?.buildRuntime; let buildRuntimeOptions = framework.runtimes.map((runtime) => ({ - label: runtime, + label: capitalize(runtime), value: runtime, leadingHtml: `` }));