diff --git a/packages/manager/.changeset/pr-12671-upcoming-features-1754967230297.md b/packages/manager/.changeset/pr-12671-upcoming-features-1754967230297.md new file mode 100644 index 00000000000..9cee6408b07 --- /dev/null +++ b/packages/manager/.changeset/pr-12671-upcoming-features-1754967230297.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +CloudPulse: Add new flag - 'aclpServices', filter services at `CloudPulseDashboardSelect.tsx`, `AlertListing.tsx`, `ServiceTypeSelect.tsx` ([#12671](https://github.com/linode/manager/pull/12671)) diff --git a/packages/manager/cypress/e2e/core/cloudpulse/aclp-support.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/aclp-support.spec.ts index d8f57e7ddaa..a1effa63ac8 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/aclp-support.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/aclp-support.spec.ts @@ -32,10 +32,10 @@ import type { Stats } from '@linode/api-v4'; describe('ACLP Components UI varies according to ACLP support by region and user preference', function () { beforeEach(function () { mockAppendFeatureFlags({ - aclpBetaServices: { + aclpServices: { linode: { - alerts: false, - metrics: true, + alerts: { beta: false, enabled: false }, + metrics: { beta: true, enabled: true }, }, }, }).as('getFeatureFlags'); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/alert-errors.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/alert-errors.spec.ts index 328620c8cf5..6b986d80cb5 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/alert-errors.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/alert-errors.spec.ts @@ -7,11 +7,7 @@ import { import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { ui } from 'support/ui'; -import { accountFactory, alertFactory } from 'src/factories'; - -import type { Flags } from 'src/featureFlags'; - -const flags: Partial = { aclp: { beta: true, enabled: true } }; +import { accountFactory, alertFactory, flagsFactory } from 'src/factories'; const mockAccount = accountFactory.build(); const mockAlerts = [ alertFactory.build({ @@ -39,7 +35,7 @@ describe('Alerts Listing Page - Error Handling', () => { * - Confirms that the UI does not reflect a successful state change if the request fails. */ beforeEach(() => { - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); mockGetCloudPulseServices(['linode', 'dbaas']); mockGetAllAlertDefinitions(mockAlerts).as('getAlertDefinitionsList'); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/alerts-listing-page.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/alerts-listing-page.spec.ts index a31796a0fab..eebafe4b5ea 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/alerts-listing-page.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/alerts-listing-page.spec.ts @@ -15,7 +15,12 @@ import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { mockGetProfile } from 'support/intercepts/profile'; import { ui } from 'support/ui'; -import { accountFactory, alertFactory, alertRulesFactory } from 'src/factories'; +import { + accountFactory, + alertFactory, + alertRulesFactory, + flagsFactory, +} from 'src/factories'; import { alertLimitMessage, alertToolTipText, @@ -33,19 +38,11 @@ import type { AlertStatusType, CloudPulseServiceType, } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; const alertDefinitionsUrl = '/alerts/definitions'; const mockProfile = profileFactory.build({ timezone: 'gmt', }); -const flags: Partial = { - aclp: { beta: true, enabled: true }, - aclpBetaServices: { - dbaas: { metrics: true, alerts: true }, - linode: { metrics: true, alerts: true }, - }, -}; const mockAccount = accountFactory.build(); const now = new Date(); const mockAlerts = [ @@ -215,7 +212,7 @@ describe('Integration Tests for CloudPulse Alerts Listing Page', () => { * - Ensures API calls return correct responses and status codes. */ beforeEach(() => { - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); mockGetProfile(mockProfile); mockGetCloudPulseServices(['linode', 'dbaas']); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/alerts-service-ld-flags.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/alerts-service-ld-flags.spec.ts new file mode 100644 index 00000000000..1c0a8b6cb9f --- /dev/null +++ b/packages/manager/cypress/e2e/core/cloudpulse/alerts-service-ld-flags.spec.ts @@ -0,0 +1,167 @@ +/** + * @file Integration tests for feature flag behavior on the alert page. + */ +import { widgetDetails } from 'support/constants/widgets'; +import { mockGetAccount } from 'support/intercepts/account'; +import { + mockGetCloudPulseMetricDefinitions, + mockGetCloudPulseServices, +} from 'support/intercepts/cloudpulse'; +import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetUserPreferences } from 'support/intercepts/profile'; +import { ui } from 'support/ui'; + +import { + accountFactory, + dashboardMetricFactory, + flagsFactory, +} from 'src/factories'; +/** + * This test ensures that widget titles are displayed correctly on the dashboard. + * This test suite is dedicated to verifying the functionality and display of widgets on the Cloudpulse dashboard. + * It includes: + * Validating that widgets are correctly loaded and displayed. + * Ensuring that widget titles and data match the expected values. + * Verifying that widget settings, such as granularity and aggregation, are applied correctly. + * Testing widget interactions, including zooming and filtering, to ensure proper behavior. + * Each test ensures that widgets on the dashboard operate correctly and display accurate information. + */ + +const { metrics } = widgetDetails.linode; +const serviceType = 'linode'; + +const metricDefinitions = metrics.map(({ name, title, unit }) => + dashboardMetricFactory.build({ + label: title, + metric: name, + unit, + }) +); + +const mockAccount = accountFactory.build(); +const CREATE_ALERT_PAGE_URL = '/alerts/definitions/create'; +const NO_OPTIONS_TEXT = 'You have no options to choose from'; + +describe('Linode ACLP Metrics and Alerts Flag Behavior', () => { + beforeEach(() => { + mockGetAccount(mockAccount); // Enables the account to have capability for Akamai Cloud Pulse + mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions); + mockGetCloudPulseServices([serviceType]).as('fetchServices'); + mockGetUserPreferences({}); + }); + + it('should show Linode with beta tag in Service dropdown on Alert page when alerts.beta is true', () => { + mockAppendFeatureFlags(flagsFactory.build()); + cy.visitWithLogin(CREATE_ALERT_PAGE_URL); + ui.autocomplete.findByLabel('Service').as('serviceInput'); + cy.get('@serviceInput').click(); + + cy.get('[data-qa-id="linode"]') + .should('have.text', 'Linode') + .parent() + .as('linodeBetaServiceOption'); + + cy.get('@linodeBetaServiceOption') + .find('[data-testid="betaChip"]') + .should('be.visible') + .and('have.text', 'beta'); + + cy.get('@serviceInput').should('be.visible').type('Linode'); + ui.autocompletePopper.findByTitle('Linode').should('be.visible').click(); + }); + it('should exclude Linode beta in Service dropdown when alerts.beta is false', () => { + // Mock feature flags with alerts beta disabled + const mockflags = flagsFactory.build({ + aclpServices: { + linode: { + alerts: { beta: false, enabled: false }, + }, + }, + }); + + mockAppendFeatureFlags(mockflags); + + // Visit the alert creation page + cy.visitWithLogin(CREATE_ALERT_PAGE_URL); + + // Click the Service dropdown + ui.autocomplete.findByLabel('Service').as('serviceInput'); + cy.get('@serviceInput').click(); + + // Assert dropdown behavior + cy.get('[data-qa-autocomplete-popper]') + .should('be.visible') + .and('have.text', NO_OPTIONS_TEXT) + .and('not.contain.text', 'Linode beta'); + }); + + it('should show no available services in the Service dropdown when Linode alerts are disabled but beta is true', () => { + const mockflags = flagsFactory.build({ + aclpServices: { + linode: { + alerts: { beta: true, enabled: false }, + }, + }, + }); + + mockAppendFeatureFlags(mockflags); + // Visit the alert creation page + cy.visitWithLogin(CREATE_ALERT_PAGE_URL); + // Click the Service dropdown + ui.autocomplete.findByLabel('Service').as('serviceInput'); + cy.get('@serviceInput').click(); + + // ---------- Assert ---------- + cy.get('[data-qa-autocomplete-popper]') + .should('be.visible') + .and('have.text', NO_OPTIONS_TEXT) + .and('not.contain.text', 'Linode beta'); + }); + + it('should show no options and exclude Linode beta in Service dropdown when alerts are disabled but beta is true', () => { + const mockflags = flagsFactory.build({ + aclpServices: { + linode: { + alerts: { beta: true, enabled: false }, + }, + }, + }); + + mockAppendFeatureFlags(mockflags); + // Visit the alert creation page + cy.visitWithLogin(CREATE_ALERT_PAGE_URL); + // Click the Service dropdown + ui.autocomplete.findByLabel('Service').as('serviceInput'); + cy.get('@serviceInput').click(); + + // ---------- Assert ---------- + cy.get('[data-qa-autocomplete-popper]') + .should('be.visible') + .and('contain.text', 'You have no options to choose from') + .and('not.contain.text', 'Linode beta'); + }); + + it('should show Linode without beta tag in Service dropdown when alerts are enabled but not in beta', () => { + const mockflags = flagsFactory.build({ + aclpServices: { + linode: { + alerts: { beta: false, enabled: true }, + }, + }, + }); + mockAppendFeatureFlags(mockflags); + cy.visitWithLogin(CREATE_ALERT_PAGE_URL); + ui.autocomplete.findByLabel('Service').as('serviceInput'); + cy.get('@serviceInput').click(); + + // ---------- Assert ---------- + cy.get('[data-qa-id="linode"]') + .should('have.text', 'Linode') + .parent() + .as('linodeBetaServiceOption'); + + cy.get('@linodeBetaServiceOption') + .find('[data-testid="betaChip"]') + .should('not.exist'); + }); +}); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/cloudpulse-dashboard-errors.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/cloudpulse-dashboard-errors.spec.ts index 639cb31deb3..1ced6eaa60d 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/cloudpulse-dashboard-errors.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/cloudpulse-dashboard-errors.spec.ts @@ -33,11 +33,11 @@ import { dashboardFactory, dashboardMetricFactory, databaseFactory, + flagsFactory, widgetFactory, } from 'src/factories'; import type { Database } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; /** * Verifies the presence and values of specific properties within the aclpPreference object @@ -47,22 +47,6 @@ import type { Flags } from 'src/featureFlags'; * @param requestPayload - The payload received from the request, containing the aclpPreference object. * @param expectedValues - An object containing the expected values for properties to validate against the requestPayload. */ - -const flags: Partial = { - aclp: { beta: true, enabled: true }, - aclpResourceTypeMap: [ - { - dimensionKey: 'LINODE_ID', - maxResourceSelections: 10, - serviceType: 'linode', - }, - { - dimensionKey: 'cluster_id', - maxResourceSelections: 10, - serviceType: 'dbaas', - }, - ], -}; const { clusterName, dashboardName, engine, id, metrics, nodeType } = widgetDetails.dbaas; const serviceType = 'dbaas'; @@ -132,7 +116,7 @@ const mockAccount = accountFactory.build(); describe('Tests for API error handling', () => { beforeEach(() => { - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions); mockGetCloudPulseDashboards(serviceType, [dashboard]).as('fetchDashboard'); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/create-user-alert.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/create-user-alert.spec.ts index 2da9c95684e..82b1f5fd4a4 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/create-user-alert.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/create-user-alert.spec.ts @@ -26,15 +26,13 @@ import { cpuRulesFactory, dashboardMetricFactory, databaseFactory, + flagsFactory, memoryRulesFactory, notificationChannelFactory, triggerConditionFactory, } from 'src/factories'; import { CREATE_ALERT_SUCCESS_MESSAGE } from 'src/features/CloudPulse/Alerts/constants'; import { formatDate } from 'src/utilities/formatDate'; - -import type { Flags } from 'src/featureFlags'; - export interface MetricDetails { aggregationType: string; dataField: string; @@ -43,8 +41,6 @@ export interface MetricDetails { threshold: string; } -const flags: Partial = { aclp: { beta: true, enabled: true } }; - // Create mock data const mockAccount = accountFactory.build(); const mockRegions = [ @@ -176,7 +172,7 @@ describe('Create Alert', () => { * - Confirms that the UI displays a success message after creating an alert. */ beforeEach(() => { - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); mockGetProfile(mockProfile); mockGetCloudPulseServices([serviceType]); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/dbaas-widgets-verification.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/dbaas-widgets-verification.spec.ts index ce1c4c76183..7302cfb398e 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/dbaas-widgets-verification.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/dbaas-widgets-verification.spec.ts @@ -27,6 +27,7 @@ import { dashboardMetricFactory, databaseFactory, dimensionFilterFactory, + flagsFactory, kubeLinodeFactory, widgetFactory, } from 'src/factories'; @@ -40,7 +41,6 @@ import type { DimensionFilter, Widgets, } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; import type { Interception } from 'support/cypress-exports'; /** @@ -55,23 +55,6 @@ import type { Interception } from 'support/cypress-exports'; */ const expectedGranularityArray = ['Auto', '1 day', '1 hr', '5 min']; const timeDurationToSelect = 'Last 24 Hours'; - -const flags: Partial = { - aclp: { beta: true, enabled: true }, - aclpResourceTypeMap: [ - { - dimensionKey: 'LINODE_ID', - maxResourceSelections: 10, - serviceType: 'linode', - }, - { - dimensionKey: 'cluster_id', - maxResourceSelections: 10, - serviceType: 'dbaas', - }, - ], -}; - const { clusterName, dashboardName, engine, id, metrics, nodeType } = widgetDetails.dbaas; const serviceType = 'dbaas'; @@ -199,7 +182,7 @@ const validateWidgetFilters = (widget: Widgets) => { describe('Integration Tests for DBaaS Dashboard ', () => { beforeEach(() => { - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); // Enables the account to have capability for Akamai Cloud Pulse mockGetLinodes([mockLinode]); mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/edit-system-alert.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/edit-system-alert.spec.ts index f24a05fb99c..fc99e460e83 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/edit-system-alert.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/edit-system-alert.spec.ts @@ -16,12 +16,14 @@ import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { mockGetRegions } from 'support/intercepts/regions'; import { ui } from 'support/ui'; -import { accountFactory, alertFactory, databaseFactory } from 'src/factories'; +import { + accountFactory, + alertFactory, + databaseFactory, + flagsFactory, +} from 'src/factories'; import type { Alert, Database } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; - -const flags: Partial = { aclp: { beta: true, enabled: true } }; const expectedResourceIds = Array.from({ length: 50 }, (_, i) => String(i + 1)); const mockAccount = accountFactory.build(); @@ -69,7 +71,7 @@ describe('Integration Tests for Edit Alert', () => { * - Confirms that after submitting, the data matches with the API response. */ beforeEach(() => { - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); mockGetRegions(regions); mockGetAllAlertDefinitions([alertDetails]).as('getAlertDefinitionsList'); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/edit-user-alert.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/edit-user-alert.spec.ts index 1b55b520162..3ab96ff6578 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/edit-user-alert.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/edit-user-alert.spec.ts @@ -35,6 +35,7 @@ import { cpuRulesFactory, dashboardMetricFactory, databaseFactory, + flagsFactory, memoryRulesFactory, notificationChannelFactory, triggerConditionFactory, @@ -43,10 +44,7 @@ import { UPDATE_ALERT_SUCCESS_MESSAGE } from 'src/features/CloudPulse/Alerts/con import { formatDate } from 'src/utilities/formatDate'; import type { Database } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; -// Feature flag setup -const flags: Partial = { aclp: { beta: true, enabled: true } }; const mockAccount = accountFactory.build(); // Mock alert details @@ -127,7 +125,7 @@ describe('Integration Tests for Edit Alert', () => { */ beforeEach(() => { // Mocking various API responses - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); mockGetProfile(mockProfile); mockGetRegions(regions); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/feature-flag-disabled.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/feature-flag-disabled.spec.ts index b61ec199da9..d25dfba0a9a 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/feature-flag-disabled.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/feature-flag-disabled.spec.ts @@ -8,13 +8,13 @@ import { randomLabel, randomNumber } from 'support/util/random'; import type { UserPreferences } from '@linode/api-v4'; -describe('User preferences for alerts and metrics have no effect when aclpBetaServices alerts/metrics feature flag is disabled', () => { +describe('User preferences for alerts and metrics have no effect when aclpServices alerts/metrics feature flag is disabled', () => { beforeEach(() => { mockAppendFeatureFlags({ - aclpBetaServices: { + aclpServices: { linode: { - alerts: false, - metrics: false, + alerts: { beta: false, enabled: false }, + metrics: { beta: false, enabled: false }, }, }, }).as('getFeatureFlags'); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/groupby-tags.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/groupby-tags.spec.ts index b6c012f4dc6..e6eb5094c01 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/groupby-tags.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/groupby-tags.spec.ts @@ -13,16 +13,13 @@ import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { mockGetUserPreferences } from 'support/intercepts/profile'; import { ui } from 'support/ui'; -import { accountFactory, alertFactory } from 'src/factories'; +import { accountFactory, alertFactory, flagsFactory } from 'src/factories'; import type { Alert, AlertStatusType, CloudPulseServiceType, } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; - -const flags: Partial = { aclp: { beta: true, enabled: true } }; const mockAccount = accountFactory.build(); const statusList: AlertStatusType[] = [ @@ -89,7 +86,7 @@ describe('Integration Tests for Grouping Alerts by Tags on the CloudPulse Alerts */ it('Displays alerts accurately grouped under their corresponding tags', () => { // Setup necessary mocks and feature flags - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); mockGetCloudPulseServices(serviceTypes); mockGetAllAlertDefinitions(mockAlerts).as('getAlertDefinitionsList'); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/linode-widget-verification.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/linode-widget-verification.spec.ts index 350261e517c..8e2b98211fa 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/linode-widget-verification.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/linode-widget-verification.spec.ts @@ -24,6 +24,7 @@ import { cloudPulseMetricsResponseFactory, dashboardFactory, dashboardMetricFactory, + flagsFactory, kubeLinodeFactory, widgetFactory, } from 'src/factories'; @@ -31,7 +32,6 @@ import { generateGraphData } from 'src/features/CloudPulse/Utils/CloudPulseWidge import { formatToolTip } from 'src/features/CloudPulse/Utils/unitConversion'; import type { CloudPulseMetricsResponse } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; import type { Interception } from 'support/cypress-exports'; /** @@ -46,21 +46,6 @@ import type { Interception } from 'support/cypress-exports'; */ const expectedGranularityArray = ['Auto', '1 day', '1 hr', '5 min']; const timeDurationToSelect = 'Last 24 Hours'; -const flags: Partial = { - aclp: { beta: true, enabled: true }, - aclpResourceTypeMap: [ - { - dimensionKey: 'LINODE_ID', - maxResourceSelections: 10, - serviceType: 'linode', - }, - { - dimensionKey: 'cluster_id', - maxResourceSelections: 10, - serviceType: 'dbaas', - }, - ], -}; const { dashboardName, id, metrics, region, resource } = widgetDetails.linode; const serviceType = 'linode'; const dashboard = dashboardFactory.build({ @@ -159,7 +144,7 @@ const getWidgetLegendRowValuesFromResponse = ( describe('Integration Tests for Linode Dashboard ', () => { beforeEach(() => { - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); // Enables the account to have capability for Akamai Cloud Pulse mockGetLinodes([mockLinode]); mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/metrics-service-ld-flags.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/metrics-service-ld-flags.spec.ts new file mode 100644 index 00000000000..82a3548e5ed --- /dev/null +++ b/packages/manager/cypress/e2e/core/cloudpulse/metrics-service-ld-flags.spec.ts @@ -0,0 +1,317 @@ +/** + * @file Integration tests for feature flag behavior on the Metrics page. + */ +import { widgetDetails } from 'support/constants/widgets'; +import { mockGetAccount } from 'support/intercepts/account'; +import { + mockGetCloudPulseDashboard, + mockGetCloudPulseDashboards, + mockGetCloudPulseMetricDefinitions, + mockGetCloudPulseServices, +} from 'support/intercepts/cloudpulse'; +import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetUserPreferences } from 'support/intercepts/profile'; +import { ui } from 'support/ui'; + +import { + accountFactory, + dashboardFactory, + dashboardMetricFactory, + flagsFactory, + widgetFactory, +} from 'src/factories'; + +import type { Flags } from 'src/featureFlags'; + +/** + * This test ensures that widget titles are displayed correctly on the dashboard. + * This test suite is dedicated to verifying the functionality and display of widgets on the Cloudpulse dashboard. + * It includes: + * Validating that widgets are correctly loaded and displayed. + * Ensuring that widget titles and data match the expected values. + * Verifying that widget settings, such as granularity and aggregation, are applied correctly. + * Testing widget interactions, including zooming and filtering, to ensure proper behavior. + * Each test ensures that widgets on the dashboard operate correctly and display accurate information. + */ + +const { dashboardName, id, metrics } = widgetDetails.linode; +const serviceType = 'linode'; +const dashboard = dashboardFactory.build({ + label: dashboardName, + service_type: serviceType, + widgets: metrics.map(({ name, title, unit, yLabel }) => { + return widgetFactory.build({ + label: title, + metric: name, + unit, + y_label: yLabel, + }); + }), +}); + +const metricDefinitions = metrics.map(({ name, title, unit }) => + dashboardMetricFactory.build({ + label: title, + metric: name, + unit, + }) +); +const mockAccount = accountFactory.build(); + +describe('Linode ACLP Metrics and Alerts Flag Behavior', () => { + /* + * - Mocks ACLP feature flags dynamically to simulate various flag combinations for Linode services. + * + * - Validates visibility of "Linode" dashboard option in Metrics dropdown based on: + * - Presence of `aclpServices.linode.metrics` flag. + * - Enabled and beta states under `metrics` and `alerts` keys. + * + * - Ensures correct rendering behavior: + * - "Linode" option should appear only when `metrics.enabled` is true. + * - Beta chip should appear only when `metrics.beta` is also true. + * - Linode should not appear if `metrics` flag is missing, disabled, or malformed. + * + * - Asserts "no options" message is shown when Linode dashboard is not available. + * + * - Uses Cypress commands to: + * - Visit Metrics page after login. + * - Interact with autocomplete dropdown and select dashboards. + * - Validate presence/absence of beta chip (`[data-testid="betaChip"]`). + * + * - Improves test coverage for conditional UI behavior tied to feature flag configurations. + * - Supports staged rollout testing and toggling of experimental dashboard features. + */ + + beforeEach(() => { + mockGetAccount(mockAccount); // Enables the account to have capability for Akamai Cloud Pulse + mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions); + mockGetCloudPulseServices([serviceType]).as('fetchServices'); + mockGetUserPreferences({}); + }); + it('should display "Linode" with a beta tag in the Service dropdown on the Metrics page when metrics.beta is enabled and the service is enabled', () => { + mockAppendFeatureFlags(flagsFactory.build()); + mockGetCloudPulseDashboard(id, dashboard); + mockGetCloudPulseDashboards(serviceType, [dashboard]).as('fetchDashboard'); + + // navigate to the metrics page + cy.visitWithLogin('/metrics'); + + ui.autocomplete.findByLabel('Dashboard').as('dashboardInput'); + + // Click using the alias + cy.get('@dashboardInput').click(); + + cy.get('[data-qa-id="linode"]') // Selects the Linode label + .should('have.text', 'Linode') + .parent() // Moves up to the
  • containing both label and chip + .as('linodeBetaServiceOption'); // Alias for reuse + + cy.get('@linodeBetaServiceOption') + .find('[data-testid="betaChip"]') + .should('be.visible') + .and('have.text', 'beta'); + + ui.autocomplete + .findByLabel('Dashboard') + .should('be.visible') + .type(dashboardName); + + ui.autocompletePopper + .findByTitle(dashboardName) + .should('have.text', dashboardName) + .click(); + }); + + it('should display "Linode" without a beta tag in the Service dropdown on the Metrics page when metrics.beta is false and the service is enabled', () => { + const mockflags = flagsFactory.build({ + aclpServices: { + linode: { + metrics: { beta: false, enabled: true }, + }, + }, + }); + + // Apply mock flags + mockAppendFeatureFlags(mockflags); + mockGetCloudPulseDashboard(id, dashboard); + mockGetCloudPulseDashboards(serviceType, [dashboard]).as('fetchDashboard'); + + // Visit the Metrics page + cy.visitWithLogin('/metrics'); + + // Locate and open the Dashboard dropdown + ui.autocomplete.findByLabel('Dashboard').as('dashboardInput'); + cy.get('@dashboardInput').click(); + + // Verify "Linode" is present without a beta chip + ui.autocompletePopper + .findByTitle('Linode') + .should('be.visible') + .within(() => { + cy.get('[data-testid="betaChip"]').should('not.exist'); + }); + + // Select the dashboard + cy.get('@dashboardInput').should('be.visible').type(dashboardName); + + ui.autocompletePopper + .findByTitle(dashboardName) + .should('have.text', dashboardName) + .click(); + }); + + it('should display "Linode" with a beta tag in the Service dropdown on the Metrics page when metrics.beta is true and enabled is false', () => { + // Mock the feature flags to disable metrics for Linode + + const mockflags = flagsFactory.build({ + aclpServices: { + linode: { + metrics: { beta: true, enabled: false }, + }, + }, + }); + // Apply the mock feature flags + mockAppendFeatureFlags(mockflags); + mockGetCloudPulseDashboard(id, dashboard); + mockGetCloudPulseDashboards(serviceType, [dashboard]).as('fetchDashboard'); + + // Visit the Metrics page after login + + cy.visitWithLogin('/metrics'); + + ui.autocomplete.findByLabel('Dashboard').click(); + + // Verify the autocomplete dropdown is visible and contains the "no options" message + + cy.get('[data-qa-autocomplete-popper]') + .should('be.visible') + .and('contain.text', 'You have no options to choose from') + .within(() => { + // Assert that "Linode" does not appear in the dropdown + + cy.contains('Linode').should('not.exist'); + // Assert that the beta chip is not rendered + + cy.get('[data-testid="betaChip"]').should('not.exist'); + }); + }); + + it('should not display "Linode" when its feature flag is missing', () => { + // Mock the feature flags without linode under aclpServices + const flags = { + aclp: { beta: true, enabled: true }, + aclpServices: { linode: {} }, + } as unknown as Partial; + mockAppendFeatureFlags(flags); + // Visit the Metrics page after login + cy.visitWithLogin('/metrics'); + // Open the dashboard autocomplete dropdown + ui.autocomplete.findByLabel('Dashboard').as('dashboardInput'); + cy.get('@dashboardInput').click(); + + // Verify the dropdown is visible and shows "no options" + cy.get('[data-qa-autocomplete-popper]') + .should('be.visible') + .and('contain.text', 'You have no options to choose from') + .within(() => { + // Assert "Linode" is not shown + cy.contains('Linode').should('not.exist'); + + // Assert no beta chip is visible + cy.get('[data-testid="betaChip"]').should('not.exist'); + }); + }); + + it('should not display "Linode" with a beta tag in the Service dropdown on the Metrics page when metrics.beta is false and the service is not enabled', () => { + // Mock the feature flags to disable metrics for Linode + const mockflags = flagsFactory.build({ + aclpServices: { + linode: { + metrics: { beta: false, enabled: false }, + }, + }, + }); + // Apply the mock feature flags + mockAppendFeatureFlags(mockflags); + // Visit the Metrics page after login + + cy.visitWithLogin('/metrics'); + + ui.autocomplete.findByLabel('Dashboard').click(); + + // Verify the autocomplete dropdown is visible and contains the "no options" message + + cy.get('[data-qa-autocomplete-popper]') + .should('be.visible') + .and('contain.text', 'You have no options to choose from') + .within(() => { + // Assert that "Linode" does not appear in the dropdown + + cy.contains('Linode').should('not.exist'); + // Assert that the beta chip is not rendered + + cy.get('[data-testid="betaChip"]').should('not.exist'); + }); + }); + + it('should show no service options when aclpServices flag is missing', () => { + // Mock the feature flags without linode under aclpServices + const flags = { + aclp: { beta: true, enabled: true }, + } as unknown as Partial; + mockAppendFeatureFlags(flags); + // Visit the Metrics page after login + cy.visitWithLogin('/metrics'); + // Open the dashboard autocomplete dropdown + ui.autocomplete.findByLabel('Dashboard').as('dashboardInput'); + cy.get('@dashboardInput').click(); + + // Verify the dropdown is visible and shows "no options" + cy.get('[data-qa-autocomplete-popper]') + .should('be.visible') + .and('contain.text', 'You have no options to choose from') + .within(() => { + // Assert "Linode" is not shown + cy.contains('Linode').should('not.exist'); + + // Assert no beta chip is visible + cy.get('[data-testid="betaChip"]').should('not.exist'); + }); + }); + + it('should not show Linode in Dashboard dropdown when metrics flags are missing and service is not enabled', () => { + // Mock the feature flags to disable metrics for Linode + + const mockflags = flagsFactory.build({ + aclpServices: { + linode: { + metrics: { enabled: false }, + }, + }, + }); + // Apply the mock feature flags + mockAppendFeatureFlags(mockflags); + mockGetCloudPulseDashboard(id, dashboard); + mockGetCloudPulseDashboards(serviceType, [dashboard]).as('fetchDashboard'); + + // Visit the Metrics page after login + + cy.visitWithLogin('/metrics'); + + ui.autocomplete.findByLabel('Dashboard').click(); + + // Verify the autocomplete dropdown is visible and contains the "no options" message + + cy.get('[data-qa-autocomplete-popper]') + .should('be.visible') + .and('contain.text', 'You have no options to choose from') + .within(() => { + // Assert that "Linode" does not appear in the dropdown + + cy.contains('Linode').should('not.exist'); + // Assert that the beta chip is not rendered + + cy.get('[data-testid="betaChip"]').should('not.exist'); + }); + }); +}); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/nodebalancer-widget-verification.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/nodebalancer-widget-verification.spec.ts index e716a115ccd..3f253e78fce 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/nodebalancer-widget-verification.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/nodebalancer-widget-verification.spec.ts @@ -30,6 +30,7 @@ import { cloudPulseMetricsResponseFactory, dashboardFactory, dashboardMetricFactory, + flagsFactory, kubeLinodeFactory, widgetFactory, } from 'src/factories'; @@ -37,7 +38,6 @@ import { generateGraphData } from 'src/features/CloudPulse/Utils/CloudPulseWidge import { formatToolTip } from 'src/features/CloudPulse/Utils/unitConversion'; import type { CloudPulseMetricsResponse } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; import type { Interception } from 'support/cypress-exports'; /** * This test ensures that widget titles are displayed correctly on the dashboard. @@ -51,27 +51,6 @@ import type { Interception } from 'support/cypress-exports'; */ const expectedGranularityArray = ['Auto', '1 day', '1 hr', '5 min']; const timeDurationToSelect = 'Last 24 Hours'; -const flags: Partial = { - aclp: { beta: true, enabled: true }, - aclpBetaServices: { nodebalancer: { alerts: true, metrics: true } }, - aclpResourceTypeMap: [ - { - dimensionKey: 'LINODE_ID', - maxResourceSelections: 10, - serviceType: 'linode', - }, - { - dimensionKey: 'cluster_id', - maxResourceSelections: 10, - serviceType: 'dbaas', - }, - { - dimensionKey: 'cluster_id', - maxResourceSelections: 10, - serviceType: 'nodebalancer', - }, - ], -}; const { dashboardName, id, metrics, region, resource } = widgetDetails.nodebalancer; const serviceType = 'nodebalancer'; @@ -167,9 +146,8 @@ const mockNodeBalancer = nodeBalancerFactory.build({ // Tests will be modified describe('Integration Tests for Nodebalancer Dashboard ', () => { beforeEach(() => { - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(accountFactory.build({})); - mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions); mockGetCloudPulseDashboards(serviceType, [dashboard]).as('fetchDashboard'); mockGetCloudPulseServices([serviceType]).as('fetchServices'); diff --git a/packages/manager/cypress/e2e/core/cloudpulse/timerange-verification.spec.ts b/packages/manager/cypress/e2e/core/cloudpulse/timerange-verification.spec.ts index 3b88670e2d5..0c5dc680376 100644 --- a/packages/manager/cypress/e2e/core/cloudpulse/timerange-verification.spec.ts +++ b/packages/manager/cypress/e2e/core/cloudpulse/timerange-verification.spec.ts @@ -30,12 +30,12 @@ import { dashboardFactory, dashboardMetricFactory, databaseFactory, + flagsFactory, widgetFactory, } from 'src/factories'; import { formatDate } from 'src/utilities/formatDate'; import type { Database, DateTimeWithPreset } from '@linode/api-v4'; -import type { Flags } from 'src/featureFlags'; import type { Interception } from 'support/cypress-exports'; const formatter = "yyyy-MM-dd'T'HH:mm:ss'Z'"; @@ -59,17 +59,6 @@ const mockRegion = regionFactory.build({ }, }); -const flags: Partial = { - aclp: { beta: true, enabled: true }, - aclpResourceTypeMap: [ - { - dimensionKey: 'cluster_id', - maxResourceSelections: 10, - serviceType: 'dbaas', - }, - ], -}; - const { dashboardName, engine, id, metrics } = widgetDetails.dbaas; const serviceType = 'dbaas'; const dashboard = dashboardFactory.build({ @@ -212,8 +201,7 @@ describe('Integration tests for verifying Cloudpulse custom and preset configura */ beforeEach(() => { - cy.viewport(1280, 720); - mockAppendFeatureFlags(flags); + mockAppendFeatureFlags(flagsFactory.build()); mockGetAccount(mockAccount); mockGetCloudPulseMetricDefinitions(serviceType, metricDefinitions.data); mockGetCloudPulseDashboards(serviceType, [dashboard]).as('fetchDashboard'); diff --git a/packages/manager/cypress/e2e/core/linodes/alerts-create.spec.ts b/packages/manager/cypress/e2e/core/linodes/alerts-create.spec.ts index 1c2761ba27f..87cbfc09cf0 100644 --- a/packages/manager/cypress/e2e/core/linodes/alerts-create.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/alerts-create.spec.ts @@ -36,10 +36,16 @@ describe('Create flow when beta alerts enabled by region and feature flag', func cy.wrap(mockRegions).as('mockRegions'); mockGetRegions(mockRegions).as('getRegions'); mockAppendFeatureFlags({ - aclpBetaServices: { + aclpServices: { linode: { - alerts: true, - metrics: false, + alerts: { + beta: true, + enabled: true, + }, + metrics: { + beta: false, + enabled: false, + }, }, }, }).as('getFeatureFlags'); @@ -453,7 +459,7 @@ describe('Create flow when beta alerts enabled by region and feature flag', func }); }); -describe('aclpBetaServices feature flag disabled', function () { +describe('aclpServices feature flag disabled', function () { it('Alerts not present when feature flag disabled', function () { const mockEnabledRegion = regionFactory.build({ capabilities: ['Linodes'], @@ -465,10 +471,16 @@ describe('aclpBetaServices feature flag disabled', function () { cy.wrap(mockRegions).as('mockRegions'); mockGetRegions(mockRegions).as('getRegions'); mockAppendFeatureFlags({ - aclpBetaServices: { + aclpServices: { linode: { - alerts: false, - metrics: false, + alerts: { + beta: false, + enabled: false, + }, + metrics: { + beta: false, + enabled: false, + }, }, }, }).as('getFeatureFlags'); diff --git a/packages/manager/cypress/e2e/core/linodes/alerts-edit.spec.ts b/packages/manager/cypress/e2e/core/linodes/alerts-edit.spec.ts index da0a70af60d..74c3ab9e3a5 100644 --- a/packages/manager/cypress/e2e/core/linodes/alerts-edit.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/alerts-edit.spec.ts @@ -58,10 +58,16 @@ const mockEnabledBetaAlerts = { describe('region enables alerts', function () { beforeEach(() => { mockAppendFeatureFlags({ - aclpBetaServices: { + aclpServices: { linode: { - alerts: true, - metrics: false, + alerts: { + beta: true, + enabled: true, + }, + metrics: { + beta: false, + enabled: false, + }, }, }, }).as('getFeatureFlags'); @@ -400,10 +406,16 @@ describe('region enables alerts', function () { describe('region disables alerts. beta alerts not available regardless of linode settings', function () { beforeEach(() => { mockAppendFeatureFlags({ - aclpBetaServices: { + aclpServices: { linode: { - alerts: true, - metrics: false, + alerts: { + beta: true, + enabled: true, + }, + metrics: { + beta: false, + enabled: false, + }, }, }, }).as('getFeatureFlags'); diff --git a/packages/manager/src/dev-tools/FeatureFlagTool.tsx b/packages/manager/src/dev-tools/FeatureFlagTool.tsx index 92bc087325a..f5a4055b900 100644 --- a/packages/manager/src/dev-tools/FeatureFlagTool.tsx +++ b/packages/manager/src/dev-tools/FeatureFlagTool.tsx @@ -21,7 +21,7 @@ const MOCK_FEATURE_FLAGS_STORAGE_KEY = 'devTools/mock-feature-flags'; const options: { flag: keyof Flags; label: string }[] = [ { flag: 'aclp', label: 'CloudPulse' }, { flag: 'aclpAlerting', label: 'CloudPulse Alerting' }, - { flag: 'aclpBetaServices', label: 'ACLP Beta Services' }, + { flag: 'aclpServices', label: 'ACLP Services' }, { flag: 'aclpLogs', label: 'ACLP Logs' }, { flag: 'apl', label: 'Akamai App Platform' }, { flag: 'aplGeneralAvailability', label: 'Akamai App Platform GA' }, diff --git a/packages/manager/src/factories/featureFlags.ts b/packages/manager/src/factories/featureFlags.ts index e3348c6c856..128355a483d 100644 --- a/packages/manager/src/factories/featureFlags.ts +++ b/packages/manager/src/factories/featureFlags.ts @@ -1,6 +1,6 @@ import { Factory } from '@linode/utilities'; -import type { ProductInformationBannerFlag } from 'src/featureFlags'; +import type { Flags, ProductInformationBannerFlag } from 'src/featureFlags'; export const productInformationBannerFactory = Factory.Sync.makeFactory({ @@ -15,3 +15,47 @@ export const productInformationBannerFactory = message: 'Store critical data and media files with S3-Compatible Object Storage. New Availability: Atlanta', }); + +export const flagsFactory = Factory.Sync.makeFactory>({ + aclp: { beta: true, enabled: true }, + aclpServices: { + linode: { + alerts: { beta: true, enabled: true }, + metrics: { beta: true, enabled: true }, + }, + firewall: { + alerts: { beta: true, enabled: true }, + metrics: { beta: true, enabled: true }, + }, + dbaas: { + alerts: { beta: true, enabled: true }, + metrics: { beta: true, enabled: true }, + }, + nodebalancer: { + alerts: { beta: true, enabled: true }, + metrics: { beta: true, enabled: true }, + }, + }, + aclpResourceTypeMap: [ + { + dimensionKey: 'LINODE_ID', + maxResourceSelections: 10, + serviceType: 'linode', + }, + { + dimensionKey: 'cluster_id', + maxResourceSelections: 10, + serviceType: 'dbaas', + }, + { + dimensionKey: 'cluster_id', + maxResourceSelections: 10, + serviceType: 'nodebalancer', + }, + { + dimensionKey: 'firewall', + maxResourceSelections: 10, + serviceType: 'firewall', + }, + ], +}); diff --git a/packages/manager/src/featureFlags.ts b/packages/manager/src/featureFlags.ts index f32d66907a2..008bbddc867 100644 --- a/packages/manager/src/featureFlags.ts +++ b/packages/manager/src/featureFlags.ts @@ -68,8 +68,17 @@ interface GeckoFeatureFlag extends BaseFeatureFlag { } interface AclpFlag { + /** + * This property indicates whether the feature is in beta + */ beta: boolean; + /** + * This property indicates whether to bypass account capabilities check or not + */ bypassAccountCapabilities?: boolean; + /** + * This property indicates whether the feature is enabled + */ enabled: boolean; } @@ -126,10 +135,10 @@ export interface Flags { aclp: AclpFlag; aclpAlerting: AclpAlerting; aclpAlertServiceTypeConfig: AclpAlertServiceTypeConfig[]; - aclpBetaServices: Partial; aclpLogs: BetaFeatureFlag; aclpReadEndpoint: string; aclpResourceTypeMap: CloudPulseResourceTypeMapFlag[]; + aclpServices: Partial; apicliButtonCopy: string; apiMaintenance: APIMaintenance; apl: boolean; @@ -318,9 +327,9 @@ export interface AclpAlertServiceTypeConfig { // This can be extended to have supportedRegions, supportedFilters and other tags } -export type AclpBetaServices = { +export type AclpServices = { [serviceType in CloudPulseServiceType]: { - alerts: boolean; - metrics: boolean; + alerts?: AclpFlag; + metrics?: AclpFlag; }; }; diff --git a/packages/manager/src/features/CloudPulse/Alerts/AlertsDetail/AlertDetailOverview.tsx b/packages/manager/src/features/CloudPulse/Alerts/AlertsDetail/AlertDetailOverview.tsx index 17e4eaa71a9..3609cd728d6 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/AlertsDetail/AlertDetailOverview.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/AlertsDetail/AlertDetailOverview.tsx @@ -37,7 +37,7 @@ export const AlertDetailOverview = React.memo((props: OverviewProps) => { } = alertDetails; const { data: serviceTypeList, isFetching } = useCloudPulseServiceTypes(true); - const { aclpBetaServices } = useFlags(); + const { aclpServices } = useFlags(); if (isFetching) { return ; @@ -65,7 +65,7 @@ export const AlertDetailOverview = React.memo((props: OverviewProps) => { ({ useAllAlertDefinitionsQuery: vi.fn().mockReturnValue({}), useCloudPulseServiceTypes: vi.fn().mockReturnValue({}), + useFlags: vi.fn(), })); +const aclpServicesFlag: Partial = { + linode: { + alerts: { enabled: true, beta: true }, + metrics: { enabled: true, beta: true }, + }, + dbaas: { + alerts: { enabled: true, beta: true }, + metrics: { enabled: true, beta: true }, + }, +}; + +const linodeLabel = 'Linode beta'; +const databasesLabel = 'Databases beta'; + vi.mock('src/queries/cloudpulse/alerts', async () => { const actual = await vi.importActual('src/queries/cloudpulse/alerts'); return { @@ -36,6 +53,14 @@ vi.mock('src/queries/cloudpulse/services', async () => { }; }); +vi.mock('src/hooks/useFlags', () => ({ + useFlags: queryMocks.useFlags, +})); + +queryMocks.useFlags.mockReturnValue({ + aclpServices: aclpServicesFlag, +}); + const mockResponse = alertFactory.buildList(3); const serviceTypes = [ { @@ -48,14 +73,23 @@ const serviceTypes = [ }, ]; -describe('Alert Listing', () => { - it('should render the alert landing table with items', async () => { +describe('Alert Listing - Core Functionality', () => { + beforeEach(() => { queryMocks.useAllAlertDefinitionsQuery.mockReturnValue({ data: mockResponse, isError: false, isLoading: false, status: 'success', }); + queryMocks.useCloudPulseServiceTypes.mockReturnValue({ + data: { data: serviceTypes }, + isError: false, + isLoading: false, + status: 'success', + }); + }); + + it('should render the alert landing table with items', async () => { renderWithTheme(); expect(screen.getByText('Alert Name')).toBeVisible(); expect(screen.getByText('Service')).toBeVisible(); @@ -71,13 +105,6 @@ describe('Alert Listing', () => { }); it('should render the alert row', async () => { - queryMocks.useAllAlertDefinitionsQuery.mockReturnValue({ - data: mockResponse, - isError: false, - isLoading: false, - status: 'success', - }); - const { getByText } = renderWithTheme(); expect(getByText(mockResponse[0].label)).toBeVisible(); expect(getByText(mockResponse[1].label)).toBeVisible(); @@ -94,13 +121,6 @@ describe('Alert Listing', () => { status: 'success', }); - queryMocks.useCloudPulseServiceTypes.mockReturnValue({ - data: { data: serviceTypes }, - isError: false, - isLoading: false, - status: 'success', - }); - const { getByRole, getByTestId, getByText, queryByText } = renderWithTheme( ); @@ -112,11 +132,11 @@ describe('Alert Listing', () => { within(serviceFilter).getByRole('button', { name: 'Open' }) ); await waitFor(() => { - getByRole('option', { name: 'Databases' }); - getByRole('option', { name: 'Linode' }); + getByRole('option', { name: databasesLabel }); + getByRole('option', { name: linodeLabel }); }); await act(async () => { - await userEvent.click(getByRole('option', { name: 'Databases' })); + await userEvent.click(getByRole('option', { name: databasesLabel })); }); await waitFor(() => { @@ -250,6 +270,7 @@ describe('Alert Listing', () => { 'Creation of 3 alerts has failed as indicated in the status column. Please open a support ticket for assistance.' ); }); + it('should disable the create button when the alerts are loading', async () => { queryMocks.useAllAlertDefinitionsQuery.mockReturnValue({ data: null, @@ -263,3 +284,125 @@ describe('Alert Listing', () => { expect(createButton).toBeDisabled(); }); }); + +describe('Alert Listing - Feature Flag Management', () => { + beforeEach(() => { + queryMocks.useAllAlertDefinitionsQuery.mockReturnValue({ + data: mockResponse, + isError: false, + isLoading: false, + status: 'success', + }); + + queryMocks.useCloudPulseServiceTypes.mockReturnValue({ + data: { data: serviceTypes }, + isError: false, + isLoading: false, + status: 'success', + }); + }); + + it('should render the alerts from the enabled services', async () => { + queryMocks.useFlags.mockReturnValue({ + aclpServices: aclpServicesFlag, + }); + + renderWithTheme(); + + expect(screen.getByText(mockResponse[0].label)).toBeVisible(); + expect(screen.getByText(mockResponse[1].label)).toBeVisible(); + expect(screen.getByText(mockResponse[2].label)).toBeVisible(); + }); + + it('should not render the alerts from the disabled services', async () => { + queryMocks.useFlags.mockReturnValue({ + aclpServices: { + linode: { + alerts: { enabled: false, beta: true }, + metrics: { enabled: false, beta: true }, + }, + dbaas: { + alerts: { enabled: true, beta: true }, + metrics: { enabled: true, beta: true }, + }, + }, + }); + + renderWithTheme(); + expect(screen.queryByText(mockResponse[0].label)).not.toBeInTheDocument(); + expect(screen.queryByText(mockResponse[1].label)).not.toBeInTheDocument(); + expect(screen.queryByText(mockResponse[2].label)).not.toBeInTheDocument(); + }); + + it('should not render the alerts from the services which are missing in the flag', async () => { + queryMocks.useFlags.mockReturnValue({ + aclpServices: { + dbaas: { + alerts: { enabled: true, beta: true }, + metrics: { enabled: true, beta: true }, + }, + }, + }); + + renderWithTheme(); + expect(screen.queryByText(mockResponse[0].label)).not.toBeInTheDocument(); + expect(screen.queryByText(mockResponse[1].label)).not.toBeInTheDocument(); + expect(screen.queryByText(mockResponse[2].label)).not.toBeInTheDocument(); + }); + + it('should render the service types based on the enabled services from the aclp services flag', async () => { + queryMocks.useFlags.mockReturnValue({ + aclpServices: { + linode: { + alerts: { enabled: true, beta: true }, + metrics: { enabled: true, beta: true }, + }, + dbaas: { + alerts: { enabled: false, beta: true }, + metrics: { enabled: false, beta: true }, + }, + }, + }); + + renderWithTheme(); + + const serviceFilterDropdown = screen.getByTestId('alert-service-filter'); + await userEvent.click( + within(serviceFilterDropdown).getByRole('button', { name: 'Open' }) + ); + + expect(screen.getByRole('option', { name: linodeLabel })).toBeVisible(); + expect(screen.queryByRole('option', { name: databasesLabel })).toBeNull(); // Verify that Databases is NOT present (filtered out by the flag) + }); + + it('should not return service types that are missing in the flag', async () => { + queryMocks.useFlags.mockReturnValue({ + aclpServices: { + linode: { + alerts: { enabled: true, beta: true }, + metrics: { enabled: true, beta: true }, + }, + }, + }); + renderWithTheme(); + const serviceFilterDropdown = screen.getByTestId('alert-service-filter'); + await userEvent.click( + within(serviceFilterDropdown).getByRole('button', { name: 'Open' }) + ); + expect(screen.getByRole('option', { name: linodeLabel })).toBeVisible(); + expect(screen.queryByRole('option', { name: 'Databases' })).toBeNull(); + }); + + it('should not return service types that are missing the alerts property in the flag', async () => { + queryMocks.useFlags.mockReturnValue({ + aclpServices: { + linode: { + metrics: { enabled: true, beta: true }, + }, + }, + }); + + renderWithTheme(); + expect(screen.queryByRole('option', { name: 'Linode' })).toBeNull(); + }); +}); diff --git a/packages/manager/src/features/CloudPulse/Alerts/AlertsListing/AlertListing.tsx b/packages/manager/src/features/CloudPulse/Alerts/AlertsListing/AlertListing.tsx index 3d94db48c23..ab02de71775 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/AlertsListing/AlertListing.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/AlertsListing/AlertListing.tsx @@ -24,6 +24,7 @@ import { usePreferencesToggle } from '../../Utils/UserPreference'; import { alertStatusOptions } from '../constants'; import { AlertListNoticeMessages } from '../Utils/AlertListNoticeMessages'; import { scrollToElement } from '../Utils/AlertResourceUtils'; +import { alertsFromEnabledServices } from '../Utils/utils'; import { AlertsListTable } from './AlertListTable'; import { alertLimitMessage, @@ -51,13 +52,18 @@ interface AlertsLimitErrorMessageProps { export const AlertListing = () => { const navigate = useNavigate(); - const { data: alerts, error, isLoading } = useAllAlertDefinitionsQuery(); + const { data: allAlerts, error, isLoading } = useAllAlertDefinitionsQuery(); const { data: serviceOptions, error: serviceTypesError, isLoading: serviceTypesLoading, } = useCloudPulseServiceTypes(true); - const { aclpBetaServices, aclpAlerting } = useFlags(); + + const { aclpServices, aclpAlerting } = useFlags(); + + // Filter alerts based on the enabled services from the LD flag + const alerts = alertsFromEnabledServices(allAlerts, aclpServices); + const userAlerts = alerts?.filter(({ type }) => type === 'user') ?? []; const isAlertLimitReached = userAlerts.length >= (aclpAlerting?.accountAlertLimit ?? 10); @@ -74,12 +80,17 @@ export const AlertListing = () => { CloudPulseServiceType >[] => { return serviceOptions && serviceOptions.data.length > 0 - ? serviceOptions.data.map((service) => ({ - label: service.label, - value: service.service_type, - })) + ? serviceOptions.data + .filter( + (service) => + aclpServices?.[service.service_type]?.alerts?.enabled ?? false + ) + .map((service) => ({ + label: service.label, + value: service.service_type, + })) : []; - }, [serviceOptions]); + }, [aclpServices, serviceOptions]); const [searchText, setSearchText] = React.useState(''); @@ -252,7 +263,7 @@ export const AlertListing = () => { return ( {option.label}{' '} - {aclpBetaServices?.[option.value]?.alerts && } + {aclpServices?.[option.value]?.alerts?.beta && } ); diff --git a/packages/manager/src/features/CloudPulse/Alerts/AlertsListing/AlertTableRow.tsx b/packages/manager/src/features/CloudPulse/Alerts/AlertsListing/AlertTableRow.tsx index d4343734097..93707367ff9 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/AlertsListing/AlertTableRow.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/AlertsListing/AlertTableRow.tsx @@ -45,7 +45,7 @@ export const AlertTableRow = (props: Props) => { updated_by, } = alert; - const { aclpBetaServices } = useFlags(); + const { aclpServices } = useFlags(); return ( @@ -68,7 +68,7 @@ export const AlertTableRow = (props: Props) => { {services.find((service) => service.value === service_type)?.label}{' '} - {aclpBetaServices?.[service_type]?.alerts && } + {aclpServices?.[service_type]?.alerts?.beta && } {created_by} diff --git a/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertReusableComponent.tsx b/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertReusableComponent.tsx index aa1459cf746..e49121f398b 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertReusableComponent.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertReusableComponent.tsx @@ -84,7 +84,7 @@ export const AlertReusableComponent = (props: AlertReusableComponentProps) => { [alerts, regionId, searchText, selectedType] ); - const { aclpBetaServices } = useFlags(); + const { aclpServices } = useFlags(); const navigate = useNavigate(); @@ -102,7 +102,7 @@ export const AlertReusableComponent = (props: AlertReusableComponentProps) => { Alerts - {aclpBetaServices?.[serviceType]?.alerts && } + {aclpServices?.[serviceType]?.alerts?.beta && }