From bef3dd2dda29c12b717415c790241986fa29c955 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Mon, 15 Sep 2025 21:39:12 +0530 Subject: [PATCH 01/15] [DI-26882] - Add initial changes for object storage integration in metrics --- packages/api-v4/src/cloudpulse/types.ts | 6 +- .../Dashboard/CloudPulseDashboard.tsx | 12 +++- .../CloudPulseDashboardWithFilters.tsx | 8 ++- .../Utils/CloudPulseWidgetUtils.test.ts | 25 ++++++++ .../CloudPulse/Utils/CloudPulseWidgetUtils.ts | 58 ++++++++++++++++--- .../features/CloudPulse/Utils/FilterConfig.ts | 51 ++++++++++++++++ .../ReusableDashboardFilterUtils.test.ts | 23 ++++++++ .../Utils/ReusableDashboardFilterUtils.ts | 14 ++++- .../CloudPulse/Widget/CloudPulseWidget.tsx | 10 +++- .../Widget/CloudPulseWidgetRenderer.tsx | 6 ++ packages/manager/src/mocks/serverHandlers.ts | 36 ++++++++++++ .../manager/src/queries/cloudpulse/queries.ts | 30 +++++++++- .../src/queries/cloudpulse/resources.ts | 14 ++++- .../utilities/src/__data__/regionsData.ts | 3 +- 14 files changed, 275 insertions(+), 21 deletions(-) diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index 494e4e2ee21..bae8d0b9a3d 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -9,7 +9,6 @@ export type CloudPulseServiceType = | 'linode' | 'nodebalancer' | 'objectstorage'; - export type AlertClass = 'dedicated' | 'shared'; export type DimensionFilterOperatorType = | 'endswith' @@ -134,7 +133,7 @@ export interface Dimension { } export interface JWETokenPayLoad { - entity_ids: number[]; + entity_ids: number[] | undefined; } export interface JWEToken { @@ -149,7 +148,8 @@ export interface Metric { export interface CloudPulseMetricsRequest { absolute_time_duration: DateTimeWithPreset | undefined; associated_entity_region?: string; - entity_ids: number[]; + entity_ids: number[] | string[]; + entity_region?: string; filters?: Filters[]; group_by?: string[]; metrics: Metric[]; diff --git a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx index f9b3c3240ad..ca3d42846d4 100644 --- a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx +++ b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx @@ -81,10 +81,17 @@ export const CloudPulseDashboard = (props: DashboardProperties) => { savePref, groupBy, linodeRegion, + region, } = props; const { preferences } = useAclpPreference(); - const getJweTokenPayload = (): JWETokenPayLoad => { + + const getJweTokenPayload = ( + dashboardId: number | undefined + ): JWETokenPayLoad => { + if (!dashboardId || dashboardId === 6) { + return { entity_ids: undefined }; + } return { entity_ids: resources?.map((resource) => Number(resource)) ?? [], }; @@ -122,7 +129,7 @@ export const CloudPulseDashboard = (props: DashboardProperties) => { isFetching: isJweTokenFetching, } = useCloudPulseJWEtokenQuery( dashboard?.service_type, - getJweTokenPayload(), + getJweTokenPayload(dashboard?.id), Boolean(resources) && !isDashboardLoading && !isDashboardApiError ); @@ -169,6 +176,7 @@ export const CloudPulseDashboard = (props: DashboardProperties) => { manualRefreshTimeStamp={manualRefreshTimeStamp} metricDefinitions={metricDefinitions} preferences={preferences} + region={region} resourceList={resourceList} resources={resources} savePref={savePref} diff --git a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx index 26e39a95ed2..e0afc656092 100644 --- a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx +++ b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx @@ -28,6 +28,10 @@ export interface CloudPulseDashboardWithFiltersProp { * The id of the dashboard that needs to be rendered */ dashboardId: number; + /** + * The region for which the metrics will be listed + */ + region?: string; /** * The resource id for which the metrics will be listed */ @@ -36,7 +40,7 @@ export interface CloudPulseDashboardWithFiltersProp { export const CloudPulseDashboardWithFilters = React.memo( (props: CloudPulseDashboardWithFiltersProp) => { - const { dashboardId, resource } = props; + const { dashboardId, resource, region } = props; const { data: dashboard, isError } = useCloudPulseDashboardByIdQuery(dashboardId); const [filterData, setFilterData] = React.useState({ @@ -122,6 +126,7 @@ export const CloudPulseDashboardWithFilters = React.memo( dashboardObj: dashboard, filterValue: filterData.id, resource, + region, timeDuration, groupBy, }); @@ -206,6 +211,7 @@ export const CloudPulseDashboardWithFilters = React.memo( dashboardObj: dashboard, filterValue: filterData.id, resource, + region, timeDuration, groupBy, })} diff --git a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts index aba673b33a6..7153b0e8a5f 100644 --- a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts @@ -1,9 +1,12 @@ import { formatPercentage } from '@linode/utilities'; +import { widgetFactory } from 'src/factories'; + import { generateGraphData, generateMaxUnit, getDimensionName, + getEntityIds, getLabelName, getTimeDurationFromPreset, mapResourceIdToName, @@ -261,4 +264,26 @@ describe('getTimeDurationFromPreset method', () => { const result = getTimeDurationFromPreset('15min'); expect(result).toBe(undefined); }); + + describe('getEntityIds method', () => { + it('should return entity ids for dashboard id 6', () => { + const result = getEntityIds( + [{ id: '123', label: 'linode-1' }], + ['123'], + widgetFactory.build(), + 2 + ); + expect(result).toEqual([123]); + }); + + it('should return entity ids for dashboard id 2', () => { + const result = getEntityIds( + [{ id: 'bucket-1', label: 'bucket-name-1' }], + ['bucket-1'], + widgetFactory.build(), + 6 + ); + expect(result).toEqual(['bucket-1']); + }); + }); }); diff --git a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts index 7ebfc02b5c4..bab6c68dc6f 100644 --- a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts @@ -111,6 +111,11 @@ interface GraphDataOptionsProps { } interface MetricRequestProps { + /** + * id of the selected dashboard + */ + dashboardId: number; + /** * time duration for the metrics data */ @@ -128,6 +133,11 @@ interface MetricRequestProps { */ linodeRegion?: string; + /** + * selected region for the widget + */ + region?: string; + /** * list of CloudPulse resources available */ @@ -326,18 +336,23 @@ export const generateMaxUnit = ( export const getCloudPulseMetricRequest = ( props: MetricRequestProps ): CloudPulseMetricsRequest => { - const { duration, entityIds, resources, widget, groupBy, linodeRegion } = - props; + const { + duration, + entityIds, + resources, + widget, + groupBy, + linodeRegion, + dashboardId, + region, + } = props; const preset = duration.preset; - - return { + const metricsRequest: CloudPulseMetricsRequest = { absolute_time_duration: preset !== 'reset' && preset !== 'this month' && preset !== 'last month' ? undefined : { end: duration.end, start: duration.start }, - entity_ids: resources - ? entityIds.map((id) => parseInt(id, 10)) - : widget.entity_ids.map((id) => parseInt(id, 10)), + entity_ids: getEntityIds(resources, entityIds, widget, dashboardId), filters: undefined, group_by: !groupBy?.length ? undefined : groupBy, relative_time_duration: getTimeDurationFromPreset(preset), @@ -356,6 +371,35 @@ export const getCloudPulseMetricRequest = ( }, associated_entity_region: linodeRegion, }; + + if (dashboardId === 6) { + return { + ...metricsRequest, + entity_region: region, + }; + } + return metricsRequest; +}; + +/** + * + * @param resources list of CloudPulse resources + * @param entityIds list of entity ids + * @param widget widget + * @returns transformed entity ids + */ +export const getEntityIds = ( + resources: CloudPulseResources[], + entityIds: string[], + widget: Widgets, + dashboardId: number +) => { + if (dashboardId === 6) { + return entityIds; + } + return resources + ? entityIds.map((id) => parseInt(id, 10)) + : widget.entity_ids.map((id) => parseInt(id, 10)); }; /** diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts b/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts index c93438a7e29..c15d305b9a4 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts @@ -1,6 +1,7 @@ import { capabilityServiceTypeMapping } from '@linode/api-v4'; import { + ENDPOINT, INTERFACE_IDS_PLACEHOLDER_TEXT, LINODE_REGION, RESOURCE_ID, @@ -315,6 +316,55 @@ export const FIREWALL_CONFIG: Readonly = { serviceType: 'firewall', }; +export const OBJECTSTORAGE_CONFIG_BUCKET: Readonly = + { + capability: capabilityServiceTypeMapping['objectstorage'], + filters: [ + { + configuration: { + filterKey: 'region', + filterType: 'string', + isFilterable: true, + isMetricsFilter: true, + name: 'Region', + priority: 1, + neededInViews: [CloudPulseAvailableViews.central], + }, + name: 'Region', + }, + { + configuration: { + dependency: ['region'], + filterKey: ENDPOINT, + filterType: 'string', + isFilterable: false, + isMetricsFilter: false, + isMultiSelect: true, + name: 'Endpoints', + priority: 2, + neededInViews: [CloudPulseAvailableViews.central], + }, + name: 'Endpoints', + }, + { + configuration: { + dependency: ['region', ENDPOINT], + filterKey: RESOURCE_ID, + filterType: 'string', + isFilterable: true, + isMetricsFilter: true, + isMultiSelect: true, + name: 'Buckets', + neededInViews: [CloudPulseAvailableViews.central], + placeholder: 'Select Buckets', + priority: 3, + }, + name: 'Buckets', + }, + ], + serviceType: 'objectstorage', + }; + export const FILTER_CONFIG: Readonly< Map > = new Map([ @@ -322,4 +372,5 @@ export const FILTER_CONFIG: Readonly< [2, LINODE_CONFIG], [3, NODEBALANCER_CONFIG], [4, FIREWALL_CONFIG], + [6, OBJECTSTORAGE_CONFIG_BUCKET], ]); diff --git a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts index d3e0132dbfe..553e833beb3 100644 --- a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts @@ -93,6 +93,29 @@ it('test checkMandatoryFiltersSelected method for role', () => { expect(result).toBe(true); }); +it('checkMandatoryFiltersSelected method should return false if no region is selected for dashboard id 5', () => { + const result = checkMandatoryFiltersSelected({ + dashboardObj: { ...mockDashboard, id: 5 }, + filterValue: {}, + resource: 1, + timeDuration: { end: end.toISO(), preset, start: start.toISO() }, + groupBy: [], + }); + expect(result).toBe(false); +}); + +it('checkMandatoryFiltersSelected method should return true if region is selected for dashboard id 5', () => { + const result = checkMandatoryFiltersSelected({ + dashboardObj: { ...mockDashboard, id: 5 }, + filterValue: {}, + resource: 1, + timeDuration: { end: end.toISO(), preset, start: start.toISO() }, + groupBy: [], + region: 'ap-west', + }); + expect(result).toBe(true); +}); + it('test constructDimensionFilters method', () => { mockDashboard.service_type = 'dbaas'; const result = constructDimensionFilters({ diff --git a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts index 7337e235b4d..61a8270cb23 100644 --- a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts @@ -23,6 +23,10 @@ interface ReusableDashboardFilterUtilProps { * The selected grouping criteria */ groupBy: string[]; + /** + * The selected region + */ + region?: string; /** * The selected resource id */ @@ -40,7 +44,8 @@ interface ReusableDashboardFilterUtilProps { export const getDashboardProperties = ( props: ReusableDashboardFilterUtilProps ): DashboardProperties => { - const { dashboardObj, filterValue, resource, timeDuration, groupBy } = props; + const { dashboardObj, filterValue, resource, timeDuration, groupBy, region } = + props; return { additionalFilters: constructDimensionFilters({ dashboardObj, @@ -53,6 +58,7 @@ export const getDashboardProperties = ( resources: [String(resource)], savePref: false, groupBy, + region, }; }; @@ -63,7 +69,7 @@ export const getDashboardProperties = ( export const checkMandatoryFiltersSelected = ( props: ReusableDashboardFilterUtilProps ): boolean => { - const { dashboardObj, filterValue, resource, timeDuration } = props; + const { dashboardObj, filterValue, resource, timeDuration, region } = props; const serviceTypeConfig = FILTER_CONFIG.get(dashboardObj.id); if (!serviceTypeConfig) { @@ -74,6 +80,10 @@ export const checkMandatoryFiltersSelected = ( return false; } + if (dashboardObj.id === 6 && !region) { + return false; + } + return serviceTypeConfig.filters.every(({ configuration }) => { const { filterKey, neededInViews } = configuration; diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx index 8ef969b8e97..0f89b33eebd 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx @@ -91,6 +91,11 @@ export interface CloudPulseWidgetProperties { */ linodeRegion?: string; + /** + * selected region for the widget + */ + region?: string; + /** * List of resources available of selected service type */ @@ -149,7 +154,6 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { const { additionalFilters, - dashboardId, ariaLabel, authToken, availableMetrics, @@ -163,6 +167,8 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { unit, widget: widgetProp, linodeRegion, + dashboardId, + region, } = props; const timezone = @@ -262,6 +268,8 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { widget, groupBy: [...(widgetProp.group_by ?? []), ...groupBy], linodeRegion, + dashboardId, + region, }), filters, // any additional dimension filters will be constructed and passed here }, diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidgetRenderer.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidgetRenderer.tsx index e09fa2129e2..ee4d5c867b2 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidgetRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidgetRenderer.tsx @@ -37,6 +37,10 @@ interface WidgetProps { manualRefreshTimeStamp?: number; metricDefinitions: ResourcePage | undefined; preferences?: AclpConfig; + /** + * Selected region for the widget + */ + region?: string; resourceList: CloudPulseResources[] | undefined; resources: string[]; savePref?: boolean; @@ -68,6 +72,7 @@ export const RenderWidgets = React.memo( savePref, groupBy, linodeRegion, + region, } = props; const getCloudPulseGraphProperties = ( @@ -176,6 +181,7 @@ export const RenderWidgets = React.memo( availableMetrics={availMetrics} isJweTokenFetching={isJweTokenFetching} linodeRegion={linodeRegion} + region={region} resources={resourceList!} savePref={savePref} /> diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts index b051a64238c..4b84a248c22 100644 --- a/packages/manager/src/mocks/serverHandlers.ts +++ b/packages/manager/src/mocks/serverHandlers.ts @@ -1340,6 +1340,11 @@ export const handlers = [ region: 'us-mia', s3_endpoint: 'us-mia-1.linodeobjects.com', }), + objectStorageBucketFactoryGen2.build({ + endpoint_type: 'E3', + region: 'ap-west', + s3_endpoint: 'ap-west-1.linodeobjects.com', + }), ]; return HttpResponse.json(makeResourcePage(endpoints)); }), @@ -1448,6 +1453,18 @@ export const handlers = [ label: `obj-bucket-${randomBucketNumber}`, region, }); + if (region === 'ap-west') { + buckets.push( + objectStorageBucketFactoryGen2.build({ + cluster: `${region}-1`, + endpoint_type: 'E3', + s3_endpoint: 'ap-west-1.linodeobjects.com', + hostname: `obj-bucket-${randomBucketNumber}.${region}.linodeobjects.com`, + label: `obj-bucket-${randomBucketNumber}`, + region, + }) + ); + } return HttpResponse.json({ data: buckets.slice( @@ -3048,6 +3065,12 @@ export const handlers = [ regions: 'us-iad,us-east', alert: serviceAlertFactory.build({ scope: ['entity'] }), }), + serviceTypesFactory.build({ + label: 'Object Storage', + service_type: 'objectstorage', + regions: 'us-iad,us-east', + alert: serviceAlertFactory.build({ scope: ['entity'] }), + }), ], }; @@ -3136,6 +3159,16 @@ export const handlers = [ ); } + if (params.serviceType === 'objectstorage') { + response.data.push( + dashboardFactory.build({ + id: 6, + label: 'Object Storage Dashboard', + service_type: 'objectstorage', + }) + ); + } + return HttpResponse.json(response); }), http.get( @@ -3422,6 +3455,9 @@ export const handlers = [ } else if (id === '4') { serviceType = 'firewall'; dashboardLabel = 'Firewall Service I/O Statistics'; + } else if (id === '6') { + serviceType = 'objectstorage'; + dashboardLabel = 'Object Storage Service I/O Statistics'; } else { serviceType = 'linode'; dashboardLabel = 'Linode Service I/O Statistics'; diff --git a/packages/manager/src/queries/cloudpulse/queries.ts b/packages/manager/src/queries/cloudpulse/queries.ts index d53fedbfb23..27811b7a0c1 100644 --- a/packages/manager/src/queries/cloudpulse/queries.ts +++ b/packages/manager/src/queries/cloudpulse/queries.ts @@ -16,6 +16,11 @@ import { } from '@linode/queries'; import { createQueryKeys } from '@lukemorales/query-key-factory'; +import { objectStorageQueries } from '../object-storage/queries'; +import { + getAllBucketsFromEndpoints, + getAllObjectStorageEndpoints, +} from '../object-storage/requests'; import { fetchCloudPulseMetrics } from './metrics'; import { getAllAlertsRequest, @@ -124,6 +129,14 @@ export const queryFactory = createQueryKeys(key, { case 'nodebalancer': return nodebalancerQueries.nodebalancers._ctx.all(params, filters); + case 'objectstorage': + return { + queryFn: () => getAllBuckets(), + queryKey: [ + objectStorageQueries.buckets.queryKey, + objectStorageQueries.endpoints.queryKey, + ], + }; case 'volumes': return volumeQueries.lists._ctx.all(params, filters); // in this we don't need to define our own query factory, we will reuse existing implementation in volumes.ts @@ -134,6 +147,21 @@ export const queryFactory = createQueryKeys(key, { token: (serviceType: string | undefined, request: JWETokenPayLoad) => ({ queryFn: () => getJWEToken(request, serviceType!), - queryKey: [serviceType, { resource_ids: request.entity_ids.sort() }], + queryKey: [serviceType, { resource_ids: request.entity_ids?.sort() }], }), }); + +const getAllBuckets = async () => { + const endpoints = await getAllObjectStorageEndpoints(); + // Filter out the endpoints that are not GEN2 + const endpointsGen2 = endpoints.filter( + (endpoint) => + endpoint.endpoint_type !== 'E0' && endpoint.endpoint_type !== 'E1' + ); + // Get all the buckets from the endpoints + const allBuckets = await getAllBucketsFromEndpoints(endpointsGen2); + if (allBuckets.errors.length) { + throw new Error('Unable to fetch the data.'); + } + return allBuckets.buckets; +}; diff --git a/packages/manager/src/queries/cloudpulse/resources.ts b/packages/manager/src/queries/cloudpulse/resources.ts index 4fa2e79d4eb..c7092d20cac 100644 --- a/packages/manager/src/queries/cloudpulse/resources.ts +++ b/packages/manager/src/queries/cloudpulse/resources.ts @@ -14,7 +14,8 @@ export const useResourcesQuery = ( useQuery({ ...queryFactory.resources(resourceType, params, filters), enabled, - select: (resources) => { + retry: resourceType === 'objectstorage' ? false : 3, + select: (resources: any[]) => { return resources.map((resource) => { const entities: Record = {}; @@ -33,13 +34,20 @@ export const useResourcesQuery = ( } }); } + const id = + resourceType === 'objectstorage' + ? resource.hostname + : String(resource.id); + const label = + resourceType === 'objectstorage' ? resource.hostname : resource.label; return { engineType: resource.engine, - id: String(resource.id), - label: resource.label, + id, + label, region: resource.region, regions: resource.regions ? resource.regions : [], tags: resource.tags, + endpoint: resource.s3_endpoint, entities, clusterSize: resource.cluster_size, }; diff --git a/packages/utilities/src/__data__/regionsData.ts b/packages/utilities/src/__data__/regionsData.ts index 84587c53e5c..bb5a626c749 100644 --- a/packages/utilities/src/__data__/regionsData.ts +++ b/packages/utilities/src/__data__/regionsData.ts @@ -13,6 +13,7 @@ export const regions: Region[] = [ 'VPCs', 'Block Storage Migrations', 'Managed Databases', + 'Object Storage' ], country: 'in', id: 'ap-west', @@ -27,7 +28,7 @@ export const regions: Region[] = [ }, site_type: 'core', status: 'ok', - monitors: { alerts: ['Cloud Firewall'], metrics: [] }, + monitors: { alerts: ['Cloud Firewall'], metrics: ['Object Storage'] }, }, { capabilities: [ From 9fdd7c909708cf5eaa313c884aa66262f5319dbd Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Tue, 16 Sep 2025 20:30:33 +0530 Subject: [PATCH 02/15] [DI-26882] - Update type for reusable component, queryFn, tests, and mocks --- .../Dashboard/CloudPulseDashboardWithFilters.tsx | 2 +- .../features/CloudPulse/Utils/FilterConfig.ts | 7 ++++--- .../Utils/ReusableDashboardFilterUtils.test.ts | 8 ++++---- .../Utils/ReusableDashboardFilterUtils.ts | 2 +- .../src/features/CloudPulse/Utils/constants.ts | 5 +++++ .../shared/CloudPulseDashboardFilterBuilder.tsx | 8 ++++---- packages/manager/src/mocks/serverHandlers.ts | 3 +-- .../manager/src/queries/cloudpulse/queries.ts | 16 +++++++++------- 8 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx index e0afc656092..1763d73ef30 100644 --- a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx +++ b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx @@ -35,7 +35,7 @@ export interface CloudPulseDashboardWithFiltersProp { /** * The resource id for which the metrics will be listed */ - resource: number; + resource: number | string; } export const CloudPulseDashboardWithFilters = React.memo( diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts b/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts index c15d305b9a4..c152b66ab0d 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterConfig.ts @@ -4,6 +4,7 @@ import { ENDPOINT, INTERFACE_IDS_PLACEHOLDER_TEXT, LINODE_REGION, + REGION, RESOURCE_ID, } from './constants'; import { CloudPulseAvailableViews, CloudPulseSelectTypes } from './models'; @@ -322,7 +323,7 @@ export const OBJECTSTORAGE_CONFIG_BUCKET: Readonly { expect(result).toBe(true); }); -it('checkMandatoryFiltersSelected method should return false if no region is selected for dashboard id 5', () => { +it('checkMandatoryFiltersSelected method should return false if no region is selected for dashboard id 6', () => { const result = checkMandatoryFiltersSelected({ - dashboardObj: { ...mockDashboard, id: 5 }, + dashboardObj: { ...mockDashboard, id: 6 }, filterValue: {}, resource: 1, timeDuration: { end: end.toISO(), preset, start: start.toISO() }, @@ -104,9 +104,9 @@ it('checkMandatoryFiltersSelected method should return false if no region is sel expect(result).toBe(false); }); -it('checkMandatoryFiltersSelected method should return true if region is selected for dashboard id 5', () => { +it('checkMandatoryFiltersSelected method should return true if region is selected for dashboard id 6', () => { const result = checkMandatoryFiltersSelected({ - dashboardObj: { ...mockDashboard, id: 5 }, + dashboardObj: { ...mockDashboard, id: 6 }, filterValue: {}, resource: 1, timeDuration: { end: end.toISO(), preset, start: start.toISO() }, diff --git a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts index 61a8270cb23..15a8b5b7354 100644 --- a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts @@ -30,7 +30,7 @@ interface ReusableDashboardFilterUtilProps { /** * The selected resource id */ - resource: number; + resource: number | string; /** * The selected time duration */ diff --git a/packages/manager/src/features/CloudPulse/Utils/constants.ts b/packages/manager/src/features/CloudPulse/Utils/constants.ts index 786a4795805..64f98ddbbe4 100644 --- a/packages/manager/src/features/CloudPulse/Utils/constants.ts +++ b/packages/manager/src/features/CloudPulse/Utils/constants.ts @@ -8,6 +8,10 @@ export const SECONDARY_NODE = 'secondary'; export const REGION = 'region'; +export const ENTITY_REGION = 'entity_region'; + +export const ENDPOINT = 'endpoint'; + export const LINODE_REGION = 'associated_entity_region'; export const RESOURCES = 'resources'; @@ -85,6 +89,7 @@ export const NO_REGION_MESSAGE: Record = { linode: 'No Linodes configured in any regions.', nodebalancer: 'No NodeBalancers configured in any regions.', firewall: 'No firewalls configured in any Linode regions.', + objectstorage: 'No Object Storage buckets configured in any region.', }; export const HELPER_TEXT: Record = { diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx index 34e25625503..7f238d26787 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx @@ -82,7 +82,7 @@ export interface CloudPulseDashboardFilterBuilderProps { /** * selected resource ids */ - resource_ids?: number[]; + resource_ids?: (number | string)[]; } export const CloudPulseDashboardFilterBuilder = React.memo( @@ -328,12 +328,12 @@ export const CloudPulseDashboardFilterBuilder = React.memo( config, dashboard, dependentFilters: resource_ids?.length - ? { [RESOURCE_ID]: resource_ids } + ? { [RESOURCE_ID]: resource_ids as number[] } : dependentFilterReference.current, isServiceAnalyticsIntegration, preferences, resource_ids: resource_ids?.length - ? resource_ids + ? (resource_ids as number[]) : ( dependentFilterReference.current[RESOURCE_ID] as string[] )?.map((id: string) => Number(id)), @@ -362,7 +362,7 @@ export const CloudPulseDashboardFilterBuilder = React.memo( config, dashboard, dependentFilters: resource_ids?.length - ? { [RESOURCE_ID]: resource_ids } + ? { [RESOURCE_ID]: resource_ids as number[] } : dependentFilterReference.current, isServiceAnalyticsIntegration, preferences, diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts index 4b84a248c22..e5aff744b51 100644 --- a/packages/manager/src/mocks/serverHandlers.ts +++ b/packages/manager/src/mocks/serverHandlers.ts @@ -3598,9 +3598,8 @@ export const handlers = [ }, { metric: { - entity_id: '789', + entity_id: 'obj-bucket-383.ap-west.linodeobjects.com', metric_name: 'average_cpu_usage', - node_id: 'primary-3', }, values: [ [1721854379, '0.3744841110560275'], diff --git a/packages/manager/src/queries/cloudpulse/queries.ts b/packages/manager/src/queries/cloudpulse/queries.ts index 27811b7a0c1..59187f6417e 100644 --- a/packages/manager/src/queries/cloudpulse/queries.ts +++ b/packages/manager/src/queries/cloudpulse/queries.ts @@ -153,15 +153,17 @@ export const queryFactory = createQueryKeys(key, { const getAllBuckets = async () => { const endpoints = await getAllObjectStorageEndpoints(); - // Filter out the endpoints that are not GEN2 - const endpointsGen2 = endpoints.filter( - (endpoint) => - endpoint.endpoint_type !== 'E0' && endpoint.endpoint_type !== 'E1' - ); + // Get all the buckets from the endpoints - const allBuckets = await getAllBucketsFromEndpoints(endpointsGen2); + const allBuckets = await getAllBucketsFromEndpoints(endpoints); + + // Throw the error if we encounter any error for any single call. if (allBuckets.errors.length) { throw new Error('Unable to fetch the data.'); } - return allBuckets.buckets; + + // Filter the E0, E1 endpoint_type out and return the buckets + return allBuckets.buckets.filter( + (bucket) => bucket.endpoint_type !== 'E0' && bucket.endpoint_type !== 'E1' + ); }; From 3f3f6b190e381ce8ea3e73073fa2f505c0619e65 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Thu, 25 Sep 2025 10:46:29 +0530 Subject: [PATCH 03/15] [DI-26882] - Integrate new component and temporarily update obj storage metrics tab --- .../CloudPulse/Utils/FilterBuilder.test.ts | 6 +++ .../CloudPulse/Utils/FilterBuilder.ts | 51 ++++++++++++++++--- .../CloudPulse/Widget/CloudPulseWidget.tsx | 2 +- .../shared/CloudPulseComponentRenderer.tsx | 5 ++ .../CloudPulseDashboardFilterBuilder.tsx | 26 ++++++++++ .../ObjectStorage/BucketDetail/index.tsx | 13 +++++ .../manager/src/routes/objectStorage/index.ts | 10 ++++ 7 files changed, 105 insertions(+), 8 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts index 3d9b0bf6633..421ce370abe 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts @@ -527,6 +527,12 @@ describe('filterUsingDependentFilters', () => { expect(result).toEqual([mockData[0], mockData[1]]); }); + it('should filter when resource value is a string and filter value is an array', () => { + const filters = { region: ['us-east', 'us-west'] }; + const result = filterUsingDependentFilters(mockData, filters); + expect(result).toEqual([mockData[0], mockData[1]]); + }); + it('should return empty array if no resource matches', () => { const filters = { region: 'us-central' }; const result = filterUsingDependentFilters(mockData, filters); diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts index 34b07701b8b..33996ec55b5 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts @@ -14,6 +14,7 @@ import type { FilterValueType, } from '../Dashboard/CloudPulseDashboardLanding'; import type { CloudPulseCustomSelectProps } from '../shared/CloudPulseCustomSelect'; +import type { CloudPulseEndpointsSelectProps } from '../shared/CloudPulseEndpointsSelect'; import type { CloudPulseEndpoints } from '../shared/CloudPulseEndpointsSelect'; import type { CloudPulseNodeTypeFilterProps } from '../shared/CloudPulseNodeTypeFilter'; import type { CloudPulseRegionSelectProps } from '../shared/CloudPulseRegionSelect'; @@ -360,6 +361,39 @@ export const getTextFilterProperties = ( }; }; +export const getEndpointsProperties = ( + props: CloudPulseFilterProperties, + handleEndpointsChange: (endpoints: string[], savePref?: boolean) => void +): CloudPulseEndpointsSelectProps => { + const { filterKey, name: label, placeholder } = props.config.configuration; + const { + config, + dashboard, + dependentFilters, + isServiceAnalyticsIntegration, + preferences, + shouldDisable, + } = props; + return { + defaultValue: preferences?.[config.configuration.filterKey], + disabled: + shouldDisable || + shouldDisableFilterByFilterKey( + filterKey, + dependentFilters ?? {}, + dashboard, + preferences + ), + handleEndpointsSelection: handleEndpointsChange, + label, + placeholder, + serviceType: dashboard.service_type, + region: dependentFilters?.[REGION], + savePreferences: !isServiceAnalyticsIntegration, + xFilter: filterBasedOnConfig(config, dependentFilters ?? {}), + }; +}; + /** * This function helps in builder the xFilter needed to passed in a apiV4 call * @@ -652,25 +686,28 @@ export const getFilters = ( * @param dependentFilters The selected dependent filters that will be used to filter the resources * @returns The filtered resources */ -export const filterUsingDependentFilters = ( - data?: CloudPulseResources[], +export function filterUsingDependentFilters( + data?: T[], dependentFilters?: CloudPulseMetricsFilter -): CloudPulseResources[] | undefined => { +): T[] | undefined { if (!dependentFilters || !data) { return data; } return data.filter((resource) => { return Object.entries(dependentFilters).every(([key, filterValue]) => { - const resourceValue = resource[key as keyof CloudPulseResources]; + const resourceValue = resource[key as keyof T]; if (Array.isArray(resourceValue) && Array.isArray(filterValue)) { return filterValue.some((val) => resourceValue.includes(String(val))); - } else if (Array.isArray(resourceValue)) { + } + if (Array.isArray(resourceValue)) { return resourceValue.includes(String(filterValue)); - } else { - return resourceValue === filterValue; } + if (Array.isArray(filterValue)) { + return (filterValue as string[]).includes(String(resourceValue)); + } + return resourceValue === filterValue; }); }); } diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx index 0f89b33eebd..d742de0da29 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx @@ -278,7 +278,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { isFlags: Boolean(flags && !isJweTokenFetching), label: widget.label, timeStamp, - url: flags.aclpReadEndpoint!, + url: 'https://mr-devcloud.cloud-observability-dev.akadns.net/v2beta/monitor/services/', } ); let data: DataSet[] = []; diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseComponentRenderer.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseComponentRenderer.tsx index 220b36f03bb..75ab29be939 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseComponentRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseComponentRenderer.tsx @@ -5,6 +5,7 @@ import NullComponent from 'src/components/NullComponent'; import { CloudPulseCustomSelect } from './CloudPulseCustomSelect'; import { CloudPulseDateTimeRangePicker } from './CloudPulseDateTimeRangePicker'; +import { CloudPulseEndpointsSelect } from './CloudPulseEndpointsSelect'; import { CloudPulseNodeTypeFilter } from './CloudPulseNodeTypeFilter'; import { CloudPulseRegionSelect } from './CloudPulseRegionSelect'; import { CloudPulseResourcesSelect } from './CloudPulseResourcesSelect'; @@ -13,6 +14,7 @@ import { CloudPulseTextFilter } from './CloudPulseTextFilter'; import type { CloudPulseCustomSelectProps } from './CloudPulseCustomSelect'; import type { CloudPulseDateTimeRangePickerProps } from './CloudPulseDateTimeRangePicker'; +import type { CloudPulseEndpointsSelectProps } from './CloudPulseEndpointsSelect'; import type { CloudPulseNodeTypeFilterProps } from './CloudPulseNodeTypeFilter'; import type { CloudPulseRegionSelectProps } from './CloudPulseRegionSelect'; import type { CloudPulseResourcesSelectProps } from './CloudPulseResourcesSelect'; @@ -24,6 +26,7 @@ export interface CloudPulseComponentRendererProps { componentProps: | CloudPulseCustomSelectProps | CloudPulseDateTimeRangePickerProps + | CloudPulseEndpointsSelectProps | CloudPulseNodeTypeFilterProps | CloudPulseRegionSelectProps | CloudPulseResourcesSelectProps @@ -37,6 +40,7 @@ const Components: { React.ComponentType< | CloudPulseCustomSelectProps | CloudPulseDateTimeRangePickerProps + | CloudPulseEndpointsSelectProps | CloudPulseNodeTypeFilterProps | CloudPulseRegionSelectProps | CloudPulseResourcesSelectProps @@ -54,6 +58,7 @@ const Components: { resource_id: CloudPulseResourcesSelect, tags: CloudPulseTagsSelect, associated_entity_region: CloudPulseRegionSelect, + endpoint: CloudPulseEndpointsSelect, }; const buildComponent = (props: CloudPulseComponentRendererProps) => { diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx index 7f238d26787..cacea84c427 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx @@ -10,6 +10,7 @@ import NullComponent from 'src/components/NullComponent'; import RenderComponent from '../shared/CloudPulseComponentRenderer'; import { DASHBOARD_ID, + ENDPOINT, INTERFACE_ID, LINODE_REGION, NODE_TYPE, @@ -21,6 +22,7 @@ import { } from '../Utils/constants'; import { getCustomSelectProperties, + getEndpointsProperties, getFilters, getNodeTypeProperties, getRegionProperties, @@ -234,6 +236,7 @@ export const CloudPulseDashboardFilterBuilder = React.memo( filterKey === REGION ? { [filterKey]: region, + [ENDPOINT]: undefined, [RESOURCES]: undefined, [TAGS]: undefined, } @@ -251,6 +254,16 @@ export const CloudPulseDashboardFilterBuilder = React.memo( [emitFilterChangeByFilterKey] ); + const handleEndpointsChange = React.useCallback( + (endpoints: string[], savePref: boolean = false) => { + emitFilterChangeByFilterKey(ENDPOINT, endpoints, endpoints, savePref, { + [ENDPOINT]: endpoints, + [RESOURCE_ID]: undefined, + }); + }, + [emitFilterChangeByFilterKey] + ); + const handleCustomSelectChange = React.useCallback( ( filterKey: string, @@ -356,6 +369,18 @@ export const CloudPulseDashboardFilterBuilder = React.memo( }, handleTextFilterChange ); + } else if (config.configuration.filterKey === ENDPOINT) { + return getEndpointsProperties( + { + config, + dashboard, + dependentFilters: dependentFilterReference.current, + isServiceAnalyticsIntegration, + preferences, + shouldDisable: isError || isLoading, + }, + handleEndpointsChange + ); } else { return getCustomSelectProperties( { @@ -379,6 +404,7 @@ export const CloudPulseDashboardFilterBuilder = React.memo( handleRegionChange, handleTextFilterChange, handleResourceChange, + handleEndpointsChange, handleCustomSelectChange, isServiceAnalyticsIntegration, preferences, diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx index 7d9c427e6da..fa219c87f59 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx @@ -8,6 +8,7 @@ import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel'; import { TabPanels } from 'src/components/Tabs/TabPanels'; import { Tabs } from 'src/components/Tabs/Tabs'; import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList'; +import { CloudPulseDashboardWithFilters } from 'src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters'; import { useIsObjectStorageGen2Enabled } from 'src/features/ObjectStorage/hooks/useIsObjectStorageGen2Enabled'; import { useTabs } from 'src/hooks/useTabs'; import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; @@ -55,6 +56,10 @@ export const BucketDetailLanding = React.memo(() => { title: 'SSL/TLS', to: `/object-storage/buckets/$clusterId/$bucketName/ssl`, }, + { + title: 'Metrics', + to: `/object-storage/buckets/$clusterId/$bucketName/metrics`, + }, ]); return ( @@ -95,6 +100,14 @@ export const BucketDetailLanding = React.memo(() => { + + + diff --git a/packages/manager/src/routes/objectStorage/index.ts b/packages/manager/src/routes/objectStorage/index.ts index 9f058a5ccb3..73b5ce23616 100644 --- a/packages/manager/src/routes/objectStorage/index.ts +++ b/packages/manager/src/routes/objectStorage/index.ts @@ -89,6 +89,15 @@ const objectStorageBucketDetailAccessRoute = createRoute({ ).then((m) => m.bucketDetailLandingLazyRoute) ); +const objectStorageBucketMetricsRoute = createRoute({ + getParentRoute: () => objectStorageBucketDetailRoute, + path: 'metrics', +}).lazy(() => + import( + 'src/features/ObjectStorage/BucketDetail/bucketDetailLandingLazyRoute' + ).then((m) => m.bucketDetailLandingLazyRoute) +); + const objectStorageBucketSSLRoute = createRoute({ getParentRoute: () => objectStorageBucketDetailRoute, path: 'ssl', @@ -108,5 +117,6 @@ export const objectStorageRouteTree = objectStorageRoute.addChildren([ objectStorageBucketDetailObjectsRoute, objectStorageBucketDetailAccessRoute, objectStorageBucketSSLRoute, + objectStorageBucketMetricsRoute, ]), ]); From cc1b8b3651f272403d961588dd0b8ddc99074022 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Wed, 17 Sep 2025 10:57:33 +0530 Subject: [PATCH 04/15] [DI-26882] - Make prop otional --- packages/api-v4/src/cloudpulse/types.ts | 2 +- .../src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx | 2 +- .../src/features/CloudPulse/Widget/CloudPulseWidget.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api-v4/src/cloudpulse/types.ts b/packages/api-v4/src/cloudpulse/types.ts index bae8d0b9a3d..110fea9476a 100644 --- a/packages/api-v4/src/cloudpulse/types.ts +++ b/packages/api-v4/src/cloudpulse/types.ts @@ -133,7 +133,7 @@ export interface Dimension { } export interface JWETokenPayLoad { - entity_ids: number[] | undefined; + entity_ids?: number[]; } export interface JWEToken { diff --git a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx index ca3d42846d4..66b18365646 100644 --- a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx +++ b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx @@ -90,7 +90,7 @@ export const CloudPulseDashboard = (props: DashboardProperties) => { dashboardId: number | undefined ): JWETokenPayLoad => { if (!dashboardId || dashboardId === 6) { - return { entity_ids: undefined }; + return {}; } return { entity_ids: resources?.map((resource) => Number(resource)) ?? [], diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx index d742de0da29..be0ccf72d2c 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx @@ -92,7 +92,7 @@ export interface CloudPulseWidgetProperties { linodeRegion?: string; /** - * selected region for the widget + * Selected region for the widget */ region?: string; @@ -278,7 +278,7 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { isFlags: Boolean(flags && !isJweTokenFetching), label: widget.label, timeStamp, - url: 'https://mr-devcloud.cloud-observability-dev.akadns.net/v2beta/monitor/services/', + url: flags.aclpReadEndpoint!, } ); let data: DataSet[] = []; From a3134a4fee211b82471a772d787205226e5859a8 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Thu, 25 Sep 2025 10:48:51 +0530 Subject: [PATCH 05/15] [DI-26882] - Add tests for endpoints props, update comments --- .../CloudPulse/Utils/FilterBuilder.test.ts | 40 +++++++++++++++++++ .../CloudPulse/Utils/FilterBuilder.ts | 9 +++++ 2 files changed, 49 insertions(+) diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts index 421ce370abe..31633ad8958 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts @@ -13,6 +13,7 @@ import { filterBasedOnConfig, filterEndpointsUsingRegion, filterUsingDependentFilters, + getEndpointsProperties, getFilters, getTextFilterProperties, } from './FilterBuilder'; @@ -46,6 +47,13 @@ const firewallConfig = FILTER_CONFIG.get(4); const dbaasDashboard = dashboardFactory.build({ service_type: 'dbaas', id: 1 }); +const objectStorageBucketDashboard = dashboardFactory.build({ + service_type: 'objectstorage', + id: 6, +}); + +const objectStorageBucketConfig = FILTER_CONFIG.get(6); + it('test getRegionProperties method', () => { const regionConfig = linodeConfig?.filters.find( (filterObj) => filterObj.name === 'Region' @@ -408,6 +416,38 @@ it('test getTextFilterProperties method for interface_id', () => { } }); +it('test getEndpointsProperties method', () => { + const endpointsConfig = objectStorageBucketConfig?.filters.find( + (filterObj) => filterObj.name === 'Endpoints' + ); + + expect(endpointsConfig).toBeDefined(); + + if (endpointsConfig) { + const endpointsProperties = getEndpointsProperties( + { + config: endpointsConfig, + dashboard: objectStorageBucketDashboard, + dependentFilters: { region: 'us-east' }, + isServiceAnalyticsIntegration: false, + }, + vi.fn() + ); + + expect(endpointsProperties).toBeDefined(); + expect(endpointsProperties.label).toEqual( + endpointsConfig.configuration.name + ); + expect(endpointsProperties.serviceType).toEqual('objectstorage'); + expect(endpointsProperties.savePreferences).toEqual(true); + expect(endpointsProperties.disabled).toEqual(false); + expect(endpointsProperties.handleEndpointsSelection).toBeDefined(); + expect(endpointsProperties.defaultValue).toEqual(undefined); + expect(endpointsProperties.region).toEqual('us-east'); + expect(endpointsProperties.xFilter).toEqual({ region: 'us-east' }); + } +}); + it('test getFiltersForMetricsCallFromCustomSelect method', () => { const result = getMetricsCallCustomFilters( { diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts index 33996ec55b5..9ce4f229865 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts @@ -361,6 +361,15 @@ export const getTextFilterProperties = ( }; }; +/** + * This function helps in building the properties needed for endpoints selection component + * + * @param config - accepts a CloudPulseServiceTypeFilters of endpoints key + * @param handleEndpointsChange - the callback when we select new endpoints + * @param dashboard - the actual selected dashboard + * @param isServiceAnalyticsIntegration - only if this is false, we need to save preferences , else no need + * @returns CloudPulseEndpointsSelectProps + */ export const getEndpointsProperties = ( props: CloudPulseFilterProperties, handleEndpointsChange: (endpoints: string[], savePref?: boolean) => void From b1756232ba89c6d7fccea53ead33c1c58c73848e Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Wed, 17 Sep 2025 11:28:06 +0530 Subject: [PATCH 06/15] [DI-26882] - Update test case --- .../CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts index 73112dbd944..0d153ecfeab 100644 --- a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts @@ -20,11 +20,13 @@ it('test getDashboardProperties method', () => { filterValue: { region: 'us-east' }, resource: 1, groupBy: [], + region: 'us-east', }); expect(result).toBeDefined(); expect(result.dashboardId).toEqual(mockDashboard.id); expect(result.resources).toEqual(['1']); + expect(result.region).toEqual('us-east'); }); it('test checkMandatoryFiltersSelected method for time duration and resource', () => { From e57821926a6247455ab3bddcf9505f71089e67c6 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Fri, 19 Sep 2025 09:37:38 +0530 Subject: [PATCH 07/15] [DI-26882] - Review suggestions --- .../Dashboard/CloudPulseDashboard.tsx | 20 +++++++++---- .../Dashboard/CloudPulseDashboardRenderer.tsx | 1 + .../Utils/CloudPulseWidgetUtils.test.ts | 8 ++--- .../CloudPulse/Utils/CloudPulseWidgetUtils.ts | 29 +++++++------------ .../ReusableDashboardFilterUtils.test.ts | 9 +++--- .../Utils/ReusableDashboardFilterUtils.ts | 3 +- .../CloudPulse/Widget/CloudPulseWidget.tsx | 2 +- 7 files changed, 38 insertions(+), 34 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx index 66b18365646..3403f984a8a 100644 --- a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx +++ b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx @@ -17,7 +17,11 @@ import { } from '../Widget/CloudPulseWidgetRenderer'; import type { CloudPulseMetricsAdditionalFilters } from '../Widget/CloudPulseWidget'; -import type { DateTimeWithPreset, JWETokenPayLoad } from '@linode/api-v4'; +import type { + CloudPulseServiceType, + DateTimeWithPreset, + JWETokenPayLoad, +} from '@linode/api-v4'; export interface DashboardProperties { /** @@ -65,6 +69,11 @@ export interface DashboardProperties { */ savePref?: boolean; + /** + * Selected service type for the dashboard + */ + serviceType: CloudPulseServiceType; + /** * Selected tags for the dashboard */ @@ -79,6 +88,7 @@ export const CloudPulseDashboard = (props: DashboardProperties) => { manualRefreshTimeStamp, resources, savePref, + serviceType, groupBy, linodeRegion, region, @@ -86,10 +96,8 @@ export const CloudPulseDashboard = (props: DashboardProperties) => { const { preferences } = useAclpPreference(); - const getJweTokenPayload = ( - dashboardId: number | undefined - ): JWETokenPayLoad => { - if (!dashboardId || dashboardId === 6) { + const getJweTokenPayload = (): JWETokenPayLoad => { + if (serviceType === 'objectstorage') { return {}; } return { @@ -129,7 +137,7 @@ export const CloudPulseDashboard = (props: DashboardProperties) => { isFetching: isJweTokenFetching, } = useCloudPulseJWEtokenQuery( dashboard?.service_type, - getJweTokenPayload(dashboard?.id), + getJweTokenPayload(), Boolean(resources) && !isDashboardLoading && !isDashboardApiError ); diff --git a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardRenderer.tsx b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardRenderer.tsx index d0868b7b15e..5474c73dcce 100644 --- a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardRenderer.tsx +++ b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardRenderer.tsx @@ -85,6 +85,7 @@ export const CloudPulseDashboardRenderer = React.memo( : [] } savePref={true} + serviceType={dashboard.service_type} tags={ filterValue[TAGS] && Array.isArray(filterValue[TAGS]) ? (filterValue[TAGS] as string[]) diff --git a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts index 7153b0e8a5f..dfb98e4d340 100644 --- a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.test.ts @@ -266,22 +266,22 @@ describe('getTimeDurationFromPreset method', () => { }); describe('getEntityIds method', () => { - it('should return entity ids for dashboard id 6', () => { + it('should return entity ids for linode service type', () => { const result = getEntityIds( [{ id: '123', label: 'linode-1' }], ['123'], widgetFactory.build(), - 2 + 'linode' ); expect(result).toEqual([123]); }); - it('should return entity ids for dashboard id 2', () => { + it('should return entity ids for objectstorage service type', () => { const result = getEntityIds( [{ id: 'bucket-1', label: 'bucket-name-1' }], ['bucket-1'], widgetFactory.build(), - 6 + 'objectstorage' ); expect(result).toEqual(['bucket-1']); }); diff --git a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts index bab6c68dc6f..15285321937 100644 --- a/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/CloudPulseWidgetUtils.ts @@ -111,11 +111,6 @@ interface GraphDataOptionsProps { } interface MetricRequestProps { - /** - * id of the selected dashboard - */ - dashboardId: number; - /** * time duration for the metrics data */ @@ -143,6 +138,11 @@ interface MetricRequestProps { */ resources: CloudPulseResources[]; + /** + * service type of the widget + */ + serviceType: CloudPulseServiceType; + /** * widget filters for metrics data */ @@ -343,16 +343,16 @@ export const getCloudPulseMetricRequest = ( widget, groupBy, linodeRegion, - dashboardId, region, + serviceType, } = props; const preset = duration.preset; - const metricsRequest: CloudPulseMetricsRequest = { + return { absolute_time_duration: preset !== 'reset' && preset !== 'this month' && preset !== 'last month' ? undefined : { end: duration.end, start: duration.start }, - entity_ids: getEntityIds(resources, entityIds, widget, dashboardId), + entity_ids: getEntityIds(resources, entityIds, widget, serviceType), filters: undefined, group_by: !groupBy?.length ? undefined : groupBy, relative_time_duration: getTimeDurationFromPreset(preset), @@ -370,15 +370,8 @@ export const getCloudPulseMetricRequest = ( value: widget.time_granularity.value, }, associated_entity_region: linodeRegion, + entity_region: serviceType === 'objectstorage' ? region : undefined, }; - - if (dashboardId === 6) { - return { - ...metricsRequest, - entity_region: region, - }; - } - return metricsRequest; }; /** @@ -392,9 +385,9 @@ export const getEntityIds = ( resources: CloudPulseResources[], entityIds: string[], widget: Widgets, - dashboardId: number + serviceType: CloudPulseServiceType ) => { - if (dashboardId === 6) { + if (serviceType === 'objectstorage') { return entityIds; } return resources diff --git a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts index 0d153ecfeab..1f68344cf67 100644 --- a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.test.ts @@ -25,6 +25,7 @@ it('test getDashboardProperties method', () => { expect(result).toBeDefined(); expect(result.dashboardId).toEqual(mockDashboard.id); + expect(result.serviceType).toEqual(mockDashboard.service_type); expect(result.resources).toEqual(['1']); expect(result.region).toEqual('us-east'); }); @@ -95,9 +96,9 @@ it('test checkMandatoryFiltersSelected method for role', () => { expect(result).toBe(true); }); -it('checkMandatoryFiltersSelected method should return false if no region is selected for dashboard id 6', () => { +it('checkMandatoryFiltersSelected method should return false if no region is selected for objectstorage service type', () => { const result = checkMandatoryFiltersSelected({ - dashboardObj: { ...mockDashboard, id: 6 }, + dashboardObj: { ...mockDashboard, service_type: 'objectstorage', id: 6 }, filterValue: {}, resource: 1, timeDuration: { end: end.toISO(), preset, start: start.toISO() }, @@ -106,9 +107,9 @@ it('checkMandatoryFiltersSelected method should return false if no region is sel expect(result).toBe(false); }); -it('checkMandatoryFiltersSelected method should return true if region is selected for dashboard id 6', () => { +it('checkMandatoryFiltersSelected method should return true if region is selected for objectstorage service type', () => { const result = checkMandatoryFiltersSelected({ - dashboardObj: { ...mockDashboard, id: 6 }, + dashboardObj: { ...mockDashboard, service_type: 'objectstorage', id: 6 }, filterValue: {}, resource: 1, timeDuration: { end: end.toISO(), preset, start: start.toISO() }, diff --git a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts index 15a8b5b7354..edce614f656 100644 --- a/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/ReusableDashboardFilterUtils.ts @@ -56,6 +56,7 @@ export const getDashboardProperties = ( dashboardId: dashboardObj.id, duration: timeDuration ?? defaultTimeDuration(), resources: [String(resource)], + serviceType: dashboardObj.service_type, savePref: false, groupBy, region, @@ -80,7 +81,7 @@ export const checkMandatoryFiltersSelected = ( return false; } - if (dashboardObj.id === 6 && !region) { + if (dashboardObj.service_type === 'objectstorage' && !region) { return false; } diff --git a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx index be0ccf72d2c..c717a0b44d9 100644 --- a/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx +++ b/packages/manager/src/features/CloudPulse/Widget/CloudPulseWidget.tsx @@ -268,8 +268,8 @@ export const CloudPulseWidget = (props: CloudPulseWidgetProperties) => { widget, groupBy: [...(widgetProp.group_by ?? []), ...groupBy], linodeRegion, - dashboardId, region, + serviceType, }), filters, // any additional dimension filters will be constructed and passed here }, From d8258a92bdcde41575591fa20f5f3613b5903c5b Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Thu, 25 Sep 2025 10:54:40 +0530 Subject: [PATCH 08/15] [DI-26882] - Simplify props, add new func --- .../CloudPulseDashboardWithFilters.tsx | 8 +++++- .../CloudPulse/Utils/FilterBuilder.test.ts | 28 ++++++++++++------- .../CloudPulse/Utils/FilterBuilder.ts | 8 +++--- .../CloudPulseDashboardFilterBuilder.tsx | 8 +++--- .../src/queries/cloudpulse/resources.ts | 4 +-- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx index 1763d73ef30..e5dbc76f886 100644 --- a/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx +++ b/packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters.tsx @@ -185,7 +185,13 @@ export const CloudPulseDashboardWithFilters = React.memo( emitFilterChange={onFilterChange} handleToggleAppliedFilter={toggleAppliedFilter} isServiceAnalyticsIntegration - resource_ids={[resource]} + resource_ids={ + dashboard.service_type !== 'objectstorage' + ? typeof resource === 'number' + ? [resource] + : undefined + : undefined + } /> )} { }, vi.fn() ); + const { + label, + serviceType, + disabled, + savePreferences, + handleEndpointsSelection, + defaultValue, + region, + xFilter, + } = endpointsProperties; expect(endpointsProperties).toBeDefined(); - expect(endpointsProperties.label).toEqual( - endpointsConfig.configuration.name - ); - expect(endpointsProperties.serviceType).toEqual('objectstorage'); - expect(endpointsProperties.savePreferences).toEqual(true); - expect(endpointsProperties.disabled).toEqual(false); - expect(endpointsProperties.handleEndpointsSelection).toBeDefined(); - expect(endpointsProperties.defaultValue).toEqual(undefined); - expect(endpointsProperties.region).toEqual('us-east'); - expect(endpointsProperties.xFilter).toEqual({ region: 'us-east' }); + expect(label).toEqual(endpointsConfig.configuration.name); + expect(serviceType).toEqual('objectstorage'); + expect(savePreferences).toEqual(true); + expect(disabled).toEqual(false); + expect(handleEndpointsSelection).toBeDefined(); + expect(defaultValue).toEqual(undefined); + expect(region).toEqual('us-east'); + expect(xFilter).toEqual({ region: 'us-east' }); } }); diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts index 9ce4f229865..c6357202bcb 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts @@ -695,17 +695,17 @@ export const getFilters = ( * @param dependentFilters The selected dependent filters that will be used to filter the resources * @returns The filtered resources */ -export function filterUsingDependentFilters( - data?: T[], +export function filterUsingDependentFilters( + data?: CloudPulseResources[], dependentFilters?: CloudPulseMetricsFilter -): T[] | undefined { +): CloudPulseResources[] | undefined { if (!dependentFilters || !data) { return data; } return data.filter((resource) => { return Object.entries(dependentFilters).every(([key, filterValue]) => { - const resourceValue = resource[key as keyof T]; + const resourceValue = resource[key as keyof CloudPulseResources]; if (Array.isArray(resourceValue) && Array.isArray(filterValue)) { return filterValue.some((val) => resourceValue.includes(String(val))); diff --git a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx index cacea84c427..81a4f46c7d1 100644 --- a/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx +++ b/packages/manager/src/features/CloudPulse/shared/CloudPulseDashboardFilterBuilder.tsx @@ -84,7 +84,7 @@ export interface CloudPulseDashboardFilterBuilderProps { /** * selected resource ids */ - resource_ids?: (number | string)[]; + resource_ids?: number[]; } export const CloudPulseDashboardFilterBuilder = React.memo( @@ -341,12 +341,12 @@ export const CloudPulseDashboardFilterBuilder = React.memo( config, dashboard, dependentFilters: resource_ids?.length - ? { [RESOURCE_ID]: resource_ids as number[] } + ? { [RESOURCE_ID]: resource_ids } : dependentFilterReference.current, isServiceAnalyticsIntegration, preferences, resource_ids: resource_ids?.length - ? (resource_ids as number[]) + ? resource_ids : ( dependentFilterReference.current[RESOURCE_ID] as string[] )?.map((id: string) => Number(id)), @@ -387,7 +387,7 @@ export const CloudPulseDashboardFilterBuilder = React.memo( config, dashboard, dependentFilters: resource_ids?.length - ? { [RESOURCE_ID]: resource_ids as number[] } + ? { [RESOURCE_ID]: resource_ids } : dependentFilterReference.current, isServiceAnalyticsIntegration, preferences, diff --git a/packages/manager/src/queries/cloudpulse/resources.ts b/packages/manager/src/queries/cloudpulse/resources.ts index c7092d20cac..b688f8f5a41 100644 --- a/packages/manager/src/queries/cloudpulse/resources.ts +++ b/packages/manager/src/queries/cloudpulse/resources.ts @@ -38,12 +38,10 @@ export const useResourcesQuery = ( resourceType === 'objectstorage' ? resource.hostname : String(resource.id); - const label = - resourceType === 'objectstorage' ? resource.hostname : resource.label; return { engineType: resource.engine, id, - label, + label: resourceType === 'objectstorage' ? id : resource.label, region: resource.region, regions: resource.regions ? resource.regions : [], tags: resource.tags, From d610586bf29448dcde0436cf31a81610ba8dfb85 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Fri, 19 Sep 2025 13:55:30 +0530 Subject: [PATCH 09/15] [DI-26882] - Update unit tes --- .../src/features/CloudPulse/Utils/FilterBuilder.test.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts index fdaee74987a..c4fcb62af9a 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.test.ts @@ -575,12 +575,6 @@ describe('filterUsingDependentFilters', () => { expect(result).toEqual([mockData[0], mockData[1]]); }); - it('should filter when resource value is a string and filter value is an array', () => { - const filters = { region: ['us-east', 'us-west'] }; - const result = filterUsingDependentFilters(mockData, filters); - expect(result).toEqual([mockData[0], mockData[1]]); - }); - it('should return empty array if no resource matches', () => { const filters = { region: 'us-central' }; const result = filterUsingDependentFilters(mockData, filters); From 217472dd73d8bfa16ad190f1669762720be03559 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Fri, 19 Sep 2025 13:57:38 +0530 Subject: [PATCH 10/15] [DI-26882] - Update util --- .../manager/src/features/CloudPulse/Utils/FilterBuilder.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts index c6357202bcb..a875dec9481 100644 --- a/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts +++ b/packages/manager/src/features/CloudPulse/Utils/FilterBuilder.ts @@ -695,10 +695,10 @@ export const getFilters = ( * @param dependentFilters The selected dependent filters that will be used to filter the resources * @returns The filtered resources */ -export function filterUsingDependentFilters( +export const filterUsingDependentFilters = ( data?: CloudPulseResources[], dependentFilters?: CloudPulseMetricsFilter -): CloudPulseResources[] | undefined { +): CloudPulseResources[] | undefined => { if (!dependentFilters || !data) { return data; } From 2b241ed88375d1f879d399d7038086159bbf1de4 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Mon, 22 Sep 2025 12:37:19 +0530 Subject: [PATCH 11/15] [DI-26882] - Fix linting --- packages/utilities/src/__data__/regionsData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utilities/src/__data__/regionsData.ts b/packages/utilities/src/__data__/regionsData.ts index bb5a626c749..6b1f916e968 100644 --- a/packages/utilities/src/__data__/regionsData.ts +++ b/packages/utilities/src/__data__/regionsData.ts @@ -13,7 +13,7 @@ export const regions: Region[] = [ 'VPCs', 'Block Storage Migrations', 'Managed Databases', - 'Object Storage' + 'Object Storage', ], country: 'in', id: 'ap-west', From efa9170e549f4fddac5e3bf9202456b118b9332e Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Thu, 25 Sep 2025 12:17:10 +0530 Subject: [PATCH 12/15] [DI-26882] - Add changesets --- packages/api-v4/.changeset/pr-12912-changed-1758782562755.md | 5 +++++ .../.changeset/pr-12912-upcoming-features-1758782466180.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 packages/api-v4/.changeset/pr-12912-changed-1758782562755.md create mode 100644 packages/manager/.changeset/pr-12912-upcoming-features-1758782466180.md diff --git a/packages/api-v4/.changeset/pr-12912-changed-1758782562755.md b/packages/api-v4/.changeset/pr-12912-changed-1758782562755.md new file mode 100644 index 00000000000..a169768c34a --- /dev/null +++ b/packages/api-v4/.changeset/pr-12912-changed-1758782562755.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Changed +--- + +CloudPulse-Metrics: Update `CloudPulseMetricsRequest` and `JWETokenPayLoad` type at `types.ts` ([#12912](https://github.com/linode/manager/pull/12912)) diff --git a/packages/manager/.changeset/pr-12912-upcoming-features-1758782466180.md b/packages/manager/.changeset/pr-12912-upcoming-features-1758782466180.md new file mode 100644 index 00000000000..f7705437af0 --- /dev/null +++ b/packages/manager/.changeset/pr-12912-upcoming-features-1758782466180.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +CloudPulse-Metrics: Handle special conditions for `objectstorage` service addition, add related filters at `FilterConfig.ts`, integrate related component `CloudPulseEndpointsSelect.tsx` ([#12912](https://github.com/linode/manager/pull/12912)) From 0ed7b5375356d986343b65012a3d44323f23f41d Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Thu, 25 Sep 2025 15:42:31 +0530 Subject: [PATCH 13/15] [DI-26882] - Update queryKeys --- packages/manager/src/queries/cloudpulse/queries.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/manager/src/queries/cloudpulse/queries.ts b/packages/manager/src/queries/cloudpulse/queries.ts index 59187f6417e..1ac6118c044 100644 --- a/packages/manager/src/queries/cloudpulse/queries.ts +++ b/packages/manager/src/queries/cloudpulse/queries.ts @@ -133,8 +133,8 @@ export const queryFactory = createQueryKeys(key, { return { queryFn: () => getAllBuckets(), queryKey: [ - objectStorageQueries.buckets.queryKey, - objectStorageQueries.endpoints.queryKey, + ...objectStorageQueries.endpoints.queryKey, + objectStorageQueries.buckets.queryKey[1], ], }; case 'volumes': From dd53e47f1b08839018f2fac3bf5ab3d3c5181815 Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Thu, 25 Sep 2025 19:45:37 +0530 Subject: [PATCH 14/15] [DI-26882] - Remove type - any --- packages/manager/src/queries/cloudpulse/resources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/manager/src/queries/cloudpulse/resources.ts b/packages/manager/src/queries/cloudpulse/resources.ts index 4ae9bd6db52..074c6c98eb9 100644 --- a/packages/manager/src/queries/cloudpulse/resources.ts +++ b/packages/manager/src/queries/cloudpulse/resources.ts @@ -15,7 +15,7 @@ export const useResourcesQuery = ( ...queryFactory.resources(resourceType, params, filters), enabled, retry: resourceType === 'objectstorage' ? false : 3, - select: (resources: any[]) => { + select: (resources) => { if (!enabled) { return []; // Return empty array if the query is not enabled } From 1665682a4954ecd7c819e53001c8d6f017b518fd Mon Sep 17 00:00:00 2001 From: ankitaakamai Date: Mon, 29 Sep 2025 20:31:24 +0530 Subject: [PATCH 15/15] upcoming: [DI-26882] - Remove temporary changes --- .../features/ObjectStorage/BucketDetail/index.tsx | 13 ------------- packages/manager/src/routes/objectStorage/index.ts | 10 ---------- 2 files changed, 23 deletions(-) diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx index fa219c87f59..7d9c427e6da 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx @@ -8,7 +8,6 @@ import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel'; import { TabPanels } from 'src/components/Tabs/TabPanels'; import { Tabs } from 'src/components/Tabs/Tabs'; import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList'; -import { CloudPulseDashboardWithFilters } from 'src/features/CloudPulse/Dashboard/CloudPulseDashboardWithFilters'; import { useIsObjectStorageGen2Enabled } from 'src/features/ObjectStorage/hooks/useIsObjectStorageGen2Enabled'; import { useTabs } from 'src/hooks/useTabs'; import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; @@ -56,10 +55,6 @@ export const BucketDetailLanding = React.memo(() => { title: 'SSL/TLS', to: `/object-storage/buckets/$clusterId/$bucketName/ssl`, }, - { - title: 'Metrics', - to: `/object-storage/buckets/$clusterId/$bucketName/metrics`, - }, ]); return ( @@ -100,14 +95,6 @@ export const BucketDetailLanding = React.memo(() => { - - - diff --git a/packages/manager/src/routes/objectStorage/index.ts b/packages/manager/src/routes/objectStorage/index.ts index 73b5ce23616..9f058a5ccb3 100644 --- a/packages/manager/src/routes/objectStorage/index.ts +++ b/packages/manager/src/routes/objectStorage/index.ts @@ -89,15 +89,6 @@ const objectStorageBucketDetailAccessRoute = createRoute({ ).then((m) => m.bucketDetailLandingLazyRoute) ); -const objectStorageBucketMetricsRoute = createRoute({ - getParentRoute: () => objectStorageBucketDetailRoute, - path: 'metrics', -}).lazy(() => - import( - 'src/features/ObjectStorage/BucketDetail/bucketDetailLandingLazyRoute' - ).then((m) => m.bucketDetailLandingLazyRoute) -); - const objectStorageBucketSSLRoute = createRoute({ getParentRoute: () => objectStorageBucketDetailRoute, path: 'ssl', @@ -117,6 +108,5 @@ export const objectStorageRouteTree = objectStorageRoute.addChildren([ objectStorageBucketDetailObjectsRoute, objectStorageBucketDetailAccessRoute, objectStorageBucketSSLRoute, - objectStorageBucketMetricsRoute, ]), ]);