diff --git a/CHANGELOG.md b/CHANGELOG.md index b1a5af5e37..4065fa4cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/engine/apps/api/tests/test_alert_receive_channel.py b/engine/apps/api/tests/test_alert_receive_channel.py index 04003de123..194a564552 100644 --- a/engine/apps/api/tests/test_alert_receive_channel.py +++ b/engine/apps/api/tests/test_alert_receive_channel.py @@ -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", diff --git a/engine/apps/api/views/alert_receive_channel.py b/engine/apps/api/views/alert_receive_channel.py index 14e1a9ce13..aa79da6da3 100644 --- a/engine/apps/api/views/alert_receive_channel.py +++ b/engine/apps/api/views/alert_receive_channel.py @@ -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: diff --git a/grafana-plugin/.eslintrc.js b/grafana-plugin/.eslintrc.js index 92831e8c25..23cbc3abe6 100644 --- a/grafana-plugin/.eslintrc.js +++ b/grafana-plugin/.eslintrc.js @@ -36,6 +36,7 @@ module.exports = { 'newlines-between': 'always', }, ], + 'no-console': ['warn', { allow: ['warn', 'error'] }], 'no-unused-vars': [ 'warn', { diff --git a/grafana-plugin/e2e-tests/integrations/heartbeat.test.ts b/grafana-plugin/e2e-tests/integrations/heartbeat.test.ts index 5670263688..202f1832e3 100644 --- a/grafana-plugin/e2e-tests/integrations/heartbeat.test.ts +++ b/grafana-plugin/e2e-tests/integrations/heartbeat.test.ts @@ -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); diff --git a/grafana-plugin/e2e-tests/integrations/integrationsTable.test.ts b/grafana-plugin/e2e-tests/integrations/integrationsTable.test.ts new file mode 100644 index 0000000000..b2ad52ad75 --- /dev/null +++ b/grafana-plugin/e2e-tests/integrations/integrationsTable.test.ts @@ -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); +}); diff --git a/grafana-plugin/e2e-tests/integrations/maintenanceMode.test.ts b/grafana-plugin/e2e-tests/integrations/maintenanceMode.test.ts index d4066a93aa..88ff3c6ec7 100644 --- a/grafana-plugin/e2e-tests/integrations/maintenanceMode.test.ts +++ b/grafana-plugin/e2e-tests/integrations/maintenanceMode.test.ts @@ -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); diff --git a/grafana-plugin/e2e-tests/utils/escalationChain.ts b/grafana-plugin/e2e-tests/utils/escalationChain.ts index c24c5afb8d..74115d435a 100644 --- a/grafana-plugin/e2e-tests/utils/escalationChain.ts +++ b/grafana-plugin/e2e-tests/utils/escalationChain.ts @@ -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); + } } }; diff --git a/grafana-plugin/e2e-tests/utils/integrations.ts b/grafana-plugin/e2e-tests/utils/integrations.ts index 8c91598e1b..4a45211f97 100644 --- a/grafana-plugin/e2e-tests/utils/integrations.ts +++ b/grafana-plugin/e2e-tests/utils/integrations.ts @@ -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 => { - // 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 => { +export const createIntegration = async ({ + page, + integrationName = `integration-${generateRandomValue()}`, + integrationSearchText = 'Webhook', + shouldGoToIntegrationsPage = true, +}: { + page: Page; + integrationName?: string; + integrationSearchText?: string; + shouldGoToIntegrationsPage?: boolean; +}): Promise => { + 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 => { - await createIntegration(page, integrationName); + await createIntegration({ page, integrationName }); await assignEscalationChainToIntegration(page, escalationChainName); await sendDemoAlert(page); }; diff --git a/grafana-plugin/package.json b/grafana-plugin/package.json index e3b7d1c1e3..a93cb29bdd 100644 --- a/grafana-plugin/package.json +++ b/grafana-plugin/package.json @@ -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", diff --git a/grafana-plugin/src/containers/Labels/LabelsFilter.tsx b/grafana-plugin/src/containers/Labels/LabelsFilter.tsx index 5c09998759..667630c99b 100644 --- a/grafana-plugin/src/containers/Labels/LabelsFilter.tsx +++ b/grafana-plugin/src/containers/Labels/LabelsFilter.tsx @@ -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) => { diff --git a/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx b/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx index 936ec10cab..13be43ab11 100644 --- a/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx +++ b/grafana-plugin/src/containers/RemoteFilters/RemoteFilters.tsx @@ -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 { 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); diff --git a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts index 67afa9e6f0..972dccb69e 100644 --- a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts +++ b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.ts @@ -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?.()) { diff --git a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts index 81c1940f10..ebb2fbba36 100644 --- a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts +++ b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts @@ -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; +} diff --git a/grafana-plugin/src/pages/integrations/Integrations.module.scss b/grafana-plugin/src/pages/integrations/Integrations.module.scss index a2d942f2e6..69f57cad40 100644 --- a/grafana-plugin/src/pages/integrations/Integrations.module.scss +++ b/grafana-plugin/src/pages/integrations/Integrations.module.scss @@ -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; +} diff --git a/grafana-plugin/src/pages/integrations/Integrations.tsx b/grafana-plugin/src/pages/integrations/Integrations.tsx index d67af53071..205d4eeba2 100644 --- a/grafana-plugin/src/pages/integrations/Integrations.tsx +++ b/grafana-plugin/src/pages/integrations/Integrations.tsx @@ -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; + 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 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 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 }, } = 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 } }; + 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
- - + + {TABS.map(({ label, value }) => ( + this.onTabChange(value)} + /> + ))} + + + name === 'integration', + })} + /> + {isDirectPagingSelectedOnConnectionsTab && ( + + + They are in a separate tab now. Go to{' '} + + Direct Paging tab + {' '} + to view them. + + + )} + +
@@ -455,6 +552,7 @@ class Integrations extends React.Component 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 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 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');