forked from linode/manager
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
upcoming: [DI-19610] - Added CloudPulse widget to show graph for diff…
…erent metrices (linode#10676) Co-authored-by: Hana Xu <[email protected]> Co-authored-by: Jaalah Ramos <[email protected]> Co-authored-by: Jaalah Ramos <[email protected]>
- Loading branch information
1 parent
1cc9de6
commit 8d1ad97
Showing
27 changed files
with
1,244 additions
and
49 deletions.
There are no files selected for viewing
5 changes: 5 additions & 0 deletions
5
packages/api-v4/.changeset/pr-10676-upcoming-features-1721049858027.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/api-v4": Upcoming Features | ||
--- | ||
|
||
Added MetricDefinitions, Dimension, JWETokenPayload, JWEToken and metricDefinitions, dashboard by id and jwe token api calls ([#10676](https://github.com/linode/manager/pull/10676)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
export * from './types'; | ||
|
||
export * from './dashboards'; | ||
|
||
export * from './services'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { API_ROOT } from 'src/constants'; | ||
import Request, { setData, setMethod, setURL } from '../request'; | ||
import { JWEToken, JWETokenPayLoad, MetricDefinitions } from './types'; | ||
import { ResourcePage as Page } from 'src/types'; | ||
|
||
export const getMetricDefinitionsByServiceType = (serviceType: string) => { | ||
return Request<Page<MetricDefinitions>>( | ||
setURL( | ||
`${API_ROOT}/monitor/services/${encodeURIComponent( | ||
serviceType | ||
)}/metric-definitions` | ||
), | ||
setMethod('GET') | ||
); | ||
}; | ||
|
||
export const getJWEToken = (data: JWETokenPayLoad, serviceType: string) => | ||
Request<JWEToken>( | ||
setURL( | ||
`${API_ROOT}/monitor/services/${encodeURIComponent(serviceType)}/token` | ||
), | ||
setMethod('POST'), | ||
setData(data) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
packages/manager/.changeset/pr-10676-upcoming-features-1721049757333.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@linode/manager": Upcoming Features | ||
--- | ||
|
||
Added widget component in the UI for metrics data ([#10676](https://github.com/linode/manager/pull/10676)) |
5 changes: 5 additions & 0 deletions
5
packages/manager/src/assets/icons/entityIcons/cv_overview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
241 changes: 241 additions & 0 deletions
241
packages/manager/src/features/CloudPulse/Dashboard/CloudPulseDashboard.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
import { Grid, Paper } from '@mui/material'; | ||
import React from 'react'; | ||
|
||
import CloudPulseIcon from 'src/assets/icons/entityIcons/cv_overview.svg'; | ||
import { CircleProgress } from 'src/components/CircleProgress'; | ||
import { ErrorState } from 'src/components/ErrorState/ErrorState'; | ||
import { Placeholder } from 'src/components/Placeholder/Placeholder'; | ||
import { useCloudPulseDashboardByIdQuery } from 'src/queries/cloudpulse/dashboards'; | ||
import { useResourcesQuery } from 'src/queries/cloudpulse/resources'; | ||
import { | ||
useCloudPulseJWEtokenQuery, | ||
useGetCloudPulseMetricDefinitionsByServiceType, | ||
} from 'src/queries/cloudpulse/services'; | ||
|
||
import { getUserPreferenceObject } from '../Utils/UserPreference'; | ||
import { createObjectCopy } from '../Utils/utils'; | ||
import { CloudPulseWidget } from '../Widget/CloudPulseWidget'; | ||
import { | ||
all_interval_options, | ||
getInSeconds, | ||
getIntervalIndex, | ||
} from '../Widget/components/CloudPulseIntervalSelect'; | ||
|
||
import type { CloudPulseWidgetProperties } from '../Widget/CloudPulseWidget'; | ||
import type { | ||
AvailableMetrics, | ||
Dashboard, | ||
JWETokenPayLoad, | ||
TimeDuration, | ||
Widgets, | ||
} from '@linode/api-v4'; | ||
|
||
export interface DashboardProperties { | ||
/** | ||
* Id of the selected dashboard | ||
*/ | ||
dashboardId: number; | ||
|
||
/** | ||
* time duration to fetch the metrics data in this widget | ||
*/ | ||
duration: TimeDuration; | ||
|
||
/** | ||
* optional timestamp to pass as react query param to forcefully re-fetch data | ||
*/ | ||
manualRefreshTimeStamp?: number | undefined; | ||
|
||
/** | ||
* Selected region for the dashboard | ||
*/ | ||
region?: string; | ||
|
||
/** | ||
* Selected resources for the dashboard | ||
*/ | ||
resources: string[]; | ||
|
||
/** | ||
* optional flag to check whether changes should be stored in preferences or not (in case this component is reused) | ||
*/ | ||
savePref?: boolean; | ||
} | ||
|
||
export const CloudPulseDashboard = (props: DashboardProperties) => { | ||
const { | ||
dashboardId, | ||
duration, | ||
manualRefreshTimeStamp, | ||
resources, | ||
savePref, | ||
} = props; | ||
|
||
const getJweTokenPayload = (): JWETokenPayLoad => { | ||
return { | ||
resource_id: resourceList?.map((resource) => String(resource.id)) ?? [], | ||
}; | ||
}; | ||
|
||
const getCloudPulseGraphProperties = ( | ||
widget: Widgets | ||
): CloudPulseWidgetProperties => { | ||
const graphProp: CloudPulseWidgetProperties = { | ||
ariaLabel: widget.label, | ||
authToken: '', | ||
availableMetrics: undefined, | ||
duration, | ||
errorLabel: 'Error While Loading Data', | ||
resourceIds: resources, | ||
resources: [], | ||
serviceType: dashboard?.service_type ?? '', | ||
timeStamp: manualRefreshTimeStamp, | ||
unit: widget.unit ?? '%', | ||
widget: { ...widget }, | ||
}; | ||
if (savePref) { | ||
setPreferredWidgetPlan(graphProp.widget); | ||
} | ||
return graphProp; | ||
}; | ||
|
||
const setPreferredWidgetPlan = (widgetObj: Widgets) => { | ||
const widgetPreferences = getUserPreferenceObject().widgets; | ||
const pref = widgetPreferences?.[widgetObj.label]; | ||
if (pref) { | ||
Object.assign(widgetObj, { | ||
aggregate_function: pref.aggregateFunction, | ||
size: pref.size, | ||
time_granularity: { ...pref.timeGranularity }, | ||
}); | ||
} | ||
}; | ||
|
||
const getTimeGranularity = (scrapeInterval: string) => { | ||
const scrapeIntervalValue = getInSeconds(scrapeInterval); | ||
const index = getIntervalIndex(scrapeIntervalValue); | ||
return index < 0 ? all_interval_options[0] : all_interval_options[index]; | ||
}; | ||
|
||
const { | ||
data: dashboard, | ||
isLoading: isDashboardLoading, | ||
} = useCloudPulseDashboardByIdQuery(dashboardId); | ||
|
||
const { | ||
data: resourceList, | ||
isLoading: isResourcesLoading, | ||
} = useResourcesQuery( | ||
Boolean(dashboard?.service_type), | ||
dashboard?.service_type, | ||
{}, | ||
{} | ||
); | ||
|
||
const { | ||
data: metricDefinitions, | ||
isError: isMetricDefinitionError, | ||
isLoading: isMetricDefinitionLoading, | ||
} = useGetCloudPulseMetricDefinitionsByServiceType( | ||
dashboard?.service_type, | ||
Boolean(dashboard?.service_type) | ||
); | ||
|
||
const { | ||
data: jweToken, | ||
isError: isJweTokenError, | ||
} = useCloudPulseJWEtokenQuery( | ||
dashboard?.service_type, | ||
getJweTokenPayload(), | ||
Boolean(resourceList) | ||
); | ||
|
||
if (isJweTokenError) { | ||
return ( | ||
<Grid item xs> | ||
<ErrorState errorText="Failed to get jwe token" /> | ||
</Grid> | ||
); | ||
} | ||
|
||
if (isMetricDefinitionLoading || isDashboardLoading || isResourcesLoading) { | ||
return <CircleProgress />; | ||
} | ||
|
||
if (isMetricDefinitionError) { | ||
return <ErrorState errorText={'Error loading metric definitions'} />; | ||
} | ||
|
||
const RenderWidgets = () => { | ||
if (!dashboard || Boolean(dashboard.widgets?.length)) { | ||
return renderPlaceHolder( | ||
'No visualizations are available at this moment. Create Dashboards to list here.' | ||
); | ||
} | ||
|
||
if ( | ||
!dashboard.service_type || | ||
!Boolean(resources.length > 0) || | ||
!jweToken?.token || | ||
!Boolean(resourceList?.length) | ||
) { | ||
return renderPlaceHolder( | ||
'Select Dashboard, Region and Resource to visualize metrics' | ||
); | ||
} | ||
|
||
// maintain a copy | ||
const newDashboard: Dashboard = createObjectCopy(dashboard)!; | ||
return ( | ||
<Grid columnSpacing={1} container item rowSpacing={2} xs={12}> | ||
{{ ...newDashboard }.widgets.map((widget, index) => { | ||
// check if widget metric definition is available or not | ||
if (widget) { | ||
// find the metric defintion of the widget label | ||
const availMetrics = metricDefinitions?.data.find( | ||
(availMetrics: AvailableMetrics) => | ||
widget.label === availMetrics.label | ||
); | ||
const cloudPulseWidgetProperties = getCloudPulseGraphProperties({ | ||
...widget, | ||
}); | ||
|
||
// metric definition is available but time_granularity is not present | ||
if ( | ||
availMetrics && | ||
!cloudPulseWidgetProperties.widget.time_granularity | ||
) { | ||
cloudPulseWidgetProperties.widget.time_granularity = getTimeGranularity( | ||
availMetrics.scrape_interval | ||
); | ||
} | ||
return ( | ||
<CloudPulseWidget | ||
key={widget.label} | ||
{...cloudPulseWidgetProperties} | ||
authToken={jweToken?.token} | ||
availableMetrics={availMetrics} | ||
resources={resourceList!} | ||
savePref={savePref} | ||
/> | ||
); | ||
} else { | ||
return <React.Fragment key={index}></React.Fragment>; | ||
} | ||
})} | ||
</Grid> | ||
); | ||
}; | ||
|
||
const renderPlaceHolder = (subtitle: string) => { | ||
return ( | ||
<Grid item xs> | ||
<Paper> | ||
<Placeholder icon={CloudPulseIcon} subtitle={subtitle} title="" /> | ||
</Paper> | ||
</Grid> | ||
); | ||
}; | ||
|
||
return <RenderWidgets />; | ||
}; |
Oops, something went wrong.