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
This commit is contained in:
Joey Orlando 2023-03-20 15:51:39 +01:00 committed by GitHub
parent 046d1dcbcf
commit 7ea5b07704
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 45 additions and 7 deletions

View file

@ -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

View file

@ -46,7 +46,7 @@ class EscalationChainViewSet(
}
filter_backends = [SearchFilter]
search_fields = ("^name",)
search_fields = ("name",)
serializer_class = EscalationChainSerializer
list_serializer_class = EscalationChainListSerializer

View file

@ -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<void> => {
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);
});

View file

@ -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));
}

View file

@ -16,8 +16,8 @@ const escalationStepValuePlaceholder: Record<EscalationStep, string> = {
export const createEscalationChain = async (
page: Page,
escalationChainName: string,
escalationStep: EscalationStep | null,
escalationStepValue: string | null
escalationStep?: EscalationStep,
escalationStepValue?: string
): Promise<void> => {
// 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;
}

View file

@ -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({

View file

@ -39,6 +39,7 @@ const EscalationsFilters: FC<EscalationsFiltersProps> = (props) => {
<div className={cx('root')}>
<Input
autoFocus
data-testid="escalation-chain-search-input"
className={cx('search')}
prefix={<Icon name="search" />}
placeholder="Search escalations..."

View file

@ -175,7 +175,7 @@ class EscalationChainsPage extends React.Component<EscalationChainsPageProps, Es
</Button>
</WithPermissionControlTooltip>
)}
<div className={cx('escalations-list')}>
<div className={cx('escalations-list')} data-testid="escalation-chains-list">
{searchResult ? (
<GList
autoScroll