Skip to content

Commit 1a7642e

Browse files
committed
[web-console] Add pipeline search feature to the pipeline list sidebar on the pipeline page
Signed-off-by: Karakatiza666 <bulakh.96@gmail.com>
1 parent 49d9bec commit 1a7642e

2 files changed

Lines changed: 86 additions & 4 deletions

File tree

js-packages/web-console/src/lib/components/pipelines/List.svelte

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<script lang="ts">
66
import PipelineStatus from '$lib/components/pipelines/list/PipelineStatusDot.svelte'
77
import { resolve } from '$lib/functions/svelte'
8+
import { matchesSubstring } from '$lib/functions/common/string'
89
import { type PipelineThumb } from '$lib/services/pipelineManager'
910
1011
let {
@@ -18,6 +19,9 @@
1819
onclose?: () => void
1920
onaction?: () => void
2021
} = $props()
22+
23+
let nameSearch = $state('')
24+
const visiblePipelines = $derived(pipelines?.filter((p) => matchesSubstring(p.name, nameSearch)))
2125
const bindScrollY = (node: HTMLElement, val: { scrollY: number }) => {
2226
$effect(() => {
2327
node.scrollTop = scrollY
@@ -37,13 +41,18 @@
3741
style="overflow-y: overlay;"
3842
use:bindScrollY={{ scrollY }}
3943
>
40-
<div class="bg-white-dark sticky top-0 -mr-1 flex justify-between pb-2 pl-4">
41-
<span class="content-center font-semibold">Pipelines</span>
44+
<div class="bg-white-dark sticky top-0 -mr-1 flex items-center gap-2 pb-2">
45+
<input
46+
class="input h-8 min-w-0 flex-1"
47+
type="search"
48+
placeholder="Search pipelines..."
49+
bind:value={nameSearch}
50+
/>
4251
<button onclick={onclose} class="fd fd-x btn-icon text-[24px]" aria-label="Close pipelines list"
4352
></button>
4453
</div>
45-
{#if pipelines}
46-
{#each pipelines as pipeline}
54+
{#if visiblePipelines}
55+
{#each visiblePipelines as pipeline}
4756
<a
4857
class="flex h-9 flex-nowrap items-center justify-between gap-2 rounded py-2 pl-4 {pipelineName ===
4958
pipeline.name
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { page } from 'vitest/browser'
3+
import { render } from 'vitest-browser-svelte'
4+
import type { PipelineThumb } from '$lib/services/pipelineManager'
5+
import List from './List.svelte'
6+
7+
// List only reads `name` (for the label/filter) and `status` (for the status
8+
// dot) off each thumb, so a minimal stub is enough to exercise the search.
9+
const thumb = (name: string): PipelineThumb =>
10+
({ name, status: 'Stopped' }) as unknown as PipelineThumb
11+
12+
const pipelines = [thumb('orders'), thumb('payments'), thumb('fraud-detection')]
13+
14+
// Each rendered pipeline is an <a>; the only other interactive elements are the
15+
// search box and the close button, so counting links counts visible pipelines.
16+
const visibleNames = () => page.getByRole('link').elements().map((el) => el.textContent?.trim())
17+
18+
describe('pipeline list search', () => {
19+
it('lists every pipeline before any search term is entered', async () => {
20+
render(List, { pipelineName: '', pipelines })
21+
22+
await expect.element(page.getByText('orders')).toBeInTheDocument()
23+
await expect.element(page.getByText('payments')).toBeInTheDocument()
24+
await expect.element(page.getByText('fraud-detection')).toBeInTheDocument()
25+
expect(visibleNames()).toHaveLength(3)
26+
})
27+
28+
it('filters to pipelines whose name contains the search term', async () => {
29+
render(List, { pipelineName: '', pipelines })
30+
31+
await page.getByPlaceholder('Search pipelines...').fill('pay')
32+
33+
await expect.element(page.getByText('payments')).toBeInTheDocument()
34+
await expect.element(page.getByText('orders')).not.toBeInTheDocument()
35+
await expect.element(page.getByText('fraud-detection')).not.toBeInTheDocument()
36+
expect(visibleNames()).toEqual(['payments'])
37+
})
38+
39+
it('matches case-insensitively', async () => {
40+
render(List, { pipelineName: '', pipelines })
41+
42+
await page.getByPlaceholder('Search pipelines...').fill('PAY')
43+
44+
expect(visibleNames()).toEqual(['payments'])
45+
})
46+
47+
it('matches a substring anywhere in the name, not only a prefix', async () => {
48+
render(List, { pipelineName: '', pipelines })
49+
50+
await page.getByPlaceholder('Search pipelines...').fill('detect')
51+
52+
expect(visibleNames()).toEqual(['fraud-detection'])
53+
})
54+
55+
it('shows no pipelines when the term matches nothing', async () => {
56+
render(List, { pipelineName: '', pipelines })
57+
58+
await page.getByPlaceholder('Search pipelines...').fill('no-such-pipeline')
59+
60+
expect(visibleNames()).toHaveLength(0)
61+
})
62+
63+
it('restores the full list when the search term is cleared', async () => {
64+
render(List, { pipelineName: '', pipelines })
65+
const search = page.getByPlaceholder('Search pipelines...')
66+
67+
await search.fill('pay')
68+
expect(visibleNames()).toEqual(['payments'])
69+
70+
await search.fill('')
71+
expect(visibleNames()).toHaveLength(3)
72+
})
73+
})

0 commit comments

Comments
 (0)