mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
Merge pull request #2067 from OneUptime/service-catalog-pages
Service catalog pages
This commit is contained in:
@@ -27,6 +27,7 @@ export interface ComponentProps<T extends GenericObject> {
|
||||
onAdvancedFiltersToggle?:
|
||||
| undefined
|
||||
| ((showAdvancedFilters: boolean) => void);
|
||||
showAdvancedFiltersByDefault?: boolean | undefined;
|
||||
}
|
||||
|
||||
type FiltersFormFunction = <T extends GenericObject>(
|
||||
@@ -56,7 +57,9 @@ const FiltersForm: FiltersFormFunction = <T extends GenericObject>(
|
||||
}),
|
||||
);
|
||||
|
||||
const [showMoreFilters, setShowMoreFilters] = React.useState<boolean>(false);
|
||||
const [showMoreFilters, setShowMoreFilters] = React.useState<boolean>(
|
||||
props.showAdvancedFiltersByDefault ?? false,
|
||||
);
|
||||
|
||||
return (
|
||||
<div id={props.id}>
|
||||
|
||||
@@ -1,51 +1,147 @@
|
||||
import MetricsAggregationType from "Common/Types/Metrics/MetricsAggregationType";
|
||||
import MetricView from "./MetricView";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, { FunctionComponent, ReactElement } from "react";
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import OneUptimeDate from "Common/Types/Date";
|
||||
import InBetween from "Common/Types/BaseDatabase/InBetween";
|
||||
import MetricViewData from "Common/Types/Metrics/MetricViewData";
|
||||
import MetricQueryConfigData from "Common/Types/Metrics/MetricQueryConfigData";
|
||||
import Dictionary from "Common/Types/Dictionary";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import Text from "Common/Types/Text";
|
||||
import FilterData from "Common/UI/Components/Filters/Types/FilterData";
|
||||
import MetricsQuery from "Common/Types/Metrics/MetricsQuery";
|
||||
|
||||
const MetricExplorer: FunctionComponent = (): ReactElement => {
|
||||
const metricName: string =
|
||||
Navigation.getQueryStringByName("metricName") || "";
|
||||
const metricQueriesFromUrl: Array<MetricQueryFromUrl> =
|
||||
getMetricQueriesFromQuery();
|
||||
|
||||
const serviceName: string =
|
||||
Navigation.getQueryStringByName("serviceName") || "";
|
||||
const defaultEndDate: Date = OneUptimeDate.getCurrentDate();
|
||||
const defaultStartDate: Date = OneUptimeDate.addRemoveHours(
|
||||
defaultEndDate,
|
||||
-1,
|
||||
);
|
||||
const defaultStartAndEndDate: InBetween<Date> = new InBetween(
|
||||
defaultStartDate,
|
||||
defaultEndDate,
|
||||
);
|
||||
|
||||
// set it to past 1 hour
|
||||
const endDate: Date = OneUptimeDate.getCurrentDate();
|
||||
const startDate: Date = OneUptimeDate.addRemoveHours(endDate, -1);
|
||||
const initialTimeRange: InBetween<Date> =
|
||||
getTimeRangeFromQuery() ?? defaultStartAndEndDate;
|
||||
|
||||
const startAndEndDate: InBetween<Date> = new InBetween(startDate, endDate);
|
||||
const initialQueryConfigs: Array<MetricQueryConfigData> =
|
||||
metricQueriesFromUrl.map(
|
||||
(
|
||||
metricQuery: MetricQueryFromUrl,
|
||||
index: number,
|
||||
): MetricQueryConfigData => {
|
||||
return {
|
||||
metricAliasData: {
|
||||
metricVariable: Text.getLetterFromAByNumber(index),
|
||||
title: metricQuery.alias?.title || "",
|
||||
description: metricQuery.alias?.description || "",
|
||||
legend: metricQuery.alias?.legend || "",
|
||||
legendUnit: metricQuery.alias?.legendUnit || "",
|
||||
},
|
||||
metricQueryData: {
|
||||
filterData: {
|
||||
metricName: metricQuery.metricName,
|
||||
attributes: metricQuery.attributes,
|
||||
aggegationType:
|
||||
metricQuery.aggregationType || MetricsAggregationType.Avg,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const [metricViewData, setMetricViewData] = React.useState<MetricViewData>({
|
||||
startAndEndDate: startAndEndDate,
|
||||
queryConfigs: [
|
||||
{
|
||||
metricAliasData: {
|
||||
metricVariable: "a",
|
||||
title: "",
|
||||
description: "",
|
||||
legend: "",
|
||||
legendUnit: "",
|
||||
},
|
||||
metricQueryData: {
|
||||
filterData: {
|
||||
metricName: metricName,
|
||||
attributes: serviceName
|
||||
? {
|
||||
"resource.oneuptime.telemetry.service.name": serviceName,
|
||||
}
|
||||
: {},
|
||||
aggegationType: MetricsAggregationType.Avg,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
startAndEndDate: initialTimeRange,
|
||||
queryConfigs:
|
||||
initialQueryConfigs.length > 0
|
||||
? initialQueryConfigs
|
||||
: [
|
||||
{
|
||||
metricAliasData: {
|
||||
metricVariable: "a",
|
||||
title: "",
|
||||
description: "",
|
||||
legend: "",
|
||||
legendUnit: "",
|
||||
},
|
||||
metricQueryData: {
|
||||
filterData: {
|
||||
metricName: "",
|
||||
attributes: {},
|
||||
aggegationType: MetricsAggregationType.Avg,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
formulaConfigs: [],
|
||||
});
|
||||
|
||||
const lastSerializedStateRef: React.MutableRefObject<string> =
|
||||
useRef<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
const metricQueriesFromState: Array<MetricQueryFromUrl> =
|
||||
buildMetricQueriesFromState(metricViewData);
|
||||
|
||||
const metricQueriesForUrl: Array<MetricQueryFromUrl> =
|
||||
metricQueriesFromState.filter(isMeaningfulMetricQuery);
|
||||
|
||||
const startTimeValue: Date | undefined =
|
||||
metricViewData.startAndEndDate?.startValue;
|
||||
const endTimeValue: Date | undefined =
|
||||
metricViewData.startAndEndDate?.endValue;
|
||||
|
||||
const serializedState: string = JSON.stringify({
|
||||
metricQueries: metricQueriesForUrl,
|
||||
startTime: startTimeValue ? OneUptimeDate.toString(startTimeValue) : null,
|
||||
endTime: endTimeValue ? OneUptimeDate.toString(endTimeValue) : null,
|
||||
});
|
||||
|
||||
if (serializedState === lastSerializedStateRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const params: URLSearchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (metricQueriesForUrl.length > 0) {
|
||||
params.set("metricQueries", JSON.stringify(metricQueriesForUrl));
|
||||
} else {
|
||||
params.delete("metricQueries");
|
||||
}
|
||||
|
||||
if (startTimeValue && endTimeValue) {
|
||||
params.set("startTime", OneUptimeDate.toString(startTimeValue));
|
||||
params.set("endTime", OneUptimeDate.toString(endTimeValue));
|
||||
} else {
|
||||
params.delete("startTime");
|
||||
params.delete("endTime");
|
||||
}
|
||||
|
||||
params.delete("metricName");
|
||||
params.delete("attributes");
|
||||
params.delete("serviceName");
|
||||
|
||||
const newQueryString: string = params.toString();
|
||||
const newUrl: string =
|
||||
newQueryString.length > 0
|
||||
? `${window.location.pathname}?${newQueryString}`
|
||||
: window.location.pathname;
|
||||
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
|
||||
lastSerializedStateRef.current = serializedState;
|
||||
}, [metricViewData]);
|
||||
|
||||
return (
|
||||
<MetricView
|
||||
data={metricViewData}
|
||||
@@ -57,3 +153,305 @@ const MetricExplorer: FunctionComponent = (): ReactElement => {
|
||||
};
|
||||
|
||||
export default MetricExplorer;
|
||||
|
||||
type MetricQueryFromUrl = {
|
||||
metricName: string;
|
||||
attributes: Dictionary<string | number | boolean>;
|
||||
aggregationType?: MetricsAggregationType;
|
||||
alias?: MetricQueryAliasFromUrl;
|
||||
};
|
||||
|
||||
type MetricQueryAliasFromUrl = {
|
||||
title?: string;
|
||||
description?: string;
|
||||
legend?: string;
|
||||
legendUnit?: string;
|
||||
};
|
||||
|
||||
function buildMetricQueriesFromState(
|
||||
data: MetricViewData,
|
||||
): Array<MetricQueryFromUrl> {
|
||||
return data.queryConfigs.map(
|
||||
(queryConfig: MetricQueryConfigData): MetricQueryFromUrl => {
|
||||
const filterData: FilterData<MetricsQuery> =
|
||||
queryConfig.metricQueryData.filterData;
|
||||
const filterDataRecord: Record<string, unknown> = filterData as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
const metricNameValue: unknown = filterDataRecord["metricName"];
|
||||
|
||||
const metricName: string =
|
||||
typeof metricNameValue === "string" ? metricNameValue : "";
|
||||
|
||||
const aggregationValue: unknown = filterDataRecord["aggegationType"];
|
||||
|
||||
const aggregationType: MetricsAggregationType | undefined =
|
||||
getAggregationTypeFromValue(aggregationValue);
|
||||
|
||||
const attributes: Dictionary<string | number | boolean> =
|
||||
sanitizeAttributes(filterDataRecord["attributes"]);
|
||||
|
||||
const aliasData: MetricQueryAliasFromUrl | undefined =
|
||||
buildAliasFromMetricAliasData(queryConfig.metricAliasData);
|
||||
|
||||
return {
|
||||
metricName,
|
||||
attributes,
|
||||
...(aggregationType ? { aggregationType } : {}),
|
||||
...(aliasData ? { alias: aliasData } : {}),
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function getMetricQueriesFromQuery(): Array<MetricQueryFromUrl> {
|
||||
const metricQueriesParam: string | null =
|
||||
Navigation.getQueryStringByName("metricQueries");
|
||||
|
||||
if (!metricQueriesParam) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedValue: unknown = JSONFunctions.parse(metricQueriesParam);
|
||||
|
||||
if (!Array.isArray(parsedValue)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const sanitizedQueries: Array<MetricQueryFromUrl> = [];
|
||||
|
||||
for (const entry of parsedValue) {
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entryRecord: Record<string, unknown> = entry as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
const metricName: string =
|
||||
typeof entryRecord["metricName"] === "string"
|
||||
? (entryRecord["metricName"] as string)
|
||||
: "";
|
||||
|
||||
const attributes: Dictionary<string | number | boolean> =
|
||||
sanitizeAttributes(entryRecord["attributes"]);
|
||||
|
||||
const aggregationType: MetricsAggregationType | undefined =
|
||||
getAggregationTypeFromValue(entryRecord["aggregationType"]);
|
||||
|
||||
const alias: MetricQueryAliasFromUrl | undefined = sanitizeAlias(
|
||||
entryRecord["alias"],
|
||||
entryRecord,
|
||||
);
|
||||
|
||||
sanitizedQueries.push({
|
||||
metricName,
|
||||
attributes,
|
||||
...(aggregationType ? { aggregationType } : {}),
|
||||
...(alias ? { alias } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
return sanitizedQueries;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function getTimeRangeFromQuery(): InBetween<Date> | null {
|
||||
const startTimeParam: string | null =
|
||||
Navigation.getQueryStringByName("startTime");
|
||||
const endTimeParam: string | null =
|
||||
Navigation.getQueryStringByName("endTime");
|
||||
|
||||
if (!startTimeParam || !endTimeParam) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
!OneUptimeDate.isValidDateString(startTimeParam) ||
|
||||
!OneUptimeDate.isValidDateString(endTimeParam)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const startDate: Date = OneUptimeDate.fromString(startTimeParam);
|
||||
const endDate: Date = OneUptimeDate.fromString(endTimeParam);
|
||||
|
||||
if (!OneUptimeDate.isOnOrBefore(startDate, endDate)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new InBetween(startDate, endDate);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeAttributes(
|
||||
value: unknown,
|
||||
): Dictionary<string | number | boolean> {
|
||||
if (value === null || value === undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let candidate: unknown = value;
|
||||
|
||||
if (typeof value === "string") {
|
||||
try {
|
||||
candidate = JSONFunctions.parse(value);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const attributes: Dictionary<string | number | boolean> = {};
|
||||
|
||||
for (const key in candidate as Record<string, unknown>) {
|
||||
const attributeValue: unknown = (candidate as Record<string, unknown>)[key];
|
||||
|
||||
if (
|
||||
typeof attributeValue === "string" ||
|
||||
typeof attributeValue === "number" ||
|
||||
typeof attributeValue === "boolean"
|
||||
) {
|
||||
attributes[key] = attributeValue;
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
function buildAliasFromMetricAliasData(
|
||||
data: MetricQueryConfigData["metricAliasData"],
|
||||
): MetricQueryAliasFromUrl | undefined {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const alias: MetricQueryAliasFromUrl = {};
|
||||
|
||||
if (typeof data.title === "string" && data.title.trim() !== "") {
|
||||
alias.title = data.title;
|
||||
}
|
||||
|
||||
if (typeof data.description === "string" && data.description.trim() !== "") {
|
||||
alias.description = data.description;
|
||||
}
|
||||
|
||||
if (typeof data.legend === "string" && data.legend.trim() !== "") {
|
||||
alias.legend = data.legend;
|
||||
}
|
||||
|
||||
if (typeof data.legendUnit === "string" && data.legendUnit.trim() !== "") {
|
||||
alias.legendUnit = data.legendUnit;
|
||||
}
|
||||
|
||||
return Object.keys(alias).length > 0 ? alias : undefined;
|
||||
}
|
||||
|
||||
function sanitizeAlias(
|
||||
value: unknown,
|
||||
fallback?: Record<string, unknown>,
|
||||
): MetricQueryAliasFromUrl | undefined {
|
||||
const alias: MetricQueryAliasFromUrl = {};
|
||||
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
const aliasRecord: Record<string, unknown> = value as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
if (typeof aliasRecord["title"] === "string") {
|
||||
alias.title = aliasRecord["title"] as string;
|
||||
}
|
||||
|
||||
if (typeof aliasRecord["description"] === "string") {
|
||||
alias.description = aliasRecord["description"] as string;
|
||||
}
|
||||
|
||||
if (typeof aliasRecord["legend"] === "string") {
|
||||
alias.legend = aliasRecord["legend"] as string;
|
||||
}
|
||||
|
||||
if (typeof aliasRecord["legendUnit"] === "string") {
|
||||
alias.legendUnit = aliasRecord["legendUnit"] as string;
|
||||
}
|
||||
}
|
||||
|
||||
// Backward compatibility: allow flat keys on the main query record.
|
||||
if (fallback) {
|
||||
if (alias.title === undefined && typeof fallback["title"] === "string") {
|
||||
alias.title = fallback["title"] as string;
|
||||
}
|
||||
|
||||
if (
|
||||
alias.description === undefined &&
|
||||
typeof fallback["description"] === "string"
|
||||
) {
|
||||
alias.description = fallback["description"] as string;
|
||||
}
|
||||
|
||||
if (alias.legend === undefined && typeof fallback["legend"] === "string") {
|
||||
alias.legend = fallback["legend"] as string;
|
||||
}
|
||||
|
||||
if (
|
||||
alias.legendUnit === undefined &&
|
||||
typeof fallback["legendUnit"] === "string"
|
||||
) {
|
||||
alias.legendUnit = fallback["legendUnit"] as string;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(alias).length > 0 ? alias : undefined;
|
||||
}
|
||||
|
||||
function getAggregationTypeFromValue(
|
||||
value: unknown,
|
||||
): MetricsAggregationType | undefined {
|
||||
if (typeof value === "string") {
|
||||
const aggregationTypeValues: Array<string> = Object.values(
|
||||
MetricsAggregationType,
|
||||
) as Array<string>;
|
||||
|
||||
if (aggregationTypeValues.includes(value)) {
|
||||
return value as MetricsAggregationType;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isMeaningfulMetricQuery(query: MetricQueryFromUrl): boolean {
|
||||
if (query.metricName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Object.keys(query.attributes).length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
query.aggregationType &&
|
||||
query.aggregationType !== MetricsAggregationType.Avg
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.alias && Object.keys(query.alias).length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,19 @@ export interface ComponentProps {
|
||||
const MetricFilter: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const [showAdvancedFilters, setShowAdvancedFilters] =
|
||||
useState<boolean>(false);
|
||||
const [showAdvancedFilters, setShowAdvancedFilters] = useState<boolean>(true);
|
||||
|
||||
const initializedAdvancedFilters: React.MutableRefObject<boolean> =
|
||||
React.useRef<boolean>(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (initializedAdvancedFilters.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
initializedAdvancedFilters.current = true;
|
||||
props.onAdvancedFiltersToggle?.(true);
|
||||
}, [props.onAdvancedFiltersToggle]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -49,6 +60,7 @@ const MetricFilter: FunctionComponent<ComponentProps> = (
|
||||
setShowAdvancedFilters(show);
|
||||
props.onAdvancedFiltersToggle?.(show);
|
||||
}}
|
||||
showAdvancedFiltersByDefault={true}
|
||||
isFilterLoading={
|
||||
showAdvancedFilters ? props.isAttributesLoading : false
|
||||
}
|
||||
|
||||
@@ -35,6 +35,26 @@ import ConfirmModal from "Common/UI/Components/Modal/ConfirmModal";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import MetricType from "Common/Models/DatabaseModels/MetricType";
|
||||
|
||||
const getFetchRelevantState: (data: MetricViewData) => unknown = (
|
||||
data: MetricViewData,
|
||||
) => {
|
||||
return {
|
||||
startAndEndDate: data.startAndEndDate
|
||||
? {
|
||||
startValue: data.startAndEndDate.startValue,
|
||||
endValue: data.startAndEndDate.endValue,
|
||||
}
|
||||
: null,
|
||||
queryConfigs: data.queryConfigs.map(
|
||||
(queryConfig: MetricQueryConfigData) => {
|
||||
return {
|
||||
metricQueryData: queryConfig.metricQueryData,
|
||||
};
|
||||
},
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export interface ComponentProps {
|
||||
data: MetricViewData;
|
||||
hideQueryElements?: boolean;
|
||||
@@ -101,6 +121,9 @@ const MetricView: FunctionComponent<ComponentProps> = (
|
||||
|
||||
const metricViewDataRef: React.MutableRefObject<MetricViewData> =
|
||||
React.useRef(props.data);
|
||||
const lastFetchSnapshotRef: React.MutableRefObject<string> = React.useRef(
|
||||
JSON.stringify(getFetchRelevantState(props.data)),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
loadMetricTypes().catch((err: Error) => {
|
||||
@@ -114,20 +137,29 @@ const MetricView: FunctionComponent<ComponentProps> = (
|
||||
props.data,
|
||||
);
|
||||
|
||||
if (
|
||||
hasChanged &&
|
||||
props.data &&
|
||||
props.data.startAndEndDate &&
|
||||
props.data.startAndEndDate.startValue &&
|
||||
props.data.startAndEndDate.endValue
|
||||
) {
|
||||
if (hasChanged) {
|
||||
setCurrentQueryVariable(
|
||||
Text.getLetterFromAByNumber(props.data.queryConfigs.length),
|
||||
);
|
||||
}
|
||||
|
||||
const currentFetchSnapshot: string = JSON.stringify(
|
||||
getFetchRelevantState(props.data),
|
||||
);
|
||||
|
||||
const shouldFetch: boolean =
|
||||
currentFetchSnapshot !== lastFetchSnapshotRef.current &&
|
||||
Boolean(props.data?.startAndEndDate?.startValue) &&
|
||||
Boolean(props.data?.startAndEndDate?.endValue);
|
||||
|
||||
if (shouldFetch) {
|
||||
lastFetchSnapshotRef.current = currentFetchSnapshot;
|
||||
fetchAggregatedResults().catch((err: Error) => {
|
||||
setMetricResultsError(API.getFriendlyErrorMessage(err as Error));
|
||||
});
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
metricViewDataRef.current = props.data;
|
||||
}
|
||||
}, [props.data]);
|
||||
|
||||
@@ -2,6 +2,7 @@ import ProjectUtil from "Common/UI/Utils/Project";
|
||||
import SortOrder from "Common/Types/BaseDatabase/SortOrder";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import TelemetryService from "Common/Models/DatabaseModels/TelemetryService";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
|
||||
import PageMap from "../../Utils/PageMap";
|
||||
@@ -11,15 +12,19 @@ import React, { Fragment, FunctionComponent, ReactElement } from "react";
|
||||
import ModelTable from "Common/UI/Components/ModelTable/ModelTable";
|
||||
import MetricType from "Common/Models/DatabaseModels/MetricType";
|
||||
import Includes from "Common/Types/BaseDatabase/Includes";
|
||||
import TelemetryServicesElement from "../TelemetryService/TelemetryServiceElements";
|
||||
import MetricsAggregationType from "Common/Types/Metrics/MetricsAggregationType";
|
||||
|
||||
export interface ComponentProps {
|
||||
telemetryServiceId?: ObjectID | undefined;
|
||||
telemetryServiceName?: string | undefined;
|
||||
telemetryServiceIds?: Array<ObjectID> | undefined;
|
||||
}
|
||||
|
||||
const MetricsTable: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const telemetryServiceFilterIds: Array<ObjectID> =
|
||||
props.telemetryServiceIds || [];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ModelTable<MetricType>
|
||||
@@ -41,42 +46,73 @@ const MetricsTable: FunctionComponent<ComponentProps> = (
|
||||
"Metrics are the individual data points that make up a service. They are the building blocks of a service and represent the work done by a single service.",
|
||||
}}
|
||||
onViewPage={async (item: MetricType) => {
|
||||
if (!props.telemetryServiceId || !props.telemetryServiceName) {
|
||||
const route: Route = RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.TELEMETRY_METRIC_VIEW]!,
|
||||
);
|
||||
|
||||
const currentUrl: URL = Navigation.getCurrentURL();
|
||||
|
||||
return new URL(
|
||||
currentUrl.protocol,
|
||||
currentUrl.hostname,
|
||||
route,
|
||||
`metricName=${item.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const route: Route = RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.TELEMETRY_SERVICES_VIEW_METRIC]!,
|
||||
{
|
||||
modelId: props.telemetryServiceId,
|
||||
},
|
||||
RouteMap[PageMap.TELEMETRY_METRIC_VIEW]!,
|
||||
);
|
||||
|
||||
const currentUrl: URL = Navigation.getCurrentURL();
|
||||
|
||||
return new URL(
|
||||
const metricUrl: URL = new URL(
|
||||
currentUrl.protocol,
|
||||
currentUrl.hostname,
|
||||
route,
|
||||
`metricName=${item.name}&serviceName=${props.telemetryServiceName}`,
|
||||
);
|
||||
|
||||
const metricAttributes: Record<string, string> = {};
|
||||
|
||||
if (telemetryServiceFilterIds.length === 1) {
|
||||
const telemetryServiceId: ObjectID | undefined =
|
||||
telemetryServiceFilterIds[0];
|
||||
|
||||
const serviceIdString: string | undefined =
|
||||
telemetryServiceId?.toString();
|
||||
|
||||
if (serviceIdString) {
|
||||
metricAttributes["oneuptime.service.id"] = serviceIdString;
|
||||
|
||||
const matchingService: TelemetryService | undefined = (
|
||||
item.telemetryServices || []
|
||||
).find((service: TelemetryService) => {
|
||||
return service._id?.toString() === serviceIdString;
|
||||
});
|
||||
|
||||
if (matchingService?.name) {
|
||||
metricAttributes["oneuptime.service.name"] =
|
||||
matchingService.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const metricQueriesPayload: Array<Record<string, unknown>> = [
|
||||
{
|
||||
metricName: item.name || "",
|
||||
...(Object.keys(metricAttributes).length > 0
|
||||
? { attributes: metricAttributes }
|
||||
: {}),
|
||||
aggregationType: MetricsAggregationType.Avg,
|
||||
},
|
||||
];
|
||||
|
||||
metricUrl.addQueryParam(
|
||||
"metricQueries",
|
||||
JSON.stringify(metricQueriesPayload),
|
||||
true,
|
||||
);
|
||||
|
||||
return metricUrl;
|
||||
}}
|
||||
query={{
|
||||
projectId: ProjectUtil.getCurrentProjectId()!,
|
||||
telemetryServices: props.telemetryServiceId
|
||||
? new Includes([props.telemetryServiceId])
|
||||
: undefined,
|
||||
telemetryServices:
|
||||
telemetryServiceFilterIds.length > 0
|
||||
? new Includes(telemetryServiceFilterIds)
|
||||
: undefined,
|
||||
}}
|
||||
selectMoreFields={{
|
||||
telemetryServices: {
|
||||
_id: true,
|
||||
name: true,
|
||||
serviceColor: true,
|
||||
},
|
||||
}}
|
||||
showViewIdButton={false}
|
||||
noItemsMessage={"No metrics found for this service."}
|
||||
@@ -90,6 +126,23 @@ const MetricsTable: FunctionComponent<ComponentProps> = (
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
telemetryServices: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
title: "Telemetry Service",
|
||||
type: FieldType.EntityArray,
|
||||
filterEntityType: TelemetryService,
|
||||
filterQuery: {
|
||||
projectId: ProjectUtil.getCurrentProjectId()!,
|
||||
},
|
||||
filterDropdownField: {
|
||||
label: "name",
|
||||
value: "_id",
|
||||
},
|
||||
},
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
@@ -99,6 +152,24 @@ const MetricsTable: FunctionComponent<ComponentProps> = (
|
||||
title: "Name",
|
||||
type: FieldType.Text,
|
||||
},
|
||||
{
|
||||
field: {
|
||||
telemetryServices: {
|
||||
name: true,
|
||||
_id: true,
|
||||
serviceColor: true,
|
||||
},
|
||||
},
|
||||
title: "Telemetry Services",
|
||||
type: FieldType.Element,
|
||||
getElement: (item: MetricType): ReactElement => {
|
||||
return (
|
||||
<TelemetryServicesElement
|
||||
telemetryServices={item.telemetryServices || []}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Fragment>
|
||||
|
||||
95
Dashboard/src/Pages/ServiceCatalog/View/Alerts.tsx
Normal file
95
Dashboard/src/Pages/ServiceCatalog/View/Alerts.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import PageComponentProps from "../../PageComponentProps";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, {
|
||||
Fragment,
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import AlertsTable from "../../../Components/Alert/AlertsTable";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
|
||||
import ListResult from "Common/Types/BaseDatabase/ListResult";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import PageLoader from "Common/UI/Components/Loader/PageLoader";
|
||||
import ServiceCatalogMonitor from "Common/Models/DatabaseModels/ServiceCatalogMonitor";
|
||||
import Includes from "Common/Types/BaseDatabase/Includes";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import Alert from "Common/Models/DatabaseModels/Alert";
|
||||
|
||||
const ServiceCatalogAlerts: FunctionComponent<
|
||||
PageComponentProps
|
||||
> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
const [monitorIds, setMonitorIds] = useState<Array<ObjectID> | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchMonitorsInService: PromiseVoidFunction =
|
||||
async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const serviceCatalogMonitors: ListResult<ServiceCatalogMonitor> =
|
||||
await ModelAPI.getList<ServiceCatalogMonitor>({
|
||||
modelType: ServiceCatalogMonitor,
|
||||
query: {
|
||||
serviceCatalogId: modelId,
|
||||
},
|
||||
select: {
|
||||
monitorId: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
sort: {},
|
||||
});
|
||||
|
||||
const ids: ObjectID[] = serviceCatalogMonitors.data.map(
|
||||
(serviceCatalogMonitor: ServiceCatalogMonitor) => {
|
||||
return serviceCatalogMonitor.monitorId!;
|
||||
},
|
||||
);
|
||||
|
||||
setMonitorIds(ids);
|
||||
setIsLoading(false);
|
||||
} catch (err) {
|
||||
setIsLoading(false);
|
||||
setError(API.getFriendlyMessage(err));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitorsInService().catch((err: Error) => {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage message={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || monitorIds === null) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
const query: Query<Alert> = {
|
||||
monitorId: new Includes(monitorIds),
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<AlertsTable
|
||||
query={query}
|
||||
title="Service Alerts"
|
||||
description="Alerts generated by monitors attached to this service."
|
||||
noItemsMessage={"No alerts found for this service."}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceCatalogAlerts;
|
||||
97
Dashboard/src/Pages/ServiceCatalog/View/Logs.tsx
Normal file
97
Dashboard/src/Pages/ServiceCatalog/View/Logs.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import DashboardLogsViewer from "../../../Components/Logs/LogsViewer";
|
||||
import PageComponentProps from "../../PageComponentProps";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, {
|
||||
Fragment,
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import ServiceCatalogTelemetryService from "Common/Models/DatabaseModels/ServiceCatalogTelemetryService";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
|
||||
import ListResult from "Common/Types/BaseDatabase/ListResult";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import PageLoader from "Common/UI/Components/Loader/PageLoader";
|
||||
|
||||
const ServiceCatalogLogs: FunctionComponent<
|
||||
PageComponentProps
|
||||
> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
const [telemetryServiceIds, setTelemetryServiceIds] =
|
||||
useState<Array<ObjectID> | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchTelemetryServices: PromiseVoidFunction =
|
||||
async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response: ListResult<ServiceCatalogTelemetryService> =
|
||||
await ModelAPI.getList<ServiceCatalogTelemetryService>({
|
||||
modelType: ServiceCatalogTelemetryService,
|
||||
query: {
|
||||
serviceCatalogId: modelId,
|
||||
},
|
||||
select: {
|
||||
telemetryServiceId: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
sort: {},
|
||||
});
|
||||
|
||||
const ids: ObjectID[] = response.data.map(
|
||||
(serviceCatalogTelemetryService: ServiceCatalogTelemetryService) => {
|
||||
return serviceCatalogTelemetryService.telemetryServiceId!;
|
||||
},
|
||||
);
|
||||
|
||||
setTelemetryServiceIds(ids);
|
||||
setIsLoading(false);
|
||||
} catch (err) {
|
||||
setIsLoading(false);
|
||||
setError(API.getFriendlyMessage(err));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTelemetryServices().catch((err: Error) => {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage message={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || telemetryServiceIds === null) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
if (telemetryServiceIds.length === 0) {
|
||||
return (
|
||||
<ErrorMessage message="Assign telemetry services to this service to view logs." />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<DashboardLogsViewer
|
||||
id="service-catalog-logs"
|
||||
telemetryServiceIds={telemetryServiceIds}
|
||||
showFilters={true}
|
||||
enableRealtime={true}
|
||||
limit={100}
|
||||
noLogsMessage="No logs found for this service."
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceCatalogLogs;
|
||||
89
Dashboard/src/Pages/ServiceCatalog/View/Metrics.tsx
Normal file
89
Dashboard/src/Pages/ServiceCatalog/View/Metrics.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import MetricsTable from "../../../Components/Metrics/MetricsTable";
|
||||
import PageComponentProps from "../../PageComponentProps";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, {
|
||||
Fragment,
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import ServiceCatalogTelemetryService from "Common/Models/DatabaseModels/ServiceCatalogTelemetryService";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
|
||||
import ListResult from "Common/Types/BaseDatabase/ListResult";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import PageLoader from "Common/UI/Components/Loader/PageLoader";
|
||||
|
||||
const ServiceCatalogMetrics: FunctionComponent<
|
||||
PageComponentProps
|
||||
> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
const [telemetryServiceIds, setTelemetryServiceIds] =
|
||||
useState<Array<ObjectID> | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchTelemetryServices: PromiseVoidFunction =
|
||||
async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response: ListResult<ServiceCatalogTelemetryService> =
|
||||
await ModelAPI.getList<ServiceCatalogTelemetryService>({
|
||||
modelType: ServiceCatalogTelemetryService,
|
||||
query: {
|
||||
serviceCatalogId: modelId,
|
||||
},
|
||||
select: {
|
||||
telemetryServiceId: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
sort: {},
|
||||
});
|
||||
|
||||
const ids: ObjectID[] = response.data.map(
|
||||
(serviceCatalogTelemetryService: ServiceCatalogTelemetryService) => {
|
||||
return serviceCatalogTelemetryService.telemetryServiceId!;
|
||||
},
|
||||
);
|
||||
|
||||
setTelemetryServiceIds(ids);
|
||||
setIsLoading(false);
|
||||
} catch (err) {
|
||||
setIsLoading(false);
|
||||
setError(API.getFriendlyMessage(err));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTelemetryServices().catch((err: Error) => {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage message={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || telemetryServiceIds === null) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
if (telemetryServiceIds.length === 0) {
|
||||
return (
|
||||
<ErrorMessage message="Assign telemetry services to this service to view metrics." />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
<MetricsTable telemetryServiceIds={telemetryServiceIds} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceCatalogMetrics;
|
||||
@@ -66,18 +66,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
|
||||
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Incidents",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_INCIDENTS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Alert}
|
||||
/>
|
||||
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Telemetry",
|
||||
title: "Telemetry Services",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[
|
||||
PageMap.SERVICE_CATALOG_VIEW_TELEMETRY_SERVICES
|
||||
@@ -89,6 +78,65 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Operations">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Alerts",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_ALERTS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.BellRinging}
|
||||
/>
|
||||
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Incidents",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_INCIDENTS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Alert}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Telemetry">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Logs",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_LOGS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Logs}
|
||||
/>
|
||||
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Traces",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_TRACES] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Workflow}
|
||||
/>
|
||||
|
||||
<SideMenuItem
|
||||
link={{
|
||||
title: "Metrics",
|
||||
to: RouteUtil.populateRouteParams(
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_METRICS] as Route,
|
||||
{ modelId: props.modelId },
|
||||
),
|
||||
}}
|
||||
icon={IconProp.Graph}
|
||||
/>
|
||||
</SideMenuSection>
|
||||
|
||||
<SideMenuSection title="Advanced">
|
||||
<SideMenuItem
|
||||
link={{
|
||||
|
||||
100
Dashboard/src/Pages/ServiceCatalog/View/Traces.tsx
Normal file
100
Dashboard/src/Pages/ServiceCatalog/View/Traces.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import TraceTable from "../../../Components/Traces/TraceTable";
|
||||
import PageComponentProps from "../../PageComponentProps";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Navigation from "Common/UI/Utils/Navigation";
|
||||
import React, {
|
||||
Fragment,
|
||||
FunctionComponent,
|
||||
ReactElement,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import ServiceCatalogTelemetryService from "Common/Models/DatabaseModels/ServiceCatalogTelemetryService";
|
||||
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
||||
import ModelAPI from "Common/UI/Utils/ModelAPI/ModelAPI";
|
||||
import ListResult from "Common/Types/BaseDatabase/ListResult";
|
||||
import { LIMIT_PER_PROJECT } from "Common/Types/Database/LimitMax";
|
||||
import API from "Common/UI/Utils/API/API";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import PageLoader from "Common/UI/Components/Loader/PageLoader";
|
||||
import Includes from "Common/Types/BaseDatabase/Includes";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import Span from "Common/Models/AnalyticsModels/Span";
|
||||
|
||||
const ServiceCatalogTraces: FunctionComponent<
|
||||
PageComponentProps
|
||||
> = (): ReactElement => {
|
||||
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
|
||||
|
||||
const [telemetryServiceIds, setTelemetryServiceIds] =
|
||||
useState<Array<ObjectID> | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchTelemetryServices: PromiseVoidFunction =
|
||||
async (): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response: ListResult<ServiceCatalogTelemetryService> =
|
||||
await ModelAPI.getList<ServiceCatalogTelemetryService>({
|
||||
modelType: ServiceCatalogTelemetryService,
|
||||
query: {
|
||||
serviceCatalogId: modelId,
|
||||
},
|
||||
select: {
|
||||
telemetryServiceId: true,
|
||||
},
|
||||
limit: LIMIT_PER_PROJECT,
|
||||
skip: 0,
|
||||
sort: {},
|
||||
});
|
||||
|
||||
const ids: ObjectID[] = response.data.map(
|
||||
(serviceCatalogTelemetryService: ServiceCatalogTelemetryService) => {
|
||||
return serviceCatalogTelemetryService.telemetryServiceId!;
|
||||
},
|
||||
);
|
||||
|
||||
setTelemetryServiceIds(ids);
|
||||
setIsLoading(false);
|
||||
} catch (err) {
|
||||
setIsLoading(false);
|
||||
setError(API.getFriendlyMessage(err));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTelemetryServices().catch((err: Error) => {
|
||||
setError(API.getFriendlyMessage(err));
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return <ErrorMessage message={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || telemetryServiceIds === null) {
|
||||
return <PageLoader isVisible={true} />;
|
||||
}
|
||||
|
||||
if (telemetryServiceIds.length === 0) {
|
||||
return (
|
||||
<ErrorMessage message="Assign telemetry services to this service to view traces." />
|
||||
);
|
||||
}
|
||||
|
||||
const spanQuery: Query<Span> = {
|
||||
serviceId: new Includes(telemetryServiceIds),
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TraceTable
|
||||
spanQuery={spanQuery}
|
||||
noItemsMessage="No traces found for this service."
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServiceCatalogTraces;
|
||||
@@ -71,12 +71,7 @@ const MetricsTablePage: FunctionComponent<
|
||||
return <ErrorMessage message="Telemetry Service not found." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<MetricsTable
|
||||
telemetryServiceId={telemetryService.id!}
|
||||
telemetryServiceName={telemetryService.name!}
|
||||
/>
|
||||
);
|
||||
return <MetricsTable telemetryServiceIds={[telemetryService.id!]} />;
|
||||
};
|
||||
|
||||
export default MetricsTablePage;
|
||||
|
||||
@@ -45,12 +45,36 @@ const ServiceCatalogViewIncidents: LazyExoticComponent<
|
||||
return import("../Pages/ServiceCatalog/View/Incidents");
|
||||
});
|
||||
|
||||
const ServiceCatalogViewAlerts: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
return import("../Pages/ServiceCatalog/View/Alerts");
|
||||
});
|
||||
|
||||
const ServiceCatalogViewTelemetryServices: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
return import("../Pages/ServiceCatalog/View/TelemetryServices");
|
||||
});
|
||||
|
||||
const ServiceCatalogViewLogs: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
return import("../Pages/ServiceCatalog/View/Logs");
|
||||
});
|
||||
|
||||
const ServiceCatalogViewTraces: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
return import("../Pages/ServiceCatalog/View/Traces");
|
||||
});
|
||||
|
||||
const ServiceCatalogViewMetrics: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
return import("../Pages/ServiceCatalog/View/Metrics");
|
||||
});
|
||||
|
||||
const ServiceCatalogViewDelete: LazyExoticComponent<
|
||||
FunctionComponent<ComponentProps>
|
||||
> = lazy(() => {
|
||||
@@ -173,6 +197,22 @@ const ServiceCatalogRoutes: FunctionComponent<ComponentProps> = (
|
||||
}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(
|
||||
PageMap.SERVICE_CATALOG_VIEW_ALERTS,
|
||||
)}
|
||||
element={
|
||||
<Suspense fallback={Loader}>
|
||||
<ServiceCatalogViewAlerts
|
||||
{...props}
|
||||
pageRoute={
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_ALERTS] as Route
|
||||
}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(
|
||||
PageMap.SERVICE_CATALOG_VIEW_INCIDENTS,
|
||||
@@ -207,6 +247,50 @@ const ServiceCatalogRoutes: FunctionComponent<ComponentProps> = (
|
||||
}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(PageMap.SERVICE_CATALOG_VIEW_LOGS)}
|
||||
element={
|
||||
<Suspense fallback={Loader}>
|
||||
<ServiceCatalogViewLogs
|
||||
{...props}
|
||||
pageRoute={RouteMap[PageMap.SERVICE_CATALOG_VIEW_LOGS] as Route}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(
|
||||
PageMap.SERVICE_CATALOG_VIEW_TRACES,
|
||||
)}
|
||||
element={
|
||||
<Suspense fallback={Loader}>
|
||||
<ServiceCatalogViewTraces
|
||||
{...props}
|
||||
pageRoute={
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_TRACES] as Route
|
||||
}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(
|
||||
PageMap.SERVICE_CATALOG_VIEW_METRICS,
|
||||
)}
|
||||
element={
|
||||
<Suspense fallback={Loader}>
|
||||
<ServiceCatalogViewMetrics
|
||||
{...props}
|
||||
pageRoute={
|
||||
RouteMap[PageMap.SERVICE_CATALOG_VIEW_METRICS] as Route
|
||||
}
|
||||
/>
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
|
||||
<PageRoute
|
||||
path={RouteUtil.getLastPathForKey(
|
||||
PageMap.SERVICE_CATALOG_VIEW_OWNERS,
|
||||
|
||||
@@ -51,12 +51,36 @@ export function getServiceCatalogBreadcrumbs(
|
||||
"View Service",
|
||||
"Monitors",
|
||||
]),
|
||||
...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_ALERTS, [
|
||||
"Project",
|
||||
"Service Catalog",
|
||||
"View Service",
|
||||
"Alerts",
|
||||
]),
|
||||
...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_INCIDENTS, [
|
||||
"Project",
|
||||
"Service Catalog",
|
||||
"View Service",
|
||||
"Incidents",
|
||||
]),
|
||||
...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_LOGS, [
|
||||
"Project",
|
||||
"Service Catalog",
|
||||
"View Service",
|
||||
"Logs",
|
||||
]),
|
||||
...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_TRACES, [
|
||||
"Project",
|
||||
"Service Catalog",
|
||||
"View Service",
|
||||
"Traces",
|
||||
]),
|
||||
...BuildBreadcrumbLinksByTitles(PageMap.SERVICE_CATALOG_VIEW_METRICS, [
|
||||
"Project",
|
||||
"Service Catalog",
|
||||
"View Service",
|
||||
"Metrics",
|
||||
]),
|
||||
...BuildBreadcrumbLinksByTitles(
|
||||
PageMap.SERVICE_CATALOG_VIEW_TELEMETRY_SERVICES,
|
||||
["Project", "Service Catalog", "View Service", "Telemetry"],
|
||||
|
||||
@@ -165,6 +165,10 @@ enum PageMap {
|
||||
SERVICE_CATALOG_VIEW_SETTINGS = "SERVICE_CATALOG_VIEW_SETTINGS",
|
||||
SERVICE_CATALOG_VIEW_MONITORS = "SERVICE_CATALOG_VIEW_MONITORS",
|
||||
SERVICE_CATALOG_VIEW_INCIDENTS = "SERVICE_CATALOG_VIEW_INCIDENTS",
|
||||
SERVICE_CATALOG_VIEW_ALERTS = "SERVICE_CATALOG_VIEW_ALERTS",
|
||||
SERVICE_CATALOG_VIEW_LOGS = "SERVICE_CATALOG_VIEW_LOGS",
|
||||
SERVICE_CATALOG_VIEW_TRACES = "SERVICE_CATALOG_VIEW_TRACES",
|
||||
SERVICE_CATALOG_VIEW_METRICS = "SERVICE_CATALOG_VIEW_METRICS",
|
||||
SERVICE_CATALOG_VIEW_TELEMETRY_SERVICES = "SERVICE_CATALOG_VIEW_TELEMETRY_SERVICES",
|
||||
SERVICE_CATALOG_VIEW_OWNERS = "SERVICE_CATALOG_VIEW_OWNERS",
|
||||
SERVICE_CATALOG_VIEW_DEPENDENCIES = "SERVICE_CATALOG_VIEW_DEPENDENCIES",
|
||||
|
||||
@@ -39,6 +39,10 @@ export const ServiceCatalogRoutePath: Dictionary<string> = {
|
||||
[PageMap.SERVICE_CATALOG_VIEW_SETTINGS]: `${RouteParams.ModelID}/settings`,
|
||||
[PageMap.SERVICE_CATALOG_VIEW_MONITORS]: `${RouteParams.ModelID}/monitors`,
|
||||
[PageMap.SERVICE_CATALOG_VIEW_INCIDENTS]: `${RouteParams.ModelID}/incidents`,
|
||||
[PageMap.SERVICE_CATALOG_VIEW_ALERTS]: `${RouteParams.ModelID}/alerts`,
|
||||
[PageMap.SERVICE_CATALOG_VIEW_LOGS]: `${RouteParams.ModelID}/logs`,
|
||||
[PageMap.SERVICE_CATALOG_VIEW_TRACES]: `${RouteParams.ModelID}/traces`,
|
||||
[PageMap.SERVICE_CATALOG_VIEW_METRICS]: `${RouteParams.ModelID}/metrics`,
|
||||
[PageMap.SERVICE_CATALOG_VIEW_TELEMETRY_SERVICES]: `${RouteParams.ModelID}/telemetry-service`,
|
||||
};
|
||||
|
||||
@@ -947,6 +951,30 @@ const RouteMap: Dictionary<Route> = {
|
||||
}`,
|
||||
),
|
||||
|
||||
[PageMap.SERVICE_CATALOG_VIEW_ALERTS]: new Route(
|
||||
`/dashboard/${RouteParams.ProjectID}/service-catalog/${
|
||||
ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_ALERTS]
|
||||
}`,
|
||||
),
|
||||
|
||||
[PageMap.SERVICE_CATALOG_VIEW_LOGS]: new Route(
|
||||
`/dashboard/${RouteParams.ProjectID}/service-catalog/${
|
||||
ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_LOGS]
|
||||
}`,
|
||||
),
|
||||
|
||||
[PageMap.SERVICE_CATALOG_VIEW_TRACES]: new Route(
|
||||
`/dashboard/${RouteParams.ProjectID}/service-catalog/${
|
||||
ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_TRACES]
|
||||
}`,
|
||||
),
|
||||
|
||||
[PageMap.SERVICE_CATALOG_VIEW_METRICS]: new Route(
|
||||
`/dashboard/${RouteParams.ProjectID}/service-catalog/${
|
||||
ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_METRICS]
|
||||
}`,
|
||||
),
|
||||
|
||||
[PageMap.SERVICE_CATALOG_VIEW_TELEMETRY_SERVICES]: new Route(
|
||||
`/dashboard/${RouteParams.ProjectID}/service-catalog/${
|
||||
ServiceCatalogRoutePath[PageMap.SERVICE_CATALOG_VIEW_TELEMETRY_SERVICES]
|
||||
|
||||
Reference in New Issue
Block a user