@oneuptime/common 11.4.0 → 11.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Server/API/AIAgentTaskAPI.ts +10 -24
- package/Server/API/TelemetryAPI.ts +4 -3
- package/Server/Services/AIAgentTaskService.ts +62 -0
- package/Server/Services/AnalyticsDatabaseService.ts +27 -0
- package/Server/Services/LogAggregationService.ts +29 -11
- package/Server/Services/MetricService.ts +603 -13
- package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +1 -1
- package/Server/Utils/AI/Sentinel/README.md +10 -0
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +44 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +108 -2
- package/Tests/Server/Services/AIAgentTaskServiceClaim.test.ts +146 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +22 -0
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +194 -0
- package/Tests/Types/AI/AIAgentTaskStatus.test.ts +86 -0
- package/Tests/Types/BaseDatabase/AggregationIntervalUtil.test.ts +127 -0
- package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.test.ts +152 -0
- package/Tests/Types/Trace/TraceRecordingRuleDefinition.test.ts +308 -0
- package/Tests/UI/Components/Dropdown.test.tsx +17 -0
- package/Tests/UI/Components/EntityDropdown.test.tsx +130 -0
- package/Tests/UI/Components/Modal.test.tsx +144 -2
- package/Tests/UI/Components/TelemetrySearchBar.test.tsx +90 -0
- package/Tests/UI/Utils/Theme.test.ts +161 -0
- package/Types/BaseDatabase/AggregateBy.ts +12 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.ts +19 -0
- package/Types/Dashboard/DashboardTemplates.ts +9 -1
- package/Types/Monitor/CriteriaFilter.ts +4 -0
- package/Types/Monitor/HttpPhaseTimings.ts +16 -0
- package/Types/Monitor/MonitorMetricType.ts +17 -0
- package/Types/Monitor/PingMonitor/PingMonitorResponse.ts +20 -0
- package/Types/Probe/ProbeMonitorResponse.ts +14 -0
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -5
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +4 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +6 -3
- package/UI/Components/CodeBlock/CodeBlock.tsx +2 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
- package/UI/Components/Divider/Divider.tsx +1 -1
- package/UI/Components/Dropdown/Dropdown.tsx +13 -4
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +110 -2
- package/UI/Components/ErrorBoundary.tsx +2 -1
- package/UI/Components/EventItem/EventItem.tsx +2 -2
- package/UI/Components/GanttChart/Bar/Index.tsx +1 -1
- package/UI/Components/Graphs/UptimeBarTooltip.tsx +30 -23
- package/UI/Components/Header/Header.tsx +7 -0
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +2 -0
- package/UI/Components/Label/Label.tsx +2 -2
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +22 -10
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +9 -3
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +8 -8
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +44 -20
- package/UI/Components/Modal/ConfirmModal.tsx +1 -1
- package/UI/Components/Modal/Modal.tsx +213 -121
- package/UI/Components/Modal/ModalFooter.tsx +33 -22
- package/UI/Components/ModelTable/TableView.tsx +1 -1
- package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +20 -12
- package/UI/Components/Pill/Pill.tsx +21 -13
- package/UI/Components/StatusBubble/StatusBubble.tsx +3 -3
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +7 -1
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +9 -3
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +62 -4
- package/UI/Components/Workflow/Component.tsx +53 -33
- package/UI/Components/Workflow/ComponentPortViewer.tsx +6 -6
- package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +8 -8
- package/UI/Components/Workflow/ComponentsModal.tsx +16 -9
- package/UI/Components/Workflow/Workflow.tsx +13 -11
- package/UI/Utils/Theme.ts +140 -0
- package/Utils/Dashboard/Components/DashboardLogChartComponent.ts +104 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/Monitor/MonitorMetricType.ts +70 -2
- package/build/dist/Server/API/AIAgentTaskAPI.js +8 -23
- package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Services/AIAgentTaskService.js +59 -0
- package/build/dist/Server/Services/AIAgentTaskService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +19 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +21 -7
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +449 -7
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +29 -0
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +86 -2
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +9 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/HttpPhaseTimings.js +2 -0
- package/build/dist/Types/Monitor/HttpPhaseTimings.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js +15 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js +2 -0
- package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js.map +1 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +7 -4
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +4 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +6 -3
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +3 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/Divider/Divider.js +1 -1
- package/build/dist/UI/Components/Divider/Divider.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +10 -4
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +76 -3
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +2 -1
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
- package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js +1 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +29 -23
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
- package/build/dist/UI/Components/Header/Header.js +1 -0
- package/build/dist/UI/Components/Header/Header.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Label/Label.js +2 -2
- package/build/dist/UI/Components/Label/Label.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +20 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +8 -2
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +8 -8
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +40 -20
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +109 -51
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +13 -10
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +17 -12
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
- package/build/dist/UI/Components/Pill/Pill.js +12 -13
- package/build/dist/UI/Components/Pill/Pill.js.map +1 -1
- package/build/dist/UI/Components/StatusBubble/StatusBubble.js +3 -3
- package/build/dist/UI/Components/StatusBubble/StatusBubble.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -2
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +36 -6
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Component.js +43 -33
- package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentPortViewer.js +6 -6
- package/build/dist/UI/Components/Workflow/ComponentPortViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +8 -8
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +15 -9
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +13 -11
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Utils/Theme.js +106 -0
- package/build/dist/UI/Utils/Theme.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js +92 -0
- package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +70 -3
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import EntityDropdown from "../../../UI/Components/EntityDropdown/EntityDropdown";
|
|
2
|
+
import {
|
|
3
|
+
DROPDOWN_MENU_Z_INDEX,
|
|
4
|
+
DropdownOption,
|
|
5
|
+
} from "../../../UI/Components/Dropdown/Dropdown";
|
|
6
|
+
import getJestMockFunction, { MockFunction } from "../../../Tests/MockType";
|
|
7
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
8
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
9
|
+
import React from "react";
|
|
10
|
+
|
|
11
|
+
describe("EntityDropdown", () => {
|
|
12
|
+
const options: Array<DropdownOption> = [
|
|
13
|
+
{ value: "members", label: "Members" },
|
|
14
|
+
{ value: "admins", label: "Admins" },
|
|
15
|
+
];
|
|
16
|
+
const originalInnerHeight: number = window.innerHeight;
|
|
17
|
+
const originalInnerWidth: number = window.innerWidth;
|
|
18
|
+
|
|
19
|
+
const setViewport: (width: number, height: number) => void = (
|
|
20
|
+
width: number,
|
|
21
|
+
height: number,
|
|
22
|
+
): void => {
|
|
23
|
+
Object.defineProperty(window, "innerWidth", {
|
|
24
|
+
configurable: true,
|
|
25
|
+
value: width,
|
|
26
|
+
});
|
|
27
|
+
Object.defineProperty(window, "innerHeight", {
|
|
28
|
+
configurable: true,
|
|
29
|
+
value: height,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const makeRect: (
|
|
34
|
+
left: number,
|
|
35
|
+
top: number,
|
|
36
|
+
width: number,
|
|
37
|
+
height: number,
|
|
38
|
+
) => DOMRect = (
|
|
39
|
+
left: number,
|
|
40
|
+
top: number,
|
|
41
|
+
width: number,
|
|
42
|
+
height: number,
|
|
43
|
+
): DOMRect => {
|
|
44
|
+
return {
|
|
45
|
+
bottom: top + height,
|
|
46
|
+
height,
|
|
47
|
+
left,
|
|
48
|
+
right: left + width,
|
|
49
|
+
top,
|
|
50
|
+
width,
|
|
51
|
+
x: left,
|
|
52
|
+
y: top,
|
|
53
|
+
toJSON: (): Record<string, never> => {
|
|
54
|
+
return {};
|
|
55
|
+
},
|
|
56
|
+
} as DOMRect;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const openDropdownAt: (rect: DOMRect) => HTMLInputElement = (
|
|
60
|
+
rect: DOMRect,
|
|
61
|
+
): HTMLInputElement => {
|
|
62
|
+
const input: HTMLInputElement = screen.getByRole("combobox", {
|
|
63
|
+
name: "Team",
|
|
64
|
+
}) as HTMLInputElement;
|
|
65
|
+
const control: HTMLElement | null =
|
|
66
|
+
input.parentElement?.parentElement || null;
|
|
67
|
+
|
|
68
|
+
if (!control) {
|
|
69
|
+
throw new Error("EntityDropdown control was not rendered.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
jest.spyOn(control, "getBoundingClientRect").mockReturnValue(rect);
|
|
73
|
+
fireEvent.focus(input);
|
|
74
|
+
|
|
75
|
+
return input;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
cleanup();
|
|
80
|
+
jest.restoreAllMocks();
|
|
81
|
+
setViewport(originalInnerWidth, originalInnerHeight);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("positions its fixed menu below the control above modal surfaces", () => {
|
|
85
|
+
setViewport(1000, 800);
|
|
86
|
+
render(<EntityDropdown ariaLabel="Team" options={options} />);
|
|
87
|
+
|
|
88
|
+
openDropdownAt(makeRect(120, 200, 320, 40));
|
|
89
|
+
|
|
90
|
+
const menu: HTMLElement = screen.getByTestId("entity-dropdown-menu");
|
|
91
|
+
|
|
92
|
+
expect(menu.classList.contains("fixed")).toBe(true);
|
|
93
|
+
expect(menu.style.top).toBe("244px");
|
|
94
|
+
expect(menu.style.bottom).toBe("");
|
|
95
|
+
expect(menu.style.left).toBe("120px");
|
|
96
|
+
expect(menu.style.width).toBe("320px");
|
|
97
|
+
expect(menu.style.maxHeight).toBe("384px");
|
|
98
|
+
expect(menu.style.visibility).toBe("visible");
|
|
99
|
+
expect(menu.style.zIndex).toBe(String(DROPDOWN_MENU_Z_INDEX));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("flips its menu above a control near the modal footer", () => {
|
|
103
|
+
setViewport(1000, 600);
|
|
104
|
+
render(<EntityDropdown ariaLabel="Team" options={options} />);
|
|
105
|
+
|
|
106
|
+
openDropdownAt(makeRect(120, 520, 320, 40));
|
|
107
|
+
|
|
108
|
+
const menu: HTMLElement = screen.getByTestId("entity-dropdown-menu");
|
|
109
|
+
|
|
110
|
+
expect(menu.style.top).toBe("");
|
|
111
|
+
expect(menu.style.bottom).toBe("84px");
|
|
112
|
+
expect(menu.style.maxHeight).toBe("384px");
|
|
113
|
+
expect(menu.style.visibility).toBe("visible");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("selects an option from the fixed menu", () => {
|
|
117
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
118
|
+
setViewport(1000, 800);
|
|
119
|
+
render(
|
|
120
|
+
<EntityDropdown ariaLabel="Team" onChange={onChange} options={options} />,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
openDropdownAt(makeRect(120, 200, 320, 40));
|
|
124
|
+
fireEvent.click(screen.getByRole("option", { name: "Members" }));
|
|
125
|
+
|
|
126
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
127
|
+
expect(onChange).toHaveBeenCalledWith("members");
|
|
128
|
+
expect(screen.queryByTestId("entity-dropdown-menu")).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -26,6 +26,148 @@ describe("Modal", () => {
|
|
|
26
26
|
expect(getByText("Modal content")).toBeInTheDocument();
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
it("renders an accessible elevated dialog shell", () => {
|
|
30
|
+
const onCloseMock: MockFunction = getJestMockFunction();
|
|
31
|
+
const { getByTestId } = render(
|
|
32
|
+
<Modal
|
|
33
|
+
title="Accessible Modal"
|
|
34
|
+
description="A concise description"
|
|
35
|
+
onClose={onCloseMock}
|
|
36
|
+
>
|
|
37
|
+
<div>Modal content</div>
|
|
38
|
+
</Modal>,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const modal: HTMLElement = getByTestId("modal");
|
|
42
|
+
const title: HTMLElement = getByTestId("modal-title");
|
|
43
|
+
const description: HTMLElement = getByTestId("modal-description");
|
|
44
|
+
const closeButton: HTMLElement = getByTestId("close-button");
|
|
45
|
+
|
|
46
|
+
expect(modal).toHaveAttribute("role", "dialog");
|
|
47
|
+
expect(modal).toHaveAttribute("aria-modal", "true");
|
|
48
|
+
expect(modal).toHaveAttribute("aria-labelledby", title.id);
|
|
49
|
+
expect(modal).toHaveAttribute("aria-describedby", description.id);
|
|
50
|
+
expect(modal).toHaveClass("rounded-t-2xl", "shadow-2xl");
|
|
51
|
+
expect(getByTestId("modal-backdrop")).toHaveClass(
|
|
52
|
+
"bg-gray-950/45",
|
|
53
|
+
"backdrop-blur-[2px]",
|
|
54
|
+
);
|
|
55
|
+
expect(closeButton).toHaveClass("inline-flex");
|
|
56
|
+
expect(closeButton).not.toHaveClass("md:hidden");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("keeps keyboard focus inside the modal", () => {
|
|
60
|
+
const onCloseMock: MockFunction = getJestMockFunction();
|
|
61
|
+
const onSubmitMock: MockFunction = getJestMockFunction();
|
|
62
|
+
const { getByLabelText, getByTestId } = render(
|
|
63
|
+
<Modal title="Focus Modal" onClose={onCloseMock} onSubmit={onSubmitMock}>
|
|
64
|
+
<input aria-label="Modal input" />
|
|
65
|
+
</Modal>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const input: HTMLElement = getByLabelText("Modal input");
|
|
69
|
+
const headerCloseButton: HTMLElement = getByTestId("close-button");
|
|
70
|
+
const submitButton: HTMLElement = getByTestId("modal-footer-submit-button");
|
|
71
|
+
|
|
72
|
+
expect(input).toHaveFocus();
|
|
73
|
+
|
|
74
|
+
submitButton.focus();
|
|
75
|
+
fireEvent.keyDown(document, { key: "Tab" });
|
|
76
|
+
expect(headerCloseButton).toHaveFocus();
|
|
77
|
+
|
|
78
|
+
fireEvent.keyDown(document, { key: "Tab", shiftKey: true });
|
|
79
|
+
expect(submitButton).toHaveFocus();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("closes only the topmost modal and restores focus to its opener", () => {
|
|
83
|
+
const onParentCloseMock: MockFunction = getJestMockFunction();
|
|
84
|
+
const onChildCloseMock: MockFunction = getJestMockFunction();
|
|
85
|
+
const onChildSubmitMock: MockFunction = getJestMockFunction();
|
|
86
|
+
|
|
87
|
+
const NestedModalFixture: React.FunctionComponent =
|
|
88
|
+
(): React.ReactElement => {
|
|
89
|
+
const [isChildOpen, setIsChildOpen] = React.useState<boolean>(false);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<Modal title="Parent Modal" onClose={onParentCloseMock}>
|
|
93
|
+
<>
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
onClick={() => {
|
|
97
|
+
setIsChildOpen(true);
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
Open child
|
|
101
|
+
</button>
|
|
102
|
+
{isChildOpen && (
|
|
103
|
+
<Modal
|
|
104
|
+
title="Child Modal"
|
|
105
|
+
onSubmit={onChildSubmitMock}
|
|
106
|
+
onClose={() => {
|
|
107
|
+
onChildCloseMock();
|
|
108
|
+
setIsChildOpen(false);
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<div>Child content</div>
|
|
112
|
+
</Modal>
|
|
113
|
+
)}
|
|
114
|
+
</>
|
|
115
|
+
</Modal>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const { getByRole } = render(<NestedModalFixture />);
|
|
120
|
+
const childOpener: HTMLElement = getByRole("button", {
|
|
121
|
+
name: "Open child",
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
fireEvent.click(childOpener);
|
|
125
|
+
fireEvent.keyDown(document, { key: "Escape" });
|
|
126
|
+
|
|
127
|
+
expect(onChildCloseMock).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(onParentCloseMock).not.toHaveBeenCalled();
|
|
129
|
+
expect(childOpener).toHaveFocus();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("restores focus when a modal contains an auto-focused field", () => {
|
|
133
|
+
const opener: HTMLButtonElement = document.createElement("button");
|
|
134
|
+
document.body.appendChild(opener);
|
|
135
|
+
opener.focus();
|
|
136
|
+
|
|
137
|
+
const { getByLabelText, unmount } = render(
|
|
138
|
+
<Modal title="Auto Focus Modal">
|
|
139
|
+
<input aria-label="Auto-focused input" autoFocus />
|
|
140
|
+
</Modal>,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(getByLabelText("Auto-focused input")).toHaveFocus();
|
|
144
|
+
|
|
145
|
+
unmount();
|
|
146
|
+
expect(opener).toHaveFocus();
|
|
147
|
+
opener.remove();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("renders the footer only when it has content", () => {
|
|
151
|
+
const { queryByTestId, rerender } = render(
|
|
152
|
+
<Modal title="No Actions Modal">
|
|
153
|
+
<div>Modal content</div>
|
|
154
|
+
</Modal>,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(queryByTestId("modal-footer")).not.toBeInTheDocument();
|
|
158
|
+
|
|
159
|
+
rerender(
|
|
160
|
+
<Modal
|
|
161
|
+
title="Utility Action Modal"
|
|
162
|
+
leftFooterElement={<button type="button">Utility action</button>}
|
|
163
|
+
>
|
|
164
|
+
<div>Modal content</div>
|
|
165
|
+
</Modal>,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(queryByTestId("modal-footer")).toBeInTheDocument();
|
|
169
|
+
});
|
|
170
|
+
|
|
29
171
|
it("closes the modal when the close button is clicked", () => {
|
|
30
172
|
const onCloseMock: MockFunction = getJestMockFunction();
|
|
31
173
|
const onSubmit: MockFunction = getJestMockFunction();
|
|
@@ -72,7 +214,7 @@ describe("Modal", () => {
|
|
|
72
214
|
</Modal>,
|
|
73
215
|
);
|
|
74
216
|
|
|
75
|
-
expect(getByTestId("modal")).toHaveClass("md:max-w-lg");
|
|
217
|
+
expect(getByTestId("modal")).toHaveClass("sm:max-w-lg", "md:max-w-lg");
|
|
76
218
|
});
|
|
77
219
|
|
|
78
220
|
it("displays the modal with the correct width when modalWidth is set", () => {
|
|
@@ -88,7 +230,7 @@ describe("Modal", () => {
|
|
|
88
230
|
</Modal>,
|
|
89
231
|
);
|
|
90
232
|
|
|
91
|
-
expect(getByTestId("modal")).toHaveClass("md:max-w-3xl");
|
|
233
|
+
expect(getByTestId("modal")).toHaveClass("sm:max-w-3xl", "md:max-w-3xl");
|
|
92
234
|
});
|
|
93
235
|
|
|
94
236
|
it("displays the children passed to the modal", () => {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import TelemetrySearchBar from "../../../UI/Components/TelemetryViewer/components/TelemetrySearchBar";
|
|
2
|
+
import "@testing-library/jest-dom/extend-expect";
|
|
3
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
6
|
+
|
|
7
|
+
describe("TelemetrySearchBar", () => {
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
cleanup();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("shows the results loader inside the search box while loading", () => {
|
|
13
|
+
const { rerender } = render(
|
|
14
|
+
<TelemetrySearchBar
|
|
15
|
+
value="service:api"
|
|
16
|
+
onChange={jest.fn()}
|
|
17
|
+
onSubmit={jest.fn()}
|
|
18
|
+
isLoading={false}
|
|
19
|
+
/>,
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
expect(
|
|
23
|
+
screen.queryByRole("status", { name: "Loading results" }),
|
|
24
|
+
).toBeNull();
|
|
25
|
+
|
|
26
|
+
rerender(
|
|
27
|
+
<TelemetrySearchBar
|
|
28
|
+
value="service:api"
|
|
29
|
+
onChange={jest.fn()}
|
|
30
|
+
onSubmit={jest.fn()}
|
|
31
|
+
isLoading={true}
|
|
32
|
+
/>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const input: HTMLElement = screen.getByRole("textbox");
|
|
36
|
+
const loader: HTMLElement = screen.getByRole("status", {
|
|
37
|
+
name: "Loading results",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(input.parentElement?.contains(loader)).toBe(true);
|
|
41
|
+
expect(input.parentElement?.getAttribute("aria-busy")).toBe("true");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("Enter submits and keeps the token when onFieldValueSelect returns false", () => {
|
|
45
|
+
const onChange: (value: string) => void = jest.fn();
|
|
46
|
+
const onSubmit: () => void = jest.fn();
|
|
47
|
+
const onFieldValueSelect: (fieldKey: string, value: string) => boolean =
|
|
48
|
+
jest.fn((_fieldKey: string, _value: string): boolean => {
|
|
49
|
+
return false;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
render(
|
|
53
|
+
<TelemetrySearchBar
|
|
54
|
+
value="name:process"
|
|
55
|
+
onChange={onChange}
|
|
56
|
+
onSubmit={onSubmit}
|
|
57
|
+
onFieldValueSelect={onFieldValueSelect}
|
|
58
|
+
/>,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Enter" });
|
|
62
|
+
|
|
63
|
+
expect(onFieldValueSelect).toHaveBeenCalledWith("name", "process");
|
|
64
|
+
expect(onSubmit).toHaveBeenCalledTimes(1);
|
|
65
|
+
// The token must stay in the input — clearing it would drop the filter.
|
|
66
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("Enter clears the token when onFieldValueSelect consumes it as a chip", () => {
|
|
70
|
+
const onChange: (value: string) => void = jest.fn();
|
|
71
|
+
const onSubmit: () => void = jest.fn();
|
|
72
|
+
const onFieldValueSelect: (fieldKey: string, value: string) => void =
|
|
73
|
+
jest.fn();
|
|
74
|
+
|
|
75
|
+
render(
|
|
76
|
+
<TelemetrySearchBar
|
|
77
|
+
value="@http.method:GET"
|
|
78
|
+
onChange={onChange}
|
|
79
|
+
onSubmit={onSubmit}
|
|
80
|
+
onFieldValueSelect={onFieldValueSelect}
|
|
81
|
+
/>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
fireEvent.keyDown(screen.getByRole("textbox"), { key: "Enter" });
|
|
85
|
+
|
|
86
|
+
expect(onFieldValueSelect).toHaveBeenCalledWith("http.method", "GET");
|
|
87
|
+
expect(onChange).toHaveBeenCalledWith("");
|
|
88
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import ThemeUtil, { THEME_STORAGE_KEY, Theme } from "../../../UI/Utils/Theme";
|
|
3
|
+
|
|
4
|
+
const LIGHT_THEME_COLOR: string = "#f9fafb";
|
|
5
|
+
const DARK_THEME_COLOR: string = "#0f172a";
|
|
6
|
+
|
|
7
|
+
const getThemeColorMeta: () => HTMLMetaElement = (): HTMLMetaElement => {
|
|
8
|
+
const themeColor: HTMLMetaElement | null = document.querySelector(
|
|
9
|
+
'meta[name="theme-color"]:not([media])',
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
expect(themeColor).not.toBeNull();
|
|
13
|
+
return themeColor as HTMLMetaElement;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const dispatchStorageEvent: (
|
|
17
|
+
key: string | null,
|
|
18
|
+
newValue: string | null,
|
|
19
|
+
) => void = (key: string | null, newValue: string | null): void => {
|
|
20
|
+
window.dispatchEvent(
|
|
21
|
+
new StorageEvent("storage", {
|
|
22
|
+
key,
|
|
23
|
+
newValue,
|
|
24
|
+
storageArea: window.localStorage,
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const makeLocalStorageUnavailable: () => () => void = (): (() => void) => {
|
|
30
|
+
const descriptor: PropertyDescriptor | undefined =
|
|
31
|
+
Object.getOwnPropertyDescriptor(window, "localStorage");
|
|
32
|
+
|
|
33
|
+
if (!descriptor) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"Expected window.localStorage to have a property descriptor",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Object.defineProperty(window, "localStorage", {
|
|
40
|
+
configurable: true,
|
|
41
|
+
get: (): never => {
|
|
42
|
+
throw new Error("Storage is unavailable");
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return (): void => {
|
|
47
|
+
Object.defineProperty(window, "localStorage", descriptor);
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe("ThemeUtil", () => {
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
window.localStorage.clear();
|
|
54
|
+
|
|
55
|
+
const root: HTMLElement = document.documentElement;
|
|
56
|
+
root.removeAttribute("class");
|
|
57
|
+
root.removeAttribute("data-theme");
|
|
58
|
+
root.style.removeProperty("color-scheme");
|
|
59
|
+
|
|
60
|
+
document.head.innerHTML = [
|
|
61
|
+
'<meta name="theme-color" content="initial">',
|
|
62
|
+
'<meta name="theme-color" media="(prefers-color-scheme: dark)" content="media-theme">',
|
|
63
|
+
].join("");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("defaults to light when no preference has been saved", () => {
|
|
67
|
+
document.documentElement.classList.add(Theme.Dark);
|
|
68
|
+
|
|
69
|
+
expect(ThemeUtil.getStoredTheme()).toBe(Theme.Light);
|
|
70
|
+
|
|
71
|
+
ThemeUtil.initialize();
|
|
72
|
+
|
|
73
|
+
expect(ThemeUtil.getAppliedTheme()).toBe(Theme.Light);
|
|
74
|
+
expect(document.documentElement.classList.contains(Theme.Dark)).toBe(false);
|
|
75
|
+
expect(document.documentElement.dataset["theme"]).toBe(Theme.Light);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("restores an exactly persisted dark preference", () => {
|
|
79
|
+
window.localStorage.setItem(THEME_STORAGE_KEY, Theme.Dark);
|
|
80
|
+
|
|
81
|
+
expect(ThemeUtil.getStoredTheme()).toBe(Theme.Dark);
|
|
82
|
+
|
|
83
|
+
ThemeUtil.initialize();
|
|
84
|
+
|
|
85
|
+
expect(ThemeUtil.getAppliedTheme()).toBe(Theme.Dark);
|
|
86
|
+
expect(document.documentElement.classList.contains(Theme.Dark)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test.each([Theme.Light, "Dark", "DARK", " dark ", "invalid", ""])(
|
|
90
|
+
"treats the persisted value %p as light",
|
|
91
|
+
(persistedValue: string) => {
|
|
92
|
+
window.localStorage.setItem(THEME_STORAGE_KEY, persistedValue);
|
|
93
|
+
|
|
94
|
+
expect(ThemeUtil.getStoredTheme()).toBe(Theme.Light);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
test("updates the root class, data attribute, color scheme, and theme-color meta", () => {
|
|
99
|
+
const mediaThemeColor: HTMLMetaElement = document.querySelector(
|
|
100
|
+
'meta[name="theme-color"][media]',
|
|
101
|
+
) as HTMLMetaElement;
|
|
102
|
+
|
|
103
|
+
ThemeUtil.setTheme(Theme.Dark);
|
|
104
|
+
|
|
105
|
+
expect(window.localStorage.getItem(THEME_STORAGE_KEY)).toBe(Theme.Dark);
|
|
106
|
+
expect(document.documentElement.classList.contains(Theme.Dark)).toBe(true);
|
|
107
|
+
expect(document.documentElement.dataset["theme"]).toBe(Theme.Dark);
|
|
108
|
+
expect(document.documentElement.style.colorScheme).toBe(Theme.Dark);
|
|
109
|
+
expect(getThemeColorMeta().getAttribute("content")).toBe(DARK_THEME_COLOR);
|
|
110
|
+
expect(mediaThemeColor.getAttribute("content")).toBe("media-theme");
|
|
111
|
+
|
|
112
|
+
ThemeUtil.setTheme(Theme.Light);
|
|
113
|
+
|
|
114
|
+
expect(window.localStorage.getItem(THEME_STORAGE_KEY)).toBe(Theme.Light);
|
|
115
|
+
expect(document.documentElement.classList.contains(Theme.Dark)).toBe(false);
|
|
116
|
+
expect(document.documentElement.dataset["theme"]).toBe(Theme.Light);
|
|
117
|
+
expect(document.documentElement.style.colorScheme).toBe(Theme.Light);
|
|
118
|
+
expect(getThemeColorMeta().getAttribute("content")).toBe(LIGHT_THEME_COLOR);
|
|
119
|
+
expect(mediaThemeColor.getAttribute("content")).toBe("media-theme");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("falls back to light when reading storage fails", () => {
|
|
123
|
+
const restoreLocalStorage: () => void = makeLocalStorageUnavailable();
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
expect(ThemeUtil.getStoredTheme()).toBe(Theme.Light);
|
|
127
|
+
} finally {
|
|
128
|
+
restoreLocalStorage();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("still applies the visual theme when writing storage fails", () => {
|
|
133
|
+
const restoreLocalStorage: () => void = makeLocalStorageUnavailable();
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
expect(() => {
|
|
137
|
+
ThemeUtil.setTheme(Theme.Dark);
|
|
138
|
+
}).not.toThrow();
|
|
139
|
+
expect(ThemeUtil.getAppliedTheme()).toBe(Theme.Dark);
|
|
140
|
+
expect(document.documentElement.dataset["theme"]).toBe(Theme.Dark);
|
|
141
|
+
} finally {
|
|
142
|
+
restoreLocalStorage();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("synchronizes dark and cleared preferences from other tabs", () => {
|
|
147
|
+
ThemeUtil.initialize();
|
|
148
|
+
|
|
149
|
+
dispatchStorageEvent(THEME_STORAGE_KEY, Theme.Dark);
|
|
150
|
+
expect(ThemeUtil.getAppliedTheme()).toBe(Theme.Dark);
|
|
151
|
+
|
|
152
|
+
dispatchStorageEvent(THEME_STORAGE_KEY, null);
|
|
153
|
+
expect(ThemeUtil.getAppliedTheme()).toBe(Theme.Light);
|
|
154
|
+
|
|
155
|
+
dispatchStorageEvent(THEME_STORAGE_KEY, Theme.Dark);
|
|
156
|
+
expect(ThemeUtil.getAppliedTheme()).toBe(Theme.Dark);
|
|
157
|
+
|
|
158
|
+
dispatchStorageEvent(null, null);
|
|
159
|
+
expect(ThemeUtil.getAppliedTheme()).toBe(Theme.Light);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -16,4 +16,16 @@ export default interface AggregateBy<TBaseModel extends GenericObject> {
|
|
|
16
16
|
skip: number;
|
|
17
17
|
sort?: Sort<TBaseModel> | undefined;
|
|
18
18
|
groupBy?: GroupBy<TBaseModel> | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* OpenTelemetry attribute keys (e.g. "service.name") to group the
|
|
21
|
+
* aggregation by. Grouping happens on the individual map entries —
|
|
22
|
+
* `attributes['service.name']` — so all rows sharing the selected
|
|
23
|
+
* key values are pooled into one series per bucket. This is distinct
|
|
24
|
+
* from `groupBy: { attributes: true }`, which groups by the ENTIRE
|
|
25
|
+
* attribute map and therefore fragments the result into one series
|
|
26
|
+
* per unique attribute combination. Only supported by models with an
|
|
27
|
+
* `attributes` map column (currently Metric); result rows carry an
|
|
28
|
+
* `attributes` object containing exactly these keys.
|
|
29
|
+
*/
|
|
30
|
+
groupByAttributeKeys?: Array<string> | undefined;
|
|
19
31
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import ObjectID from "../../ObjectID";
|
|
2
|
+
import DashboardComponentType from "../DashboardComponentType";
|
|
3
|
+
import BaseComponent from "./DashboardBaseComponent";
|
|
4
|
+
|
|
5
|
+
export default interface DashboardLogChartComponent extends BaseComponent {
|
|
6
|
+
componentType: DashboardComponentType.LogChart;
|
|
7
|
+
componentId: ObjectID;
|
|
8
|
+
arguments: {
|
|
9
|
+
title?: string | undefined;
|
|
10
|
+
serviceIds?: Array<string> | undefined;
|
|
11
|
+
severityFilters?: Array<string> | undefined;
|
|
12
|
+
bodyContains?: string | undefined;
|
|
13
|
+
/*
|
|
14
|
+
* Exact attribute matches in the log-search syntax used by Log Stream,
|
|
15
|
+
* for example: @service.name:checkout @deployment.environment:production.
|
|
16
|
+
*/
|
|
17
|
+
attributeFilterQuery?: string | undefined;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -192,7 +192,15 @@ function buildMetricQueryConfig(config: MetricConfig): Record<string, unknown> {
|
|
|
192
192
|
metricName: config.metricName,
|
|
193
193
|
aggegationType: config.aggregationType,
|
|
194
194
|
},
|
|
195
|
-
|
|
195
|
+
/*
|
|
196
|
+
* groupByAttributeKeys alone drives per-series grouping — the
|
|
197
|
+
* fetch layer compiles it to `GROUP BY attributes['<key>']`.
|
|
198
|
+
* Baking a whole-map `groupBy: { attributes: true }` alongside
|
|
199
|
+
* it (the old approach) fragmented results into one series per
|
|
200
|
+
* unique attribute combination; older stored configs that still
|
|
201
|
+
* carry it are stripped at fetch time (MetricUtil.fetchResults).
|
|
202
|
+
*/
|
|
203
|
+
groupBy: undefined,
|
|
196
204
|
groupByAttributeKeys: config.groupByAttributeKeys,
|
|
197
205
|
},
|
|
198
206
|
transformAsRate: config.transformAsRate,
|
|
@@ -3,6 +3,8 @@ import MetricCriteriaContext from "./MetricMonitor/MetricCriteriaContext";
|
|
|
3
3
|
|
|
4
4
|
export enum CheckOn {
|
|
5
5
|
ResponseTime = "Response Time (in ms)",
|
|
6
|
+
PacketLossPercent = "Packet Loss (in %)",
|
|
7
|
+
Jitter = "Jitter (in ms)",
|
|
6
8
|
ResponseStatusCode = "Response Status Code",
|
|
7
9
|
ResponseHeader = "Response Header",
|
|
8
10
|
ResponseHeaderValue = "Response Header Value",
|
|
@@ -374,6 +376,8 @@ export class CriteriaFilterUtil {
|
|
|
374
376
|
return (
|
|
375
377
|
checkOn === CheckOn.ResponseStatusCode ||
|
|
376
378
|
checkOn === CheckOn.ResponseTime ||
|
|
379
|
+
checkOn === CheckOn.PacketLossPercent ||
|
|
380
|
+
checkOn === CheckOn.Jitter ||
|
|
377
381
|
checkOn === CheckOn.DiskUsagePercent ||
|
|
378
382
|
checkOn === CheckOn.CPUUsagePercent ||
|
|
379
383
|
checkOn === CheckOn.MemoryUsagePercent ||
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Per-phase breakdown of an HTTP(S) check, all in milliseconds. Captured by
|
|
3
|
+
* the probe from socket events on the initial connection of the request.
|
|
4
|
+
* Every field is optional: phases are absent when they don't apply (no TLS
|
|
5
|
+
* on plain HTTP, no DNS lookup when the target is an IP address) or when the
|
|
6
|
+
* request went through a proxy, where per-phase timing would be misleading.
|
|
7
|
+
* When the check follows redirects, the phases describe the first connection
|
|
8
|
+
* and the download phase absorbs the rest of the exchange.
|
|
9
|
+
*/
|
|
10
|
+
export default interface HttpPhaseTimings {
|
|
11
|
+
dnsLookupInMs?: number | undefined;
|
|
12
|
+
tcpConnectInMs?: number | undefined;
|
|
13
|
+
tlsHandshakeInMs?: number | undefined;
|
|
14
|
+
timeToFirstByteInMs?: number | undefined;
|
|
15
|
+
downloadInMs?: number | undefined;
|
|
16
|
+
}
|
|
@@ -7,6 +7,23 @@ enum MonitorMetricType {
|
|
|
7
7
|
IsOnline = "oneuptime.monitor.online",
|
|
8
8
|
ExecutionTime = "oneuptime.monitor.execution.time",
|
|
9
9
|
|
|
10
|
+
/*
|
|
11
|
+
* Packet-level network metrics. Emitted by Ping/IP monitors when the
|
|
12
|
+
* probe sends multiple echo requests per check; absent for older probes.
|
|
13
|
+
*/
|
|
14
|
+
PacketLossPercent = "oneuptime.monitor.ping.packet.loss.percent",
|
|
15
|
+
Jitter = "oneuptime.monitor.ping.jitter",
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* HTTP(S) phase breakdown. Emitted by Website/API monitors when the probe
|
|
19
|
+
* captured socket-level timings; absent behind proxies and on older probes.
|
|
20
|
+
*/
|
|
21
|
+
DnsLookupTime = "oneuptime.monitor.http.dns.lookup.time",
|
|
22
|
+
TcpConnectTime = "oneuptime.monitor.http.tcp.connect.time",
|
|
23
|
+
TlsHandshakeTime = "oneuptime.monitor.http.tls.handshake.time",
|
|
24
|
+
TimeToFirstByte = "oneuptime.monitor.http.time.to.first.byte",
|
|
25
|
+
DownloadTime = "oneuptime.monitor.http.download.time",
|
|
26
|
+
|
|
10
27
|
/*
|
|
11
28
|
* Extended server/VM metrics. Emitted when the agent payload contains them;
|
|
12
29
|
* absent for older agents, which keeps the pipeline backwards-compatible.
|