Add start and end date handling to Dashboard components and implement metric result fetching

This commit is contained in:
Simon Larsen
2024-11-29 19:03:34 +00:00
parent 45d447bf2c
commit b8fc933acb
7 changed files with 257 additions and 115 deletions

View File

@@ -86,6 +86,7 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
data={value[arg.id] as MetricQueryConfigData}
metricNameAndUnits={props.metrics.metricNameAndUnits}
telemetryAttributes={props.metrics.telemetryAttributes}
hideCard={true}
/>
);
};

View File

@@ -18,6 +18,8 @@ import { GetReactElementFunction } from "Common/UI/Types/FunctionTypes";
import DashboardViewConfig from "Common/Types/Dashboard/DashboardViewConfig";
import ObjectID from "Common/Types/ObjectID";
import DashboardComponentType from "Common/Types/Dashboard/DashboardComponentType";
import DashboardStartAndEndDate from "../Types/DashboardStartAndEndDate";
import MetricNameAndUnit from "../../Metrics/Types/MetricNameAndUnit";
export interface DashboardBaseComponentProps {
componentId: ObjectID;
@@ -31,6 +33,8 @@ export interface DashboardBaseComponentProps {
dashboardCanvasWidthInPx: number;
dashboardCanvasHeightInPx: number;
dashboardViewConfig: DashboardViewConfig;
dashboardStartAndEndDate: DashboardStartAndEndDate;
metricNameAndUnits: Array<MetricNameAndUnit>;
}
export interface ComponentProps extends DashboardBaseComponentProps {

View File

@@ -1,6 +1,15 @@
import React, { FunctionComponent, ReactElement } from "react";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
import DashboardChartComponent from "Common/Types/Dashboard/DashboardComponents/DashboardChartComponent";
import { DashboardBaseComponentProps } from "./DashboardBaseComponent";
import MetricCharts from "../../Metrics/MetricCharts";
import AggregatedResult from "Common/Types/BaseDatabase/AggregatedResult";
import { DashboardStartAndEndDateUtil } from "../Types/DashboardStartAndEndDate";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import MetricViewData from "../../Metrics/Types/MetricViewData";
import MetricUtil from "../../Metrics/Utils/Metrics";
import API from "Common/UI/Utils/API/API";
export interface ComponentProps extends DashboardBaseComponentProps {
component: DashboardChartComponent;
@@ -9,7 +18,65 @@ export interface ComponentProps extends DashboardBaseComponentProps {
const DashboardChartComponentElement: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
return <div>Chart Component {props.component.componentId.toString()}</div>;
const [metricResults, setMetricResults] = React.useState<Array<AggregatedResult>>([]);
const [error, setError] = React.useState<string | null>(null);
const [isLoading, setIsLoading] = React.useState<boolean>(true);
const metricViewData: MetricViewData = {
queryConfigs: props.component.arguments.metricQueryConfig ? [props.component.arguments.metricQueryConfig] : [],
startAndEndDate: DashboardStartAndEndDateUtil.getStartAndEndDate(props.dashboardStartAndEndDate),
formulaConfigs: []
}
const fetchAggregatedResults: PromiseVoidFunction =
async (): Promise<void> => {
setIsLoading(true);
if (
!metricViewData.startAndEndDate?.startValue ||
!metricViewData.startAndEndDate?.endValue
) {
setIsLoading(false);
return;
}
try {
const results: Array<AggregatedResult> = await MetricUtil.fetchResults({
metricViewData: metricViewData,
});
setMetricResults(results);
setError("");
} catch (err: unknown) {
setError(API.getFriendlyErrorMessage(err as Error));
}
setIsLoading(false);
};
if (isLoading) {
return <PageLoader isVisible={true} />;
}
if (error) {
return <ErrorMessage error={error} />;
}
useEffect(() => {
fetchAggregatedResults();
}, [props.dashboardStartAndEndDate, props.component.arguments.metricQueryConfig, props.metricNameAndUnits]);
return <div>
<MetricCharts
metricResults={metricResults}
metricNamesAndUnits={props.metricNameAndUnits}
metricViewData={metricViewData}
/>
</div>;
};
export default DashboardChartComponentElement;

View File

@@ -1,7 +1,64 @@
import InBetween from "Common/Types/BaseDatabase/InBetween";
import DashboardStartAndEndDateRange from "./DashboardStartAndEndDateRange";
import OneUptimeDate from "Common/Types/Date";
export default interface DashboardStartAndEndDate {
startAndEndDate?: InBetween<Date> | undefined;
range: DashboardStartAndEndDateRange;
}
export class DashboardStartAndEndDateUtil {
public static getStartAndEndDate(dashboardStartAndEndDate: DashboardStartAndEndDate): InBetween<Date> {
const currentDate: Date = OneUptimeDate.getCurrentDate();
// 30 mins.
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_THIRTY_MINS) {
return new InBetween<Date>(OneUptimeDate.addRemoveMinutes(currentDate, -30), currentDate);
}
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_ONE_HOUR) {
return new InBetween<Date>(OneUptimeDate.addRemoveHours(currentDate, -1), currentDate);
}
// two hours.
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_TWO_HOURS) {
return new InBetween<Date>(OneUptimeDate.addRemoveHours(currentDate, -2), currentDate);
}
// three hours
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_THREE_HOURS) {
return new InBetween<Date>(OneUptimeDate.addRemoveHours(currentDate, -3), currentDate);
}
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_ONE_DAY) {
return new InBetween<Date>(OneUptimeDate.addRemoveDays(currentDate, -1), currentDate);
}
// two days .
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_TWO_DAYS) {
return new InBetween<Date>(OneUptimeDate.addRemoveDays(currentDate, -2), currentDate);
}
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_ONE_WEEK) {
return new InBetween<Date>(OneUptimeDate.addRemoveDays(currentDate, -7), currentDate);
}
// two weeks.
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_TWO_WEEKS) {
return new InBetween<Date>(OneUptimeDate.addRemoveDays(currentDate, -14), currentDate);
}
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_ONE_MONTH) {
return new InBetween<Date>(OneUptimeDate.addRemoveMonths(currentDate, -1), currentDate);
}
// three months.
if(dashboardStartAndEndDate.range === DashboardStartAndEndDateRange.PAST_THREE_MONTHS) {
return new InBetween<Date>(OneUptimeDate.addRemoveMonths(currentDate, -3), currentDate);
}
// custom
return dashboardStartAndEndDate.startAndEndDate || new InBetween<Date>(currentDate, currentDate);
}
}

View File

@@ -22,6 +22,7 @@ export interface ComponentProps {
onFocus?: (() => void) | undefined;
onBlur?: (() => void) | undefined;
tabIndex?: number | undefined;
hideCard?: boolean | undefined;
}
const MetricGraphConfig: FunctionComponent<ComponentProps> = (
@@ -35,53 +36,64 @@ const MetricGraphConfig: FunctionComponent<ComponentProps> = (
throw new BadDataException("MetricQueryData is required");
}
const getContent = (): ReactElement => {
return (
<div>{props.data.metricAliasData && (
<MetricAlias
data={props.data.metricAliasData}
onDataChanged={(data: MetricAliasData) => {
props.onBlur?.();
props.onFocus?.();
props.onChange &&
props.onChange({ ...props.data, metricAliasData: data });
}}
isFormula={false}
/>
)}
{props.data.metricQueryData && (
<MetricQuery
data={props.data.metricQueryData}
onDataChanged={(data: MetricQueryData) => {
props.onBlur?.();
props.onFocus?.();
props.onChange &&
props.onChange({ ...props.data, metricQueryData: data });
}}
metricNameAndUnits={props.metricNameAndUnits}
telemetryAttributes={props.telemetryAttributes}
/>
)}
{props.onRemove && (
<div className="-ml-3">
<Button
title={"Remove"}
onClick={() => {
props.onBlur?.();
props.onFocus?.();
return props.onRemove && props.onRemove();
}}
buttonSize={ButtonSize.Small}
buttonStyle={ButtonStyleType.DANGER_OUTLINE}
/>
</div>
)}
{props.error && (
<p data-testid="error-message" className="mt-1 text-sm text-red-400">
{props.error}
</p>
)}
</div>);
}
if(props.hideCard) {
return getContent();
}
return (
<Card>
<div className="-mt-5" tabIndex={props.tabIndex}>
{props.data.metricAliasData && (
<MetricAlias
data={props.data.metricAliasData}
onDataChanged={(data: MetricAliasData) => {
props.onBlur?.();
props.onFocus?.();
props.onChange &&
props.onChange({ ...props.data, metricAliasData: data });
}}
isFormula={false}
/>
)}
{props.data.metricQueryData && (
<MetricQuery
data={props.data.metricQueryData}
onDataChanged={(data: MetricQueryData) => {
props.onBlur?.();
props.onFocus?.();
props.onChange &&
props.onChange({ ...props.data, metricQueryData: data });
}}
metricNameAndUnits={props.metricNameAndUnits}
telemetryAttributes={props.telemetryAttributes}
/>
)}
{props.onRemove && (
<div className="-ml-3">
<Button
title={"Remove"}
onClick={() => {
props.onBlur?.();
props.onFocus?.();
return props.onRemove && props.onRemove();
}}
buttonSize={ButtonSize.Small}
buttonStyle={ButtonStyleType.DANGER_OUTLINE}
/>
</div>
)}
{props.error && (
<p data-testid="error-message" className="mt-1 text-sm text-red-400">
{props.error}
</p>
)}
{getContent()}
</div>
</Card>
);

View File

@@ -23,18 +23,11 @@ import Card from "Common/UI/Components/Card/Card";
import AggregatedResult from "Common/Types/BaseDatabase/AggregatedResult";
import API from "Common/UI/Utils/API/API";
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
import ModelAPI from "Common/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI";
import Metric from "Common/Models/AnalyticsModels/Metric";
import OneUptimeDate from "Common/Types/Date";
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
import ComponentLoader from "Common/UI/Components/ComponentLoader/ComponentLoader";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import AggregatedModel from "Common/Types/BaseDatabase/AggregatedModel";
import IconProp from "Common/Types/Icon/IconProp";
import PageLoader from "Common/UI/Components/Loader/PageLoader";
import Dictionary from "Common/Types/Dictionary";
import MetricNameAndUnit from "./Types/MetricNameAndUnit";
import MetricQueryConfigData from "Common/Types/Metrics/MetricQueryConfigData";
import MetricFormulaConfigData from "Common/Types/Metrics/MetricFormulaConfigData";
import MetricUtil from "./Utils/Metrics";
@@ -155,49 +148,10 @@ const MetricView: FunctionComponent<ComponentProps> = (
setIsMetricResultsLoading(false);
return;
}
const results: Array<AggregatedResult> = [];
try {
for (const queryConfig of metricViewData.queryConfigs) {
const result: AggregatedResult = await ModelAPI.aggregate({
modelType: Metric,
aggregateBy: {
query: {
time: metricViewData.startAndEndDate!,
name: queryConfig.metricQueryData.filterData.metricName!,
attributes: queryConfig.metricQueryData.filterData
.attributes as Dictionary<string | number | boolean>,
},
aggregationType:
(queryConfig.metricQueryData.filterData
.aggegationType as MetricsAggregationType) ||
MetricsAggregationType.Avg,
aggregateColumnName: "value",
aggregationTimestampColumnName: "time",
startTimestamp:
(metricViewData.startAndEndDate?.startValue as Date) ||
OneUptimeDate.getCurrentDate(),
endTimestamp:
(metricViewData.startAndEndDate?.endValue as Date) ||
OneUptimeDate.getCurrentDate(),
limit: LIMIT_PER_PROJECT,
skip: 0,
groupBy: queryConfig.metricQueryData.groupBy,
},
});
result.data.map((data: AggregatedModel) => {
// convert to int from float
if (data.value) {
data.value = Math.round(data.value);
}
return data;
});
results.push(result);
}
const results: Array<AggregatedResult> = await MetricUtil.fetchResults({
metricViewData: metricViewData,
});
setMetricResults(results);
setMetricResultsError("");
@@ -208,18 +162,6 @@ const MetricView: FunctionComponent<ComponentProps> = (
setIsMetricResultsLoading(false);
};
// type GetEmptyFormulaConfigFunction = () => MetricFormulaConfigData;
// const getEmptyFormulaConfigData: GetEmptyFormulaConfigFunction =
// (): MetricFormulaConfigData => {
// return {
// metricAliasData: { metricVariable: "", title: "", description: "" },
// metricFormulaData: {
// metricFormula: "",
// },
// };
// };
if (isPageLoading) {
return <PageLoader isVisible={true} />;
}

View File

@@ -11,8 +11,67 @@ import { JSONObject } from "Common/Types/JSON";
import API from "Common/UI/Utils/API/API";
import URL from "Common/Types/API/URL";
import { APP_API_URL } from "Common/UI/Config";
import AggregatedModel from "Common/Types/BaseDatabase/AggregatedModel";
import MetricsAggregationType from "Common/Types/Metrics/MetricsAggregationType";
import Dictionary from "Common/Types/Dictionary";
import AggregatedResult from "Common/Types/BaseDatabase/AggregatedResult";
import MetricViewData from "../Types/MetricViewData";
import OneUptimeDate from "Common/Types/Date";
export default class MetricUtil {
public static async fetchResults(data: {
metricViewData: MetricViewData;
}): Promise<Array<AggregatedResult>> {
const results: Array<AggregatedResult> = [];
const metricViewData: MetricViewData = data.metricViewData;
for (const queryConfig of metricViewData.queryConfigs) {
const result: AggregatedResult = await ModelAPI.aggregate({
modelType: Metric,
aggregateBy: {
query: {
time: metricViewData.startAndEndDate!,
name: queryConfig.metricQueryData.filterData.metricName!,
attributes: queryConfig.metricQueryData.filterData
.attributes as Dictionary<string | number | boolean>,
},
aggregationType:
(queryConfig.metricQueryData.filterData
.aggegationType as MetricsAggregationType) ||
MetricsAggregationType.Avg,
aggregateColumnName: "value",
aggregationTimestampColumnName: "time",
startTimestamp:
(metricViewData.startAndEndDate?.startValue as Date) ||
OneUptimeDate.getCurrentDate(),
endTimestamp:
(metricViewData.startAndEndDate?.endValue as Date) ||
OneUptimeDate.getCurrentDate(),
limit: LIMIT_PER_PROJECT,
skip: 0,
groupBy: queryConfig.metricQueryData.groupBy,
},
});
result.data.map((data: AggregatedModel) => {
// convert to int from float
if (data.value) {
data.value = Math.round(data.value);
}
return data;
});
results.push(result);
}
return results;
}
public static async loadAllMetricsTypes(): Promise<{
metricNamesAndUnits: Array<MetricNameAndUnit>;
telemetryAttributes: Array<string>;
@@ -63,14 +122,14 @@ export default class MetricUtil {
const metricAttributesResponse:
| HTTPResponse<JSONObject>
| HTTPErrorResponse = await API.post(
URL.fromString(APP_API_URL.toString()).addRoute(
"/telemetry/metrics/get-attributes",
),
{},
{
...ModelAPI.getCommonHeaders(),
},
);
URL.fromString(APP_API_URL.toString()).addRoute(
"/telemetry/metrics/get-attributes",
),
{},
{
...ModelAPI.getCommonHeaders(),
},
);
let attributes: Array<string> = [];