From 7ea5b07704fb62fcd18840100727fd6f2740b484 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Mon, 20 Mar 2023 15:51:39 +0100 Subject: [PATCH] change escalation chains searching to allow for partial searching (#1578) # Which issue(s) this PR fixes Previously if you had an Escalation Chain named "Something Critical" and tried searching for "Critical", it would return no results. This was because the backend was using a "starts-with" search on the `name` attribute. This PR changes that to use "partial searching" + adds a few e2e test cases. ## Checklist - [x] Tests updated - [ ] Documentation added (N/A) - [x] `CHANGELOG.md` updated --- CHANGELOG.md | 1 + engine/apps/api/views/escalation_chain.py | 2 +- .../escalationChains/searching.test.ts | 36 +++++++++++++++++++ .../integration-tests/utils/alertGroup.ts | 2 +- .../utils/escalationChain.ts | 6 ++-- .../integration-tests/utils/integrations.ts | 2 +- .../EscalationsFilters/EscalationsFilters.tsx | 1 + .../escalation-chains/EscalationChains.tsx | 2 +- 8 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 grafana-plugin/integration-tests/escalationChains/searching.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1059cf2d..352afc3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated wording throughout plugin to use 'Alert Group' instead of 'Incident' ([1565](https://github.com/grafana/oncall/pull/1565), [1576](https://github.com/grafana/oncall/pull/1576)) - Filtering for Editors/Admins was added to rotation form. It is not allowed to assign Viewer to rotation ([1124](https://github.com/grafana/oncall/issues/1124)) +- Modified search behaviour on the Escalation Chains page to allow for "partial searching" ([1578](https://github.com/grafana/oncall/pull/1578)) ### Fixed diff --git a/engine/apps/api/views/escalation_chain.py b/engine/apps/api/views/escalation_chain.py index 81cd1ecb..7cbc1f93 100644 --- a/engine/apps/api/views/escalation_chain.py +++ b/engine/apps/api/views/escalation_chain.py @@ -46,7 +46,7 @@ class EscalationChainViewSet( } filter_backends = [SearchFilter] - search_fields = ("^name",) + search_fields = ("name",) serializer_class = EscalationChainSerializer list_serializer_class = EscalationChainListSerializer diff --git a/grafana-plugin/integration-tests/escalationChains/searching.test.ts b/grafana-plugin/integration-tests/escalationChains/searching.test.ts new file mode 100644 index 00000000..e9eaba5e --- /dev/null +++ b/grafana-plugin/integration-tests/escalationChains/searching.test.ts @@ -0,0 +1,36 @@ +import { test, expect, Page } from '@playwright/test'; +import { configureOnCallPlugin } from '../utils/configurePlugin'; +import { generateRandomValue } from '../utils/forms'; +import { createEscalationChain } from '../utils/escalationChain'; + +test.beforeEach(async ({ page }) => { + await configureOnCallPlugin(page); +}); + +const assertEscalationChainSearchWorks = async ( + page: Page, + searchTerm: string, + escalationChainFullName: string +): Promise => { + await page.getByTestId('escalation-chain-search-input').fill(searchTerm); + + // wait for the API call(s) to finish + await page.waitForLoadState('networkidle'); + + await expect(page.getByTestId('escalation-chains-list')).toHaveText(escalationChainFullName); +}; + +test('searching allows case-insensitive partial matches', async ({ page }) => { + const escalationChainName = `${generateRandomValue()} ${generateRandomValue()}`; + const [firstHalf, secondHalf] = escalationChainName.split(' '); + + await createEscalationChain(page, escalationChainName); + + await assertEscalationChainSearchWorks(page, firstHalf, escalationChainName); + await assertEscalationChainSearchWorks(page, firstHalf.toUpperCase(), escalationChainName); + await assertEscalationChainSearchWorks(page, firstHalf.toLowerCase(), escalationChainName); + + await assertEscalationChainSearchWorks(page, secondHalf, escalationChainName); + await assertEscalationChainSearchWorks(page, secondHalf.toUpperCase(), escalationChainName); + await assertEscalationChainSearchWorks(page, secondHalf.toLowerCase(), escalationChainName); +}); diff --git a/grafana-plugin/integration-tests/utils/alertGroup.ts b/grafana-plugin/integration-tests/utils/alertGroup.ts index f729f1a7..7f9b39fc 100644 --- a/grafana-plugin/integration-tests/utils/alertGroup.ts +++ b/grafana-plugin/integration-tests/utils/alertGroup.ts @@ -15,7 +15,7 @@ const incidentTimelineContainsStep = async (page: Page, triggeredStepText: strin return Promise.resolve(false); } - if (!page.locator('div[data-testid="incident-timeline-list"]').getByText(triggeredStepText)) { + if (!page.getByTestId('incident-timeline-list').getByText(triggeredStepText)) { await page.reload({ waitUntil: 'networkidle' }); return incidentTimelineContainsStep(page, triggeredStepText, (retryNum += 1)); } diff --git a/grafana-plugin/integration-tests/utils/escalationChain.ts b/grafana-plugin/integration-tests/utils/escalationChain.ts index 3a4263ce..f2bec766 100644 --- a/grafana-plugin/integration-tests/utils/escalationChain.ts +++ b/grafana-plugin/integration-tests/utils/escalationChain.ts @@ -16,8 +16,8 @@ const escalationStepValuePlaceholder: Record = { export const createEscalationChain = async ( page: Page, escalationChainName: string, - escalationStep: EscalationStep | null, - escalationStepValue: string | null + escalationStep?: EscalationStep, + escalationStepValue?: string ): Promise => { // go to the escalation chains page await goToOnCallPage(page, 'escalations'); @@ -32,7 +32,7 @@ export const createEscalationChain = async ( await clickButton({ page, buttonText: 'Create' }); await page.waitForSelector(`text=${escalationChainName}`); - if (!escalationStep) { + if (!escalationStep || !escalationStepValue) { return; } diff --git a/grafana-plugin/integration-tests/utils/integrations.ts b/grafana-plugin/integration-tests/utils/integrations.ts index eb93ceff..664fcd79 100644 --- a/grafana-plugin/integration-tests/utils/integrations.ts +++ b/grafana-plugin/integration-tests/utils/integrations.ts @@ -24,7 +24,7 @@ export const createIntegrationAndSendDemoAlert = async ( await fillInInput(page, 'div[data-testid="edit-integration-name-modal"] >> input', integrationName); await clickButton({ page, buttonText: 'Update' }); - const integrationSettingsElement = page.locator('div[data-testid="integration-settings"]'); + const integrationSettingsElement = page.getByTestId('integration-settings'); // assign the escalation chain to the integration await selectDropdownValue({ diff --git a/grafana-plugin/src/components/EscalationsFilters/EscalationsFilters.tsx b/grafana-plugin/src/components/EscalationsFilters/EscalationsFilters.tsx index bcbf3134..f2387261 100644 --- a/grafana-plugin/src/components/EscalationsFilters/EscalationsFilters.tsx +++ b/grafana-plugin/src/components/EscalationsFilters/EscalationsFilters.tsx @@ -39,6 +39,7 @@ const EscalationsFilters: FC = (props) => {
} placeholder="Search escalations..." diff --git a/grafana-plugin/src/pages/escalation-chains/EscalationChains.tsx b/grafana-plugin/src/pages/escalation-chains/EscalationChains.tsx index 729da478..fb9b0cd7 100644 --- a/grafana-plugin/src/pages/escalation-chains/EscalationChains.tsx +++ b/grafana-plugin/src/pages/escalation-chains/EscalationChains.tsx @@ -175,7 +175,7 @@ class EscalationChainsPage extends React.Component )} -
+
{searchResult ? (