mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 08:42:13 +02:00
Compare commits
4 Commits
mult-slack
...
dashboard-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4e64c88d9 | ||
|
|
196c4ae42d | ||
|
|
12eea138d3 | ||
|
|
65dd8f857c |
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@@ -42,15 +42,15 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
VERSION_PREFIX_RAW="$(tr -d ' \n' < VERSION_PREFIX)"
|
||||
if [[ -z "$VERSION_PREFIX_RAW" ]]; then
|
||||
echo "VERSION_PREFIX is empty" >&2
|
||||
VERSION_RAW="$(tr -d ' \n' < VERSION)"
|
||||
if [[ -z "$VERSION_RAW" ]]; then
|
||||
echo "VERSION is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IFS='.' read -r major minor patch <<< "$VERSION_PREFIX_RAW"
|
||||
IFS='.' read -r major minor patch <<< "$VERSION_RAW"
|
||||
if [[ -z "$minor" ]]; then
|
||||
echo "VERSION_PREFIX must contain major and minor components" >&2
|
||||
echo "VERSION must contain major and minor components" >&2
|
||||
exit 1
|
||||
fi
|
||||
patch="${patch:-0}"
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
for part_name in major minor patch; do
|
||||
part="${!part_name}"
|
||||
if ! [[ "$part" =~ ^[0-9]+$ ]]; then
|
||||
echo "Invalid ${part_name} component '$part' in VERSION_PREFIX" >&2
|
||||
echo "Invalid ${part_name} component '$part' in VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
@@ -82,18 +82,18 @@ jobs:
|
||||
fi
|
||||
|
||||
new_version="${major}.${minor}.${target_patch}"
|
||||
if [[ "$new_version" != "$VERSION_PREFIX_RAW" ]]; then
|
||||
echo "$new_version" > VERSION_PREFIX
|
||||
if [[ "$new_version" != "$VERSION_RAW" ]]; then
|
||||
echo "$new_version" > VERSION
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add VERSION_PREFIX
|
||||
git add VERSION
|
||||
if ! git diff --cached --quiet; then
|
||||
branch_name="chore/bump-version-prefix-${new_version}-$(date +%s)"
|
||||
git checkout -b "$branch_name"
|
||||
git commit -m "chore: bump version prefix to ${new_version} [skip ci]"
|
||||
git push origin "$branch_name"
|
||||
pr_title="chore: bump version prefix to ${new_version}"
|
||||
pr_body=$'Automated change to VERSION_PREFIX to align release workflow with master.\n\nCreated by GitHub Actions release workflow.'
|
||||
pr_body=$'Automated change to VERSION to align release workflow with master.\n\nCreated by GitHub Actions release workflow.'
|
||||
gh pr create --repo "$REPOSITORY" --base master --head "$branch_name" --title "$pr_title" --body "$pr_body"
|
||||
pr_url="$(gh pr view "$branch_name" --repo "$REPOSITORY" --json url --jq '.url' 2>/dev/null || true)"
|
||||
updated="true"
|
||||
|
||||
12
.github/workflows/test-release.yaml
vendored
12
.github/workflows/test-release.yaml
vendored
@@ -41,15 +41,15 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
VERSION_PREFIX_RAW="$(tr -d ' \n' < VERSION_PREFIX)"
|
||||
if [[ -z "$VERSION_PREFIX_RAW" ]]; then
|
||||
echo "VERSION_PREFIX is empty" >&2
|
||||
VERSION_RAW="$(tr -d ' \n' < VERSION)"
|
||||
if [[ -z "$VERSION_RAW" ]]; then
|
||||
echo "VERSION is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IFS='.' read -r major minor patch <<< "$VERSION_PREFIX_RAW"
|
||||
IFS='.' read -r major minor patch <<< "$VERSION_RAW"
|
||||
if [[ -z "$minor" ]]; then
|
||||
echo "VERSION_PREFIX must contain major and minor components" >&2
|
||||
echo "VERSION must contain major and minor components" >&2
|
||||
exit 1
|
||||
fi
|
||||
patch="${patch:-0}"
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
for part_name in major minor patch; do
|
||||
part="${!part_name}"
|
||||
if ! [[ "$part" =~ ^[0-9]+$ ]]; then
|
||||
echo "Invalid ${part_name} component '$part' in VERSION_PREFIX" >&2
|
||||
echo "Invalid ${part_name} component '$part' in VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -2,6 +2,7 @@ enum DashboardComponentType {
|
||||
Chart = `Chart`,
|
||||
Value = `Value`,
|
||||
Text = `Text`,
|
||||
Logs = `Logs`,
|
||||
}
|
||||
|
||||
export default DashboardComponentType;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import ObjectID from "../../ObjectID";
|
||||
import DashboardComponentType from "../DashboardComponentType";
|
||||
import BaseComponent from "./DashboardBaseComponent";
|
||||
|
||||
export default interface DashboardLogsComponent extends BaseComponent {
|
||||
componentType: DashboardComponentType.Logs;
|
||||
componentId: ObjectID;
|
||||
arguments: {
|
||||
title?: string | undefined;
|
||||
telemetryServiceIdsCsv?: string | undefined;
|
||||
logQueryJson?: string | undefined;
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@ enum DashboardComponentType {
|
||||
Chart = "Chart",
|
||||
Value = "Value",
|
||||
Text = "Text",
|
||||
Logs = "Logs",
|
||||
}
|
||||
|
||||
export default DashboardComponentType;
|
||||
|
||||
68
Common/Utils/Dashboard/Components/DashboardLogsComponent.ts
Normal file
68
Common/Utils/Dashboard/Components/DashboardLogsComponent.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import DashboardLogsComponent from "../../../Types/Dashboard/DashboardComponents/DashboardLogsComponent";
|
||||
import { ObjectType } from "../../../Types/JSON";
|
||||
import ObjectID from "../../../Types/ObjectID";
|
||||
import DashboardBaseComponentUtil from "./DashboardBaseComponent";
|
||||
import {
|
||||
ComponentArgument,
|
||||
ComponentInputType,
|
||||
} from "../../../Types/Dashboard/DashboardComponents/ComponentArgument";
|
||||
import DashboardComponentType from "../../../Types/Dashboard/DashboardComponentType";
|
||||
|
||||
export default class DashboardLogsComponentUtil extends DashboardBaseComponentUtil {
|
||||
public static override getDefaultComponent(): DashboardLogsComponent {
|
||||
return {
|
||||
_type: ObjectType.DashboardComponent,
|
||||
componentType: DashboardComponentType.Logs,
|
||||
widthInDashboardUnits: 8,
|
||||
heightInDashboardUnits: 6,
|
||||
topInDashboardUnits: 0,
|
||||
leftInDashboardUnits: 0,
|
||||
componentId: ObjectID.generate(),
|
||||
minHeightInDashboardUnits: 4,
|
||||
minWidthInDashboardUnits: 6,
|
||||
arguments: {
|
||||
title: "Logs",
|
||||
telemetryServiceIdsCsv: "",
|
||||
logQueryJson: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public static override getComponentConfigArguments(): Array<
|
||||
ComponentArgument<DashboardLogsComponent>
|
||||
> {
|
||||
const componentArguments: Array<
|
||||
ComponentArgument<DashboardLogsComponent>
|
||||
> = [];
|
||||
|
||||
componentArguments.push({
|
||||
name: "Title",
|
||||
description: "Optional heading shown above the logs widget.",
|
||||
required: false,
|
||||
type: ComponentInputType.Text,
|
||||
id: "title",
|
||||
});
|
||||
|
||||
componentArguments.push({
|
||||
name: "Telemetry Service IDs",
|
||||
description:
|
||||
"Comma separated telemetry service IDs (UUIDs) to scope logs.",
|
||||
required: false,
|
||||
type: ComponentInputType.Text,
|
||||
id: "telemetryServiceIdsCsv",
|
||||
placeholder: "service-id-1, service-id-2",
|
||||
});
|
||||
|
||||
componentArguments.push({
|
||||
name: "Advanced Log Query (JSON)",
|
||||
description:
|
||||
"Optional JSON object merged with the generated query for advanced filters.",
|
||||
required: false,
|
||||
type: ComponentInputType.LongText,
|
||||
id: "logQueryJson",
|
||||
placeholder: '{ "attributes.environment": "prod" }',
|
||||
});
|
||||
|
||||
return componentArguments;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import BadDataException from "../../../Types/Exception/BadDataException";
|
||||
import DashboardChartComponentUtil from "./DashboardChartComponent";
|
||||
import DashboardTextComponentUtil from "./DashboardTextComponent";
|
||||
import DashboardValueComponentUtil from "./DashboardValueComponent";
|
||||
import DashboardLogsComponentUtil from "./DashboardLogsComponent";
|
||||
|
||||
export default class DashboardComponentsUtil {
|
||||
public static getComponentSettingsArguments(
|
||||
@@ -28,6 +29,12 @@ export default class DashboardComponentsUtil {
|
||||
>;
|
||||
}
|
||||
|
||||
if (dashboardComponentType === DashboardComponentType.Logs) {
|
||||
return DashboardLogsComponentUtil.getComponentConfigArguments() as Array<
|
||||
ComponentArgument<DashboardBaseComponent>
|
||||
>;
|
||||
}
|
||||
|
||||
throw new BadDataException(
|
||||
`Unknown dashboard component type: ${dashboardComponentType}`,
|
||||
);
|
||||
|
||||
@@ -3,9 +3,11 @@ import DashboardTextComponentType from "Common/Types/Dashboard/DashboardComponen
|
||||
import DashboardChartComponentType from "Common/Types/Dashboard/DashboardComponents/DashboardChartComponent";
|
||||
import DashboardValueComponentType from "Common/Types/Dashboard/DashboardComponents/DashboardValueComponent";
|
||||
import DashboardBaseComponent from "Common/Types/Dashboard/DashboardComponents/DashboardBaseComponent";
|
||||
import DashboardLogsComponentType from "Common/Types/Dashboard/DashboardComponents/DashboardLogsComponent";
|
||||
import DashboardChartComponent from "./DashboardChartComponent";
|
||||
import DashboardValueComponent from "./DashboardValueComponent";
|
||||
import DashboardTextComponent from "./DashboardTextComponent";
|
||||
import DashboardLogsComponent from "./DashboardLogsComponent";
|
||||
import DefaultDashboardSize, {
|
||||
GetDashboardComponentHeightInDashboardUnits,
|
||||
GetDashboardComponentWidthInDashboardUnits,
|
||||
@@ -404,6 +406,14 @@ const DashboardBaseComponentElement: FunctionComponent<ComponentProps> = (
|
||||
component={component as DashboardValueComponentType}
|
||||
/>
|
||||
)}
|
||||
{component.componentType === DashboardComponentType.Logs && (
|
||||
<DashboardLogsComponent
|
||||
{...props}
|
||||
isSelected={props.isSelected}
|
||||
isEditMode={props.isEditMode}
|
||||
component={component as DashboardLogsComponentType}
|
||||
/>
|
||||
)}
|
||||
|
||||
{getResizeWidthElement()}
|
||||
{getResizeHeightElement()}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import React, { FunctionComponent, ReactElement, useMemo } from "react";
|
||||
import { DashboardBaseComponentProps } from "./DashboardBaseComponent";
|
||||
import DashboardLogsComponentType from "Common/Types/Dashboard/DashboardComponents/DashboardLogsComponent";
|
||||
import DashboardLogsViewer from "../../Logs/LogsViewer";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Query from "Common/Types/BaseDatabase/Query";
|
||||
import Log from "Common/Models/AnalyticsModels/Log";
|
||||
import JSONFunctions from "Common/Types/JSONFunctions";
|
||||
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
|
||||
import InBetween from "Common/Types/BaseDatabase/InBetween";
|
||||
import { RangeStartAndEndDateTimeUtil } from "Common/Types/Time/RangeStartAndEndDateTime";
|
||||
|
||||
export interface ComponentProps extends DashboardBaseComponentProps {
|
||||
component: DashboardLogsComponentType;
|
||||
}
|
||||
|
||||
const DashboardLogsComponent: FunctionComponent<ComponentProps> = (
|
||||
props: ComponentProps,
|
||||
): ReactElement => {
|
||||
const args: DashboardLogsComponentType["arguments"] =
|
||||
props.component.arguments || {};
|
||||
|
||||
const sanitizeCsv: (value?: string) => Array<string> = (
|
||||
value?: string,
|
||||
): Array<string> => {
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return value
|
||||
.split(",")
|
||||
.map((entry: string) => entry.trim())
|
||||
.filter((entry: string) => entry.length > 0);
|
||||
};
|
||||
|
||||
const telemetryServiceIds: Array<ObjectID> = useMemo(() => {
|
||||
return sanitizeCsv(args.telemetryServiceIdsCsv).reduce(
|
||||
(ids: Array<ObjectID>, id: string) => {
|
||||
try {
|
||||
ids.push(ObjectID.fromString(id));
|
||||
} catch (err) {
|
||||
// ignore invalid ids to avoid breaking the widget.
|
||||
}
|
||||
|
||||
return ids;
|
||||
},
|
||||
[],
|
||||
);
|
||||
}, [args.telemetryServiceIdsCsv]);
|
||||
|
||||
const limit: number = 100;
|
||||
|
||||
const logQueryResult: {
|
||||
query: Query<Log>;
|
||||
error: string | null;
|
||||
} = useMemo(() => {
|
||||
const mergedQuery: Query<Log> = {};
|
||||
let error: string | null = null;
|
||||
|
||||
const rawQuery: string = (args.logQueryJson || "").trim();
|
||||
|
||||
if (rawQuery) {
|
||||
try {
|
||||
const parsedQuery: Query<Log> = JSONFunctions.parseJSONObject(
|
||||
rawQuery,
|
||||
) as Query<Log>;
|
||||
Object.assign(mergedQuery, parsedQuery);
|
||||
} catch (err) {
|
||||
error = `Invalid log query JSON. ${(err as Error).message}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(mergedQuery as Record<string, unknown>)["time"]) {
|
||||
const range: InBetween<Date> =
|
||||
RangeStartAndEndDateTimeUtil.getStartAndEndDate(
|
||||
props.dashboardStartAndEndDate,
|
||||
);
|
||||
|
||||
Object.assign(mergedQuery as Record<string, unknown>, {
|
||||
time: new InBetween<Date>(range.startValue, range.endValue),
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
query: mergedQuery,
|
||||
error,
|
||||
};
|
||||
}, [
|
||||
args.logQueryJson,
|
||||
props.dashboardStartAndEndDate,
|
||||
]);
|
||||
|
||||
if (logQueryResult.error) {
|
||||
return <ErrorMessage message={logQueryResult.error} />;
|
||||
}
|
||||
|
||||
const showFilters: boolean = true;
|
||||
const enableRealtime: boolean = true;
|
||||
const noLogsMessage: string = "No logs found.";
|
||||
const title: string = args.title || "Logs";
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
{title ? (
|
||||
<div className="mb-2 text-sm font-medium text-gray-500">{title}</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<div className="flex-1 overflow-auto" style={{ minHeight: 0 }}>
|
||||
<DashboardLogsViewer
|
||||
id={props.component.componentId.toString()}
|
||||
showFilters={showFilters}
|
||||
telemetryServiceIds={telemetryServiceIds}
|
||||
logQuery={logQueryResult.query}
|
||||
limit={limit}
|
||||
enableRealtime={enableRealtime}
|
||||
noLogsMessage={noLogsMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLogsComponent;
|
||||
@@ -15,6 +15,7 @@ import DashboardBaseComponent from "Common/Types/Dashboard/DashboardComponents/D
|
||||
import DashboardChartComponentUtil from "Common/Utils/Dashboard/Components/DashboardChartComponent";
|
||||
import DashboardValueComponentUtil from "Common/Utils/Dashboard/Components/DashboardValueComponent";
|
||||
import DashboardTextComponentUtil from "Common/Utils/Dashboard/Components/DashboardTextComponent";
|
||||
import DashboardLogsComponentUtil from "Common/Utils/Dashboard/Components/DashboardLogsComponent";
|
||||
import BadDataException from "Common/Types/Exception/BadDataException";
|
||||
import ObjectID from "Common/Types/ObjectID";
|
||||
import Dashboard from "Common/Models/DatabaseModels/Dashboard";
|
||||
@@ -253,6 +254,10 @@ const DashboardViewer: FunctionComponent<ComponentProps> = (
|
||||
newComponent = DashboardTextComponentUtil.getDefaultComponent();
|
||||
}
|
||||
|
||||
if (componentType === DashboardComponentType.Logs) {
|
||||
newComponent = DashboardLogsComponentUtil.getDefaultComponent();
|
||||
}
|
||||
|
||||
if (!newComponent) {
|
||||
throw new BadDataException(
|
||||
`Unknown component type: ${componentType}`,
|
||||
|
||||
@@ -81,6 +81,13 @@ const DashboardToolbar: FunctionComponent<ComponentProps> = (
|
||||
props.onAddComponentClick(DashboardComponentType.Text);
|
||||
}}
|
||||
/>
|
||||
<MoreMenuItem
|
||||
text={"Add Logs"}
|
||||
key={"add-logs"}
|
||||
onClick={() => {
|
||||
props.onAddComponentClick(DashboardComponentType.Logs);
|
||||
}}
|
||||
/>
|
||||
</MoreMenu>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
Reference in New Issue
Block a user