Direct paging integrations table (#3290)
# What this PR does Closes https://github.com/grafana/oncall/issues/3119 Closes https://github.com/grafana/oncall-private/issues/2061 ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Dominik <dominik.broj@grafana.com>
This commit is contained in:
parent
152b1b1c58
commit
37160806ca
16 changed files with 311 additions and 80 deletions
|
|
@ -9,7 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Added
|
||||
|
||||
- Added user timezone field to the users public API response ([#3311](https://github.com/grafana/oncall/pull/3311))
|
||||
- Added user timezone field to the users public API response ([#3311](https://github.com/grafana/oncall/pull/3311))
|
||||
|
||||
### Changed
|
||||
|
||||
- Split Integrations table into Connections and Direct Paging tabs ([#3290](https://github.com/grafana/oncall/pull/3290))
|
||||
|
||||
## v1.3.57 (2023-11-10)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,29 @@ def test_get_alert_receive_channel(alert_receive_channel_internal_api_setup, mak
|
|||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_get_alert_receive_channel_by_integration_ne(
|
||||
make_organization_and_user_with_plugin_token, make_user_auth_headers, make_alert_receive_channel
|
||||
):
|
||||
organization, user, token = make_organization_and_user_with_plugin_token()
|
||||
|
||||
make_alert_receive_channel(organization, integration=AlertReceiveChannel.INTEGRATION_GRAFANA)
|
||||
make_alert_receive_channel(organization, integration=AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING)
|
||||
make_alert_receive_channel(organization, integration=AlertReceiveChannel.INTEGRATION_DIRECT_PAGING)
|
||||
|
||||
client = APIClient()
|
||||
url = f"{reverse('api-internal:alert_receive_channel-list')}?integration_ne={AlertReceiveChannel.INTEGRATION_DIRECT_PAGING}"
|
||||
|
||||
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
||||
results = response.json()["results"]
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert len(results) == 2
|
||||
|
||||
for result in results:
|
||||
assert result["integration"] != AlertReceiveChannel.INTEGRATION_DIRECT_PAGING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"query_param,should_be_unpaginated",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ class AlertReceiveChannelFilter(ByTeamModelFieldFilterMixin, filters.FilterSet):
|
|||
choices=AlertReceiveChannel.MAINTENANCE_MODE_CHOICES, method="filter_maintenance_mode"
|
||||
)
|
||||
integration = filters.MultipleChoiceFilter(choices=AlertReceiveChannel.INTEGRATION_CHOICES)
|
||||
integration_ne = filters.MultipleChoiceFilter(
|
||||
choices=AlertReceiveChannel.INTEGRATION_CHOICES, field_name="integration", exclude=True
|
||||
)
|
||||
team = TeamModelMultipleChoiceFilter()
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ module.exports = {
|
|||
'newlines-between': 'always',
|
||||
},
|
||||
],
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
'no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ test.describe("updating an integration's heartbeat interval works", async () =>
|
|||
};
|
||||
|
||||
test('change heartbeat interval', async ({ adminRolePage: { page } }) => {
|
||||
await createIntegration(page, generateRandomValue());
|
||||
await createIntegration({ page, integrationName: generateRandomValue() });
|
||||
|
||||
await _openHeartbeatSettingsForm(page);
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ test.describe("updating an integration's heartbeat interval works", async () =>
|
|||
});
|
||||
|
||||
test('send heartbeat', async ({ adminRolePage: { page } }) => {
|
||||
await createIntegration(page, generateRandomValue());
|
||||
await createIntegration({ page, integrationName: generateRandomValue() });
|
||||
|
||||
await _openHeartbeatSettingsForm(page);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import { test, expect } from '../fixtures';
|
||||
import { generateRandomValue } from '../utils/forms';
|
||||
import { createIntegration } from '../utils/integrations';
|
||||
|
||||
test('Integrations table shows data in Connections and Direct Paging tabs', async ({ adminRolePage: { page } }) => {
|
||||
// // Create 2 integrations that are not Direct Paging
|
||||
const ID = generateRandomValue();
|
||||
const WEBHOOK_INTEGRATION_NAME = `Webhook-${ID}`;
|
||||
const ALERTMANAGER_INTEGRATION_NAME = `Alertmanager-${ID}`;
|
||||
const DIRECT_PAGING_INTEGRATION_NAME = `Direct paging`;
|
||||
|
||||
await createIntegration({ page, integrationSearchText: 'Webhook', integrationName: WEBHOOK_INTEGRATION_NAME });
|
||||
await page.getByRole('tab', { name: 'Tab Integrations' }).click();
|
||||
|
||||
await createIntegration({
|
||||
page,
|
||||
integrationSearchText: 'Alertmanager',
|
||||
shouldGoToIntegrationsPage: false,
|
||||
integrationName: ALERTMANAGER_INTEGRATION_NAME,
|
||||
});
|
||||
await page.getByRole('tab', { name: 'Tab Integrations' }).click();
|
||||
|
||||
// Create 1 Direct Paging integration if it doesn't exist
|
||||
const integrationsTable = page.getByTestId('integrations-table');
|
||||
await page.getByRole('tab', { name: 'Tab Direct Paging' }).click();
|
||||
const isDirectPagingAlreadyCreated = await page.getByText('Direct paging').isVisible();
|
||||
if (!isDirectPagingAlreadyCreated) {
|
||||
await createIntegration({
|
||||
page,
|
||||
integrationSearchText: 'Direct paging',
|
||||
shouldGoToIntegrationsPage: false,
|
||||
integrationName: DIRECT_PAGING_INTEGRATION_NAME,
|
||||
});
|
||||
}
|
||||
await page.getByRole('tab', { name: 'Tab Integrations' }).click();
|
||||
|
||||
// By default Connections tab is opened and newly created integrations are visible except Direct Paging one
|
||||
await expect(integrationsTable.getByText(WEBHOOK_INTEGRATION_NAME)).toBeVisible();
|
||||
await expect(integrationsTable.getByText(ALERTMANAGER_INTEGRATION_NAME)).toBeVisible();
|
||||
await expect(integrationsTable).not.toContainText(DIRECT_PAGING_INTEGRATION_NAME);
|
||||
|
||||
// Then after switching to Direct Paging tab only Direct Paging integration is visible
|
||||
await page.getByRole('tab', { name: 'Tab Direct Paging' }).click();
|
||||
await expect(integrationsTable.getByText(WEBHOOK_INTEGRATION_NAME)).not.toBeVisible();
|
||||
await expect(integrationsTable.getByText(ALERTMANAGER_INTEGRATION_NAME)).not.toBeVisible();
|
||||
await expect(integrationsTable).toContainText(DIRECT_PAGING_INTEGRATION_NAME);
|
||||
});
|
||||
|
|
@ -106,7 +106,7 @@ test.describe('maintenance mode works', () => {
|
|||
const integrationName = generateRandomValue();
|
||||
|
||||
await createEscalationChain(page, escalationChainName, EscalationStep.NotifyUsers, userName);
|
||||
await createIntegration(page, integrationName);
|
||||
await createIntegration({ page, integrationName });
|
||||
await assignEscalationChainToIntegration(page, escalationChainName);
|
||||
await enableMaintenanceMode(page, maintenanceModeType);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export const createEscalationChain = async (
|
|||
await page.locator('text=Loading...').waitFor({ state: 'detached' });
|
||||
|
||||
// open the create escalation chain modal
|
||||
(await page.waitForSelector('text=New Escalation Chain')).click();
|
||||
(await page.waitForSelector('text=/New Escalation Chain/i')).click();
|
||||
|
||||
// fill in the name input
|
||||
await fillInInput(page, 'div[data-testid="create-escalation-chain-name-input-modal"] >> input', escalationChainName);
|
||||
|
|
@ -44,7 +44,10 @@ export const createEscalationChain = async (
|
|||
if (escalationStep) {
|
||||
// add an escalation step
|
||||
await selectDropdownValue({
|
||||
page, selectType: 'grafanaSelect', placeholderText: 'Add escalation step...', value: escalationStep,
|
||||
page,
|
||||
selectType: 'grafanaSelect',
|
||||
placeholderText: 'Add escalation step...',
|
||||
value: escalationStep,
|
||||
});
|
||||
|
||||
// toggle important
|
||||
|
|
@ -52,13 +55,15 @@ export const createEscalationChain = async (
|
|||
await selectDropdownValue({
|
||||
page,
|
||||
selectType: 'grafanaSelect',
|
||||
placeholderText: "Default",
|
||||
value: "Important",
|
||||
placeholderText: 'Default',
|
||||
value: 'Important',
|
||||
});
|
||||
}
|
||||
|
||||
// select the escalation step value (e.g. user or schedule)
|
||||
if (escalationStepValue) {await selectEscalationStepValue(page, escalationStep, escalationStepValue);}
|
||||
if (escalationStepValue) {
|
||||
await selectEscalationStepValue(page, escalationStep, escalationStepValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,40 @@
|
|||
import { Page } from '@playwright/test';
|
||||
import { clickButton, selectDropdownValue } from './forms';
|
||||
import { clickButton, generateRandomValue, selectDropdownValue } from './forms';
|
||||
import { goToOnCallPage } from './navigation';
|
||||
|
||||
const CREATE_INTEGRATION_MODAL_TEST_ID_SELECTOR = 'div[data-testid="create-integration-modal"]';
|
||||
|
||||
export const openCreateIntegrationModal = async (page: Page): Promise<void> => {
|
||||
// go to the integrations page
|
||||
await goToOnCallPage(page, 'integrations');
|
||||
|
||||
// open the create integration modal
|
||||
(await page.waitForSelector('text=New integration')).click();
|
||||
await page.getByRole('button', { name: 'New integration' }).click();
|
||||
|
||||
// wait for it to pop up
|
||||
await page.waitForSelector(CREATE_INTEGRATION_MODAL_TEST_ID_SELECTOR);
|
||||
await page.getByTestId('create-integration-modal').waitFor();
|
||||
};
|
||||
|
||||
export const createIntegration = async (page: Page, integrationName: string): Promise<void> => {
|
||||
export const createIntegration = async ({
|
||||
page,
|
||||
integrationName = `integration-${generateRandomValue()}`,
|
||||
integrationSearchText = 'Webhook',
|
||||
shouldGoToIntegrationsPage = true,
|
||||
}: {
|
||||
page: Page;
|
||||
integrationName?: string;
|
||||
integrationSearchText?: string;
|
||||
shouldGoToIntegrationsPage?: boolean;
|
||||
}): Promise<void> => {
|
||||
if (shouldGoToIntegrationsPage) {
|
||||
// go to the integrations page
|
||||
await goToOnCallPage(page, 'integrations');
|
||||
}
|
||||
|
||||
await openCreateIntegrationModal(page);
|
||||
|
||||
// create a webhook integration
|
||||
(await page.waitForSelector(`${CREATE_INTEGRATION_MODAL_TEST_ID_SELECTOR} >> text=Webhook`)).click();
|
||||
// create an integration
|
||||
await page
|
||||
.getByTestId('create-integration-modal')
|
||||
.getByTestId('integration-display-name')
|
||||
.filter({ hasText: integrationSearchText })
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// fill in the required inputs
|
||||
(await page.waitForSelector('input[name="verbal_name"]', { state: 'attached' })).fill(integrationName);
|
||||
|
|
@ -55,7 +70,7 @@ export const createIntegrationAndSendDemoAlert = async (
|
|||
integrationName: string,
|
||||
escalationChainName: string
|
||||
): Promise<void> => {
|
||||
await createIntegration(page, integrationName);
|
||||
await createIntegration({ page, integrationName });
|
||||
await assignEscalationChainToIntegration(page, escalationChainName);
|
||||
await sendDemoAlert(page);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
"test:silent": "jest --silent",
|
||||
"test:e2e": "yarn playwright test --grep-invert @expensive",
|
||||
"test:e2e-expensive": "yarn playwright test --grep @expensive",
|
||||
"test:e2e:watch": "yarn test:e2e --ui",
|
||||
"test:e2e:gen": "yarn playwright codegen http://localhost:3000",
|
||||
"cleanup-e2e-results": "rm -rf playwright-report && rm -rf test-results",
|
||||
"e2e-show-report": "yarn playwright show-report",
|
||||
"dev": "grafana-toolkit plugin:dev",
|
||||
|
|
|
|||
|
|
@ -21,11 +21,8 @@ interface LabelsFilterProps {
|
|||
|
||||
const LabelsFilter = observer((props: LabelsFilterProps) => {
|
||||
const { filterType, className, autoFocus, value: propsValue, onChange } = props;
|
||||
|
||||
const [value, setValue] = useState([]);
|
||||
|
||||
const [keys, setKeys] = useState([]);
|
||||
|
||||
const { alertGroupStore, labelsStore } = useStore();
|
||||
|
||||
const loadKeys =
|
||||
|
|
@ -44,9 +41,7 @@ const LabelsFilter = observer((props: LabelsFilterProps) => {
|
|||
|
||||
useEffect(() => {
|
||||
const keyValuePairs = (propsValue || []).map((k) => k.split(':'));
|
||||
|
||||
const promises = keyValuePairs.map(([keyId]) => loadValuesForKey(keyId));
|
||||
|
||||
const fetchKeyValues = async () => await Promise.all(promises);
|
||||
|
||||
fetchKeyValues().then((list) => {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ interface RemoteFiltersProps extends WithStoreProps {
|
|||
defaultFilters?: FiltersValues;
|
||||
extraFilters?: (state, setState, onFiltersValueChange) => React.ReactNode;
|
||||
grafanaTeamStore: GrafanaTeamStore;
|
||||
skipFilterOptionFn?: (filterOption: FilterOption) => boolean;
|
||||
}
|
||||
interface RemoteFiltersState {
|
||||
filterOptions?: FilterOption[];
|
||||
|
|
@ -86,11 +87,16 @@ class RemoteFilters extends Component<RemoteFiltersProps, RemoteFiltersState> {
|
|||
page,
|
||||
store: { filtersStore },
|
||||
defaultFilters,
|
||||
skipFilterOptionFn,
|
||||
} = this.props;
|
||||
|
||||
const filterOptions = await filtersStore.updateOptionsForPage(page);
|
||||
let filterOptions = await filtersStore.updateOptionsForPage(page);
|
||||
const currentTablePageNum = parseInt(filtersStore.currentTablePageNum[page] || query.p || 1, 10);
|
||||
|
||||
if (skipFilterOptionFn) {
|
||||
filterOptions = filterOptions.filter((option: FilterOption) => !skipFilterOptionFn(option));
|
||||
}
|
||||
|
||||
// set the current page from filters/query or default it to 1
|
||||
filtersStore.setCurrentTablePageNum(page, currentTablePageNum);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
AlertReceiveChannelCounters,
|
||||
ContactPoint,
|
||||
MaintenanceMode,
|
||||
SupportedIntegrationFilters,
|
||||
} from './alert_receive_channel.types';
|
||||
|
||||
export class AlertReceiveChannelStore extends BaseStore {
|
||||
|
|
@ -132,8 +133,17 @@ export class AlertReceiveChannelStore extends BaseStore {
|
|||
return results;
|
||||
}
|
||||
|
||||
async updatePaginatedItems(query: any = '', page = 1, updateCounters = false, invalidateFn = undefined) {
|
||||
const filters = typeof query === 'string' ? { search: query } : query;
|
||||
async updatePaginatedItems({
|
||||
filters,
|
||||
page = 1,
|
||||
updateCounters = false,
|
||||
invalidateFn = undefined,
|
||||
}: {
|
||||
filters: SupportedIntegrationFilters;
|
||||
page: number;
|
||||
updateCounters: boolean;
|
||||
invalidateFn: () => boolean;
|
||||
}) {
|
||||
const { count, results, page_size } = await makeRequest(this.path, { params: { ...filters, page } });
|
||||
|
||||
if (invalidateFn?.()) {
|
||||
|
|
|
|||
|
|
@ -62,3 +62,11 @@ export interface ContactPoint {
|
|||
contactPoint: string;
|
||||
notificationConnected: boolean;
|
||||
}
|
||||
|
||||
export interface SupportedIntegrationFilters {
|
||||
integration?: string[];
|
||||
integration_ne?: string[];
|
||||
team?: string[];
|
||||
label?: string[];
|
||||
searchTerm?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
width: 40px;
|
||||
}
|
||||
|
||||
.tabsBar {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.integrations-header {
|
||||
margin-bottom: 24px;
|
||||
right: 0;
|
||||
|
|
@ -45,3 +49,7 @@
|
|||
background: var(--cards-background);
|
||||
}
|
||||
}
|
||||
|
||||
.goToDirectPagingAlert {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,18 @@
|
|||
import React from 'react';
|
||||
|
||||
import { LabelTag } from '@grafana/labels';
|
||||
import { HorizontalGroup, Button, VerticalGroup, Icon, ConfirmModal, Tooltip } from '@grafana/ui';
|
||||
import {
|
||||
HorizontalGroup,
|
||||
Button,
|
||||
VerticalGroup,
|
||||
Icon,
|
||||
ConfirmModal,
|
||||
Tooltip,
|
||||
Tab,
|
||||
TabsBar,
|
||||
TabContent,
|
||||
Alert,
|
||||
} from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { observer } from 'mobx-react';
|
||||
|
|
@ -28,7 +39,11 @@ import TeamName from 'containers/TeamName/TeamName';
|
|||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
import { HeartIcon, HeartRedIcon } from 'icons';
|
||||
import { AlertReceiveChannelStore } from 'models/alert_receive_channel/alert_receive_channel';
|
||||
import { AlertReceiveChannel, MaintenanceMode } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import {
|
||||
AlertReceiveChannel,
|
||||
MaintenanceMode,
|
||||
SupportedIntegrationFilters,
|
||||
} from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { LabelKeyValue } from 'models/label/label.types';
|
||||
import IntegrationHelper from 'pages/integration/Integration.helper';
|
||||
import { AppFeature } from 'state/features';
|
||||
|
|
@ -41,11 +56,29 @@ import { PAGE, TEXT_ELLIPSIS_CLASS } from 'utils/consts';
|
|||
|
||||
import styles from './Integrations.module.scss';
|
||||
|
||||
enum TabType {
|
||||
Connections = 'connections',
|
||||
DirectPaging = 'direct-paging',
|
||||
}
|
||||
|
||||
const TAB_QUERY_PARAM_KEY = 'tab';
|
||||
|
||||
const TABS = [
|
||||
{
|
||||
label: 'Connections',
|
||||
value: TabType.Connections,
|
||||
},
|
||||
{
|
||||
label: 'Direct Paging',
|
||||
value: TabType.DirectPaging,
|
||||
},
|
||||
];
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
const FILTERS_DEBOUNCE_MS = 500;
|
||||
|
||||
interface IntegrationsState extends PageBaseState {
|
||||
integrationsFilters: Record<string, any>;
|
||||
integrationsFilters: SupportedIntegrationFilters;
|
||||
alertReceiveChannelId?: AlertReceiveChannel['id'] | 'new';
|
||||
confirmationModal: {
|
||||
isOpen: boolean;
|
||||
|
|
@ -57,6 +90,7 @@ interface IntegrationsState extends PageBaseState {
|
|||
confirmationText?: string;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
activeTab: TabType;
|
||||
}
|
||||
|
||||
interface IntegrationsProps extends WithStoreProps, PageProps, RouteComponentProps<{ id: string }> {}
|
||||
|
|
@ -67,9 +101,10 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
integrationsFilters: { searchTerm: '' },
|
||||
integrationsFilters: { searchTerm: '', integration_ne: ['direct_paging'] },
|
||||
errorData: initErrorDataState(),
|
||||
confirmationModal: undefined,
|
||||
activeTab: props.query[TAB_QUERY_PARAM_KEY] || TabType.Connections,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -81,14 +116,12 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
if (prevProps.match.params.id !== this.props.match.params.id) {
|
||||
this.parseQueryParams();
|
||||
}
|
||||
if (prevProps.query[TAB_QUERY_PARAM_KEY] !== this.props.query[TAB_QUERY_PARAM_KEY]) {
|
||||
this.onTabChange(this.props.query[TAB_QUERY_PARAM_KEY] as TabType);
|
||||
}
|
||||
}
|
||||
|
||||
parseQueryParams = async () => {
|
||||
this.setState((_prevState) => ({
|
||||
errorData: initErrorDataState(),
|
||||
alertReceiveChannelId: undefined,
|
||||
})); // reset state on query parse
|
||||
|
||||
const {
|
||||
store,
|
||||
match: {
|
||||
|
|
@ -96,6 +129,11 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
},
|
||||
} = this.props;
|
||||
|
||||
this.setState((_prevState) => ({
|
||||
errorData: initErrorDataState(),
|
||||
alertReceiveChannelId: undefined,
|
||||
})); // reset state on query parse
|
||||
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -114,24 +152,52 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
}
|
||||
};
|
||||
|
||||
getFiltersBasedOnCurrentTab = () => ({
|
||||
...this.state.integrationsFilters,
|
||||
...(this.state.activeTab === TabType.DirectPaging
|
||||
? { integration: ['direct_paging'] }
|
||||
: {
|
||||
integration_ne: ['direct_paging'],
|
||||
integration: this.state.integrationsFilters.integration?.filter(
|
||||
(integration) => integration !== 'direct_paging'
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
update = () => {
|
||||
const { store } = this.props;
|
||||
const { integrationsFilters } = this.state;
|
||||
const page = store.filtersStore.currentTablePageNum[PAGE.Integrations];
|
||||
|
||||
LocationHelper.update({ p: page }, 'partial');
|
||||
|
||||
return store.alertReceiveChannelStore.updatePaginatedItems(integrationsFilters, page, false, () =>
|
||||
this.invalidateRequestFn(page)
|
||||
return store.alertReceiveChannelStore.updatePaginatedItems({
|
||||
filters: this.getFiltersBasedOnCurrentTab(),
|
||||
page,
|
||||
updateCounters: false,
|
||||
invalidateFn: () => this.invalidateRequestFn(page),
|
||||
});
|
||||
};
|
||||
|
||||
onTabChange = (tab: TabType) => {
|
||||
LocationHelper.update({ tab, integration: undefined, search: undefined }, 'partial');
|
||||
this.setState(
|
||||
{
|
||||
activeTab: tab,
|
||||
},
|
||||
() => {
|
||||
this.handleChangePage(1);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { store, query } = this.props;
|
||||
const { alertReceiveChannelId, confirmationModal } = this.state;
|
||||
const { alertReceiveChannelId, confirmationModal, activeTab, integrationsFilters } = this.state;
|
||||
const { alertReceiveChannelStore } = store;
|
||||
|
||||
const { count, results, page_size } = alertReceiveChannelStore.getPaginatedSearchResult();
|
||||
const isDirectPagingSelectedOnConnectionsTab =
|
||||
activeTab === TabType.Connections && integrationsFilters.integration?.includes('direct_paging');
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -158,27 +224,58 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
</HorizontalGroup>
|
||||
</div>
|
||||
<div>
|
||||
<RemoteFilters
|
||||
query={query}
|
||||
page={PAGE.Integrations}
|
||||
grafanaTeamStore={store.grafanaTeamStore}
|
||||
onChange={this.handleIntegrationsFiltersChange}
|
||||
/>
|
||||
<GTable
|
||||
emptyText={count === undefined ? 'Loading...' : 'No integrations found'}
|
||||
loading={count === undefined}
|
||||
data-testid="integrations-table"
|
||||
rowKey="id"
|
||||
data={results}
|
||||
columns={this.getTableColumns(store.hasFeature.bind(store))}
|
||||
className={cx('integrations-table')}
|
||||
rowClassName={cx('integrations-table-row')}
|
||||
pagination={{
|
||||
page: store.filtersStore.currentTablePageNum[PAGE.Integrations],
|
||||
total: results ? Math.ceil((count || 0) / page_size) : 0,
|
||||
onChange: this.handleChangePage,
|
||||
}}
|
||||
/>
|
||||
<TabsBar className={cx('tabsBar')}>
|
||||
{TABS.map(({ label, value }) => (
|
||||
<Tab
|
||||
key={value}
|
||||
label={label}
|
||||
active={activeTab === value}
|
||||
onChangeTab={() => this.onTabChange(value)}
|
||||
/>
|
||||
))}
|
||||
</TabsBar>
|
||||
<TabContent>
|
||||
<RemoteFilters
|
||||
key={activeTab} // added to remount the component on each tab
|
||||
query={query}
|
||||
page={PAGE.Integrations}
|
||||
grafanaTeamStore={store.grafanaTeamStore}
|
||||
onChange={this.handleIntegrationsFiltersChange}
|
||||
{...(activeTab === TabType.DirectPaging && {
|
||||
skipFilterOptionFn: ({ name }) => name === 'integration',
|
||||
})}
|
||||
/>
|
||||
{isDirectPagingSelectedOnConnectionsTab && (
|
||||
<Alert
|
||||
className={cx('goToDirectPagingAlert')}
|
||||
severity="info"
|
||||
title="Direct Paging integrations have been moved."
|
||||
>
|
||||
<span>
|
||||
They are in a separate tab now. Go to{' '}
|
||||
<PluginLink query={{ page: 'integrations', tab: TabType.DirectPaging }}>
|
||||
Direct Paging tab
|
||||
</PluginLink>{' '}
|
||||
to view them.
|
||||
</span>
|
||||
</Alert>
|
||||
)}
|
||||
<GTable
|
||||
emptyText={count === undefined ? 'Loading...' : 'No integrations found'}
|
||||
loading={count === undefined}
|
||||
data-testid="integrations-table"
|
||||
rowKey="id"
|
||||
data={results}
|
||||
columns={this.getTableColumns(store.hasFeature.bind(store))}
|
||||
className={cx('integrations-table')}
|
||||
rowClassName={cx('integrations-table-row')}
|
||||
pagination={{
|
||||
page: store.filtersStore.currentTablePageNum[PAGE.Integrations],
|
||||
total: results ? Math.ceil((count || 0) / page_size) : 0,
|
||||
onChange: this.handleChangePage,
|
||||
}}
|
||||
/>
|
||||
</TabContent>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -455,6 +552,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
|
||||
getTableColumns = (hasFeatureFn) => {
|
||||
const { grafanaTeamStore, alertReceiveChannelStore } = this.props.store;
|
||||
const isConnectionsTab = this.state.activeTab === TabType.Connections;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
|
@ -476,21 +574,24 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
key: 'datasource',
|
||||
render: (item: AlertReceiveChannel) => this.renderDatasource(item, alertReceiveChannelStore),
|
||||
},
|
||||
...(isConnectionsTab
|
||||
? [
|
||||
{
|
||||
width: '10%',
|
||||
title: 'Maintenance',
|
||||
key: 'maintenance',
|
||||
render: (item: AlertReceiveChannel) => this.renderMaintenance(item),
|
||||
},
|
||||
{
|
||||
width: '5%',
|
||||
title: 'Heartbeat',
|
||||
key: 'heartbeat',
|
||||
render: (item: AlertReceiveChannel) => this.renderHeartbeat(item),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
width: '10%',
|
||||
title: 'Maintenance',
|
||||
key: 'maintenance',
|
||||
render: (item: AlertReceiveChannel) => this.renderMaintenance(item),
|
||||
},
|
||||
{
|
||||
width: '5%',
|
||||
title: 'Heartbeat',
|
||||
key: 'heartbeat',
|
||||
render: (item: AlertReceiveChannel) => this.renderHeartbeat(item),
|
||||
},
|
||||
|
||||
{
|
||||
width: '15%',
|
||||
width: isConnectionsTab ? '15%' : '30%',
|
||||
title: 'Team',
|
||||
render: (item: AlertReceiveChannel) => this.renderTeam(item, grafanaTeamStore.items),
|
||||
},
|
||||
|
|
@ -572,12 +673,15 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
applyFilters = async (isOnMount: boolean) => {
|
||||
const { store } = this.props;
|
||||
const { alertReceiveChannelStore } = store;
|
||||
const { integrationsFilters } = this.state;
|
||||
|
||||
const newPage = isOnMount ? store.filtersStore.currentTablePageNum[PAGE.Integrations] : 1;
|
||||
|
||||
return alertReceiveChannelStore
|
||||
.updatePaginatedItems(integrationsFilters, newPage, false, () => this.invalidateRequestFn(newPage))
|
||||
.updatePaginatedItems({
|
||||
filters: this.getFiltersBasedOnCurrentTab(),
|
||||
page: newPage,
|
||||
updateCounters: false,
|
||||
invalidateFn: () => this.invalidateRequestFn(newPage),
|
||||
})
|
||||
.then(() => {
|
||||
store.filtersStore.currentTablePageNum[PAGE.Integrations] = newPage;
|
||||
LocationHelper.update({ p: newPage }, 'partial');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue