Skip to content

Commit

Permalink
Tweaks to overrides (added draggable bounds, take timezone into consi…
Browse files Browse the repository at this point in the history
…deration) (#4553)

# What this PR does

Part of #4428

## Which issue(s) this PR closes

Closes #4547
  • Loading branch information
teodosii authored Jun 21, 2024
1 parent 78c646f commit 919ba86
Show file tree
Hide file tree
Showing 21 changed files with 326 additions and 163 deletions.
4 changes: 2 additions & 2 deletions e2e-tests/alerts/onCallSchedule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { verifyThatAlertGroupIsTriggered } from '../utils/alertGroup';
import { createEscalationChain, EscalationStep } from '../utils/escalationChain';
import { generateRandomValue } from '../utils/forms';
import { createIntegrationAndSendDemoAlert } from '../utils/integrations';
import { createOnCallScheduleWithRotation } from '../utils/schedule';
import { createOnCallSchedule } from '../utils/schedule';

test('we can create an oncall schedule + receive an alert', async ({ adminRolePage }) => {
const { page, userName } = adminRolePage;
const escalationChainName = generateRandomValue();
const integrationName = generateRandomValue();
const onCallScheduleName = generateRandomValue();

await createOnCallScheduleWithRotation(page, onCallScheduleName, userName);
await createOnCallSchedule(page, onCallScheduleName, userName);
await createEscalationChain(
page,
escalationChainName,
Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/insights/insights.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createEscalationChain, EscalationStep } from '../utils/escalationChain'
import { clickButton, generateRandomValue } from '../utils/forms';
import { createIntegrationAndSendDemoAlert } from '../utils/integrations';
import { goToGrafanaPage, goToOnCallPage } from '../utils/navigation';
import { createOnCallScheduleWithRotation } from '../utils/schedule';
import { createOnCallSchedule } from '../utils/schedule';

/**
* Insights is dependent on Scenes which were only added in Grafana 10.0.0
Expand Down Expand Up @@ -66,7 +66,7 @@ test.describe.skip('Insights', () => {
const escalationChainName = generateRandomValue();
const integrationName = generateRandomValue();
const onCallScheduleName = generateRandomValue();
await createOnCallScheduleWithRotation(page, onCallScheduleName, userName);
await createOnCallSchedule(page, onCallScheduleName, userName);
await createEscalationChain(
page,
escalationChainName,
Expand Down
46 changes: 42 additions & 4 deletions e2e-tests/schedules/addOverride.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import dayjs from 'dayjs';

import { test, expect } from '../fixtures';
import { test, expect, Locator } from '../fixtures';
import { MOSCOW_TIMEZONE } from '../utils/constants';
import { clickButton, generateRandomValue } from '../utils/forms';
import { createOnCallScheduleWithRotation, getOverrideFormDateInputs } from '../utils/schedule';
import { setTimezoneInProfile } from '../utils/grafanaProfile';
import { createOnCallSchedule, getOverrideFormDateInputs } from '../utils/schedule';

test('default dates in override creation modal are correct', async ({ adminRolePage }) => {
test('Default dates in override creation modal are set to today', async ({ adminRolePage }) => {
const { page, userName } = adminRolePage;

const onCallScheduleName = generateRandomValue();
await createOnCallScheduleWithRotation(page, onCallScheduleName, userName);
await createOnCallSchedule(page, onCallScheduleName, userName);

await clickButton({ page, buttonText: 'Add override' });

Expand All @@ -20,3 +22,39 @@ test('default dates in override creation modal are correct', async ({ adminRoleP
expect(overrideFormDateInputs.start.isSame(expectedStart)).toBe(true);
expect(overrideFormDateInputs.end.isSame(expectedEnd)).toBe(true);
});

test('Fills in override time and reacts to timezone change', async ({ adminRolePage }) => {
const { page, userName } = adminRolePage;

await setTimezoneInProfile(page, MOSCOW_TIMEZONE); // UTC+3

const onCallScheduleName = generateRandomValue();
await createOnCallSchedule(page, onCallScheduleName, userName, false);

await clickButton({ page, buttonText: 'Add override' });

const overrideStartEl = page.getByTestId('override-start');
await changeDatePickerTime(overrideStartEl, '02');
await expect(overrideStartEl.getByTestId('date-time-picker').getByRole('textbox')).toHaveValue('02:00');

const overrideEndEl = page.getByTestId('override-end');
await changeDatePickerTime(overrideEndEl, '12');
await expect(overrideEndEl.getByTestId('date-time-picker').getByRole('textbox')).toHaveValue('12:00');

await page.getByRole('dialog').click(); // clear focus

await page.getByTestId('timezone-select').locator('svg').click();
await page.getByText('GMT', { exact: true }).click();

// expect times to go back by -3
await expect(overrideStartEl.getByTestId('date-time-picker').getByRole('textbox')).toHaveValue('23:00');
await expect(overrideEndEl.getByTestId('date-time-picker').getByRole('textbox')).toHaveValue('09:00');

async function changeDatePickerTime(element: Locator, value: string) {
await element.getByRole('img').click();
// set minutes to {value}
await page.locator('.rc-time-picker-panel').getByRole('button', { name: value }).first().click();
// set seconds to 00
await page.getByRole('button', { name: '00' }).nth(1).click();
}
});
43 changes: 43 additions & 0 deletions e2e-tests/schedules/addRotation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { test, expect, Locator } from '../fixtures';
import { MOSCOW_TIMEZONE } from '../utils/constants';
import { clickButton, generateRandomValue } from '../utils/forms';
import { setTimezoneInProfile } from '../utils/grafanaProfile';
import { createOnCallSchedule } from '../utils/schedule';

test('Fills in Rotation time and reacts to timezone change', async ({ adminRolePage }) => {
const { page, userName } = adminRolePage;

await setTimezoneInProfile(page, MOSCOW_TIMEZONE); // UTC+3

const onCallScheduleName = generateRandomValue();
await createOnCallSchedule(page, onCallScheduleName, userName, false);

await clickButton({ page, buttonText: 'Add rotation' });
// enable Rotation End
await page.getByTestId('rotation-end').getByLabel('Toggle switch').click();

const startEl = page.getByTestId('rotation-start');
await changeDatePickerTime(startEl, '02');
await expect(startEl.getByTestId('date-time-picker').getByRole('textbox')).toHaveValue('02:00');

const endEl = page.getByTestId('rotation-end');
await changeDatePickerTime(endEl, '12');
await expect(endEl.getByTestId('date-time-picker').getByRole('textbox')).toHaveValue('12:00');

await page.getByRole('dialog').click(); // clear focus

await page.getByTestId('timezone-select').locator('svg').click();
await page.getByText('GMT', { exact: true }).click();

// expect times to go back by -3
await expect(startEl.getByTestId('date-time-picker').getByRole('textbox')).toHaveValue('23:00');
await expect(endEl.getByTestId('date-time-picker').getByRole('textbox')).toHaveValue('09:00');

async function changeDatePickerTime(element: Locator, value: string) {
await element.getByRole('img').click();
// set minutes to {value}
await page.locator('.rc-time-picker-panel').getByRole('button', { name: value }).first().click();
// set seconds to 00
await page.getByRole('button', { name: '00' }).nth(1).click();
}
});
4 changes: 2 additions & 2 deletions e2e-tests/schedules/quality.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { test, expect } from '../fixtures';
import { generateRandomValue } from '../utils/forms';
import { createOnCallScheduleWithRotation } from '../utils/schedule';
import { createOnCallSchedule } from '../utils/schedule';

test('check schedule quality for simple 1-user schedule', async ({ adminRolePage }) => {
const { page, userName } = adminRolePage;
const onCallScheduleName = generateRandomValue();

await createOnCallScheduleWithRotation(page, onCallScheduleName, userName);
await createOnCallSchedule(page, onCallScheduleName, userName);

const scheduleQualityElement = page.getByTestId('schedule-quality');
await scheduleQualityElement.waitFor({ state: 'visible' });
Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/schedules/scheduleDetails.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { test, expect } from '../fixtures';
import { generateRandomValue } from '../utils/forms';
import { createOnCallScheduleWithRotation, createRotation } from '../utils/schedule';
import { createOnCallSchedule, createRotation } from '../utils/schedule';

test(`user can see the other user's details`, async ({ adminRolePage, editorRolePage }) => {
const { page, userName: adminUserName } = adminRolePage;
const editorUserName = editorRolePage.userName;
const onCallScheduleName = generateRandomValue();

await createOnCallScheduleWithRotation(page, onCallScheduleName, adminUserName);
await createOnCallSchedule(page, onCallScheduleName, adminUserName);
await createRotation(page, editorUserName, false);

await page.waitForTimeout(1_000);
Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/schedules/scheduleView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { HTML_ID } from 'utils/DOM';

import { expect, test } from '../fixtures';
import { generateRandomValue } from '../utils/forms';
import { createOnCallScheduleWithRotation } from '../utils/schedule';
import { createOnCallSchedule } from '../utils/schedule';

test.skip('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage }) => {
const { page, userName } = adminRolePage;

const onCallScheduleName = generateRandomValue();
await createOnCallScheduleWithRotation(page, onCallScheduleName, userName);
await createOnCallSchedule(page, onCallScheduleName, userName);

// ScheduleView.OneWeek is selected by default
expect(await page.getByLabel(ScheduleView.OneWeek, { exact: true }).isChecked()).toBe(true);
Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/schedules/schedulesList.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { expect, test } from '../fixtures';
import { generateRandomValue } from '../utils/forms';
import { goToOnCallPage } from '../utils/navigation';
import { createOnCallScheduleWithRotation } from '../utils/schedule';
import { createOnCallSchedule } from '../utils/schedule';

test('schedule calendar and list of schedules is correctly displayed', async ({ adminRolePage }) => {
const { page, userName } = adminRolePage;

const onCallScheduleName = generateRandomValue();
await createOnCallScheduleWithRotation(page, onCallScheduleName, userName);
await createOnCallSchedule(page, onCallScheduleName, userName);

await goToOnCallPage(page, 'schedules');

Expand Down
7 changes: 3 additions & 4 deletions e2e-tests/schedules/timezones.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import isoWeek from 'dayjs/plugin/isoWeek';
import utc from 'dayjs/plugin/utc';

import { test } from '../fixtures';
import { MOSCOW_TIMEZONE } from '../utils/constants';
import { clickButton, generateRandomValue } from '../utils/forms';
import { setTimezoneInProfile } from '../utils/grafanaProfile';
import { createOnCallScheduleWithRotation } from '../utils/schedule';
import { createOnCallSchedule } from '../utils/schedule';

dayjs.extend(utc);
dayjs.extend(isoWeek);

const MOSCOW_TIMEZONE = 'Europe/Moscow';

test.use({ timezoneId: MOSCOW_TIMEZONE }); // GMT+3 the whole year
const currentUtcTimeHour = dayjs().utc().format('HH');
const currentUtcDate = dayjs().utc().format('DD MMM');
Expand All @@ -25,7 +24,7 @@ test('dates in schedule are correct according to selected current timezone', asy
await setTimezoneInProfile(page, MOSCOW_TIMEZONE);

const onCallScheduleName = generateRandomValue();
await createOnCallScheduleWithRotation(page, onCallScheduleName, userName);
await createOnCallSchedule(page, onCallScheduleName, userName);

// Current timezone is selected by default to currently logged in user timezone
await expect(page.getByTestId('timezone-select')).toHaveText('GMT+3');
Expand Down
2 changes: 2 additions & 0 deletions e2e-tests/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export const GRAFANA_ADMIN_PASSWORD = process.env.GRAFANA_ADMIN_PASSWORD || 'onc

export const IS_OPEN_SOURCE = (process.env.IS_OPEN_SOURCE || 'true').toLowerCase() === 'true';
export const IS_CLOUD = !IS_OPEN_SOURCE;

export const MOSCOW_TIMEZONE = 'Europe/Moscow';
5 changes: 4 additions & 1 deletion e2e-tests/utils/grafanaProfile.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Page } from '@playwright/test';
import { Page, expect } from '@playwright/test';

import { goToGrafanaPage } from './navigation';

export const setTimezoneInProfile = async (page: Page, timezone: string) => {
await goToGrafanaPage(page, '/profile');
await expect(page.getByLabel('Time zone picker')).toBeVisible();

await page.getByLabel('Time zone picker').click();
await page.getByLabel('Select options menu').getByText(timezone).click();
await page.getByTestId('data-testid-shared-prefs-save').click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(3000); // wait for reload
};
9 changes: 6 additions & 3 deletions e2e-tests/utils/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import dayjs from 'dayjs';
import { clickButton, selectDropdownValue } from './forms';
import { goToOnCallPage } from './navigation';

export const createOnCallScheduleWithRotation = async (
export const createOnCallSchedule = async (
page: Page,
scheduleName: string,
userName: string
userName: string,
withRotation = true
): Promise<void> => {
// go to the schedules page
await goToOnCallPage(page, 'schedules');
Expand All @@ -22,7 +23,9 @@ export const createOnCallScheduleWithRotation = async (
// Add a new layer w/ the current user to it
await clickButton({ page, buttonText: 'Create Schedule' });

await createRotation(page, userName);
if (withRotation) {
await createRotation(page, userName);
}
};

export const createRotation = async (page: Page, userName: string, isFirstScheduleRotation = true) => {
Expand Down
41 changes: 41 additions & 0 deletions src/containers/RotationForm/RotationForm.helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Dayjs, ManipulateType } from 'dayjs';
import { DraggableData } from 'react-draggable';

import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers';
import { GRAFANA_HEADER_HEIGHT, GRAFANA_LEGACY_SIDEBAR_WIDTH } from 'utils/consts';

import { RepeatEveryPeriod } from './RotationForm.types';

Expand Down Expand Up @@ -173,3 +177,40 @@ export const dayJSAddWithDSTFixed = ({

return newDateCandidate.add(diff, 'minutes');
};

export function getDraggableModalCoordinatesOnInit(
data: DraggableData,
offsetTop: number
): {
left: number;
right: number;
top: number;
bottom: number;
} {
if (!data) {
return undefined;
}

const scrollBarReferenceElements = document.querySelectorAll<HTMLElement>('.scrollbar-view');
// top navbar display has 2 scrollbar-view elements (navbar & content)
const baseReferenceElRect = (
scrollBarReferenceElements.length === 1 ? scrollBarReferenceElements[0] : scrollBarReferenceElements[1]
).getBoundingClientRect();

const { right, bottom } = baseReferenceElRect;

return isTopNavbar()
? {
// values are adjusted by any padding/margin differences
left: -data.node.offsetLeft + 4,
right: right - (data.node.offsetLeft + data.node.offsetWidth) - 12,
top: -offsetTop + GRAFANA_HEADER_HEIGHT + 4,
bottom: bottom - data.node.offsetHeight - offsetTop - 12,
}
: {
left: -data.node.offsetLeft + 4 + GRAFANA_LEGACY_SIDEBAR_WIDTH,
right: right - (data.node.offsetLeft + data.node.offsetWidth) - 12,
top: -offsetTop + 4,
bottom: bottom - data.node.offsetHeight - offsetTop - 12,
};
}
Loading

0 comments on commit 919ba86

Please sign in to comment.