mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-11 09:09:34 +00:00
feat(finder): search all text fields and add filter badges
Searching only matched titles, so an event was unreachable by its cue, its note, or any custom field value. Custom fields are how teams model their own show data, which made the most valuable data the least findable. A bare query now matches cue, title, note and text custom fields across events, groups and milestones, and each result names the field it matched with an excerpt, so a hit in a note is legible. Widening the scan is free: at 5000 entries a pass over every field measures the same as the previous title-only pass, both far below the render cost. The filter syntax was only discoverable through a line of footer text. It is now a row of badges built from the fixed fields plus the project custom fields, which scope the search while keeping whatever the user already typed. The input becomes controlled, which removes the effect that replayed the last search on rundown changes. Raises the result cap and reports the total, since matching more fields means more results than the previous cap could show. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DzALEq9gGWwFmwTdgAiFcY
This commit is contained in:
@@ -356,6 +356,112 @@ test('Finder searches milestones by cue', async ({ page }) => {
|
||||
await expect(page.getByTestId('finder-result')).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('Finder searches notes and reports the matching field', async ({ page }) => {
|
||||
await page.goto('/rundown');
|
||||
await page.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// clear rundown
|
||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||
|
||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||
await page.getByTestId('entry-1').getByTestId('entry__title').fill('opening remarks');
|
||||
await page.getByTestId('entry-1').getByTestId('entry__title').press('Enter');
|
||||
|
||||
// the note is not shown on the rundown row, so it can only be reached by searching
|
||||
await page.getByTestId('entry-1').click();
|
||||
await page.getByLabel('Note', { exact: true }).fill('remember the zebrafish tank');
|
||||
await page.getByLabel('Note', { exact: true }).press('Tab');
|
||||
|
||||
await page.keyboard.press('ControlOrMeta+f');
|
||||
await expect(page.getByPlaceholder('Search...')).toBeVisible();
|
||||
|
||||
// a bare query now reaches the note, and the row explains which field matched
|
||||
await page.getByPlaceholder('Search...').fill('zebrafish');
|
||||
await expect(page.getByTestId('finder-result')).toHaveCount(1);
|
||||
await expect(page.getByTestId('finder-result-match')).toContainText('Note');
|
||||
await expect(page.getByTestId('finder-result-match')).toContainText('zebrafish');
|
||||
|
||||
// scoping to the title excludes it again
|
||||
await page.getByPlaceholder('Search...').fill('title zebrafish');
|
||||
await expect(page.getByTestId('finder-result')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('Finder filter badges scope the search', async ({ page }) => {
|
||||
await page.goto('/rundown');
|
||||
await page.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// clear rundown
|
||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||
|
||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||
await page.getByTestId('entry-1').getByTestId('entry__title').fill('sound check');
|
||||
await page.getByTestId('entry-1').getByTestId('entry__title').press('Enter');
|
||||
|
||||
await page.keyboard.press('ControlOrMeta+f');
|
||||
await expect(page.getByPlaceholder('Search...')).toBeVisible();
|
||||
|
||||
// typing first, then narrowing with a badge, keeps the search text
|
||||
await page.getByPlaceholder('Search...').fill('sound');
|
||||
await expect(page.getByTestId('finder-result')).toHaveCount(1);
|
||||
|
||||
const filters = page.getByTestId('finder-filters');
|
||||
await filters.getByRole('button', { name: 'Note', exact: true }).click();
|
||||
await expect(page.getByPlaceholder('Search...')).toHaveValue('note sound');
|
||||
await expect(page.getByTestId('finder-result')).toHaveCount(0);
|
||||
|
||||
// switching to another badge replaces the filter rather than stacking
|
||||
await filters.getByRole('button', { name: 'Title', exact: true }).click();
|
||||
await expect(page.getByPlaceholder('Search...')).toHaveValue('title sound');
|
||||
await expect(page.getByTestId('finder-result')).toHaveCount(1);
|
||||
await expect(page.getByTestId('finder-count')).toContainText('1 result');
|
||||
|
||||
// custom fields defined by the project are offered as filters too
|
||||
await expect(filters.getByRole('button', { name: 'Song', exact: true })).toBeVisible();
|
||||
await expect(filters.getByRole('button', { name: 'Artist', exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('Finder searches custom fields', async ({ page }) => {
|
||||
await page.goto('/rundown');
|
||||
await page.getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
// clear rundown
|
||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Clear all' }).click();
|
||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||
|
||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||
await page.getByTestId('entry-1').getByTestId('entry__title').fill('interval');
|
||||
await page.getByTestId('entry-1').getByTestId('entry__title').press('Enter');
|
||||
|
||||
// custom field values are not shown on the rundown row
|
||||
await page.getByTestId('entry-1').click();
|
||||
await page.getByLabel('Artist', { exact: true }).fill('zebrafish collective');
|
||||
await page.getByLabel('Artist', { exact: true }).press('Tab');
|
||||
|
||||
await page.keyboard.press('ControlOrMeta+f');
|
||||
await expect(page.getByPlaceholder('Search...')).toBeVisible();
|
||||
|
||||
// a bare query reaches custom field values, naming the field that matched
|
||||
await page.getByPlaceholder('Search...').fill('zebrafish');
|
||||
await expect(page.getByTestId('finder-result')).toHaveCount(1);
|
||||
await expect(page.getByTestId('finder-result-match')).toContainText('Artist');
|
||||
|
||||
// and the field can be scoped explicitly
|
||||
await page.getByTestId('finder-filters').getByRole('button', { name: 'Artist', exact: true }).click();
|
||||
await expect(page.getByPlaceholder('Search...')).toHaveValue('Artist zebrafish');
|
||||
await expect(page.getByTestId('finder-result')).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('Open settings', async ({ page }) => {
|
||||
await page.goto('/editor');
|
||||
await expect(page.getByTestId('editor-container')).toBeVisible();
|
||||
|
||||
Reference in New Issue
Block a user