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:
parent
046d1dcbcf
commit
7ea5b07704
8 changed files with 45 additions and 7 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class EscalationChainViewSet(
|
|||
}
|
||||
|
||||
filter_backends = [SearchFilter]
|
||||
search_fields = ("^name",)
|
||||
search_fields = ("name",)
|
||||
|
||||
serializer_class = EscalationChainSerializer
|
||||
list_serializer_class = EscalationChainListSerializer
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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..."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue