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 1059cf2dda..352afc3b25 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 81cd1ecbef..7cbc1f9348 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 0000000000..e9eaba5eac --- /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 f729f1a797..7f9b39fc18 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 3a4263ce56..f2bec76683 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 eb93ceff69..664fcd7943 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 bcbf313448..f2387261c3 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 729da47865..fb9b0cd7f6 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 ? (