@oneuptime/common 11.3.2 → 11.3.4
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/Infrastructure/ClickhouseConfig.ts +21 -0
- package/Server/Infrastructure/ClickhouseDatabase.ts +12 -0
- package/Server/Services/AnalyticsDatabaseService.ts +79 -4
- package/Server/Services/TraceAggregationService.ts +16 -3
- package/Tests/Server/Services/TraceAggregationService.test.ts +10 -0
- package/Tests/UI/Components/Pill.test.tsx +3 -1
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardTraceTableComponent.ts +39 -0
- package/UI/Components/Date/RangeStartAndEndDateEdit.tsx +228 -44
- package/UI/Components/Date/RangeStartAndEndDateView.tsx +73 -21
- package/UI/Components/EditionLabel/EditionLabel.tsx +6 -1
- package/UI/Components/Footer/Footer.tsx +1 -1
- package/UI/Components/GanttChart/ChartContainer.tsx +8 -1
- package/UI/Components/Icon/Icon.tsx +13 -1
- package/UI/Components/Image/Image.tsx +6 -1
- package/UI/Components/Link/Link.tsx +22 -0
- package/UI/Components/MasterPage/MasterPage.tsx +30 -1
- package/UI/Components/Page/Page.tsx +29 -2
- package/UI/Components/Pagination/Pagination.tsx +110 -180
- package/Utils/Dashboard/Components/DashboardTraceTableComponent.ts +65 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +17 -0
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js +10 -1
- package/build/dist/Server/Infrastructure/ClickhouseDatabase.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +59 -8
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +12 -3
- package/build/dist/Server/Services/TraceAggregationService.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/DashboardTraceTableComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardTraceTableComponent.js.map +1 -0
- package/build/dist/UI/Components/Date/RangeStartAndEndDateEdit.js +116 -26
- package/build/dist/UI/Components/Date/RangeStartAndEndDateEdit.js.map +1 -1
- package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js +42 -16
- package/build/dist/UI/Components/Date/RangeStartAndEndDateView.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Footer/Footer.js +1 -1
- package/build/dist/UI/Components/GanttChart/ChartContainer.js +9 -1
- package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +2 -2
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Image/Image.js +8 -1
- package/build/dist/UI/Components/Image/Image.js.map +1 -1
- package/build/dist/UI/Components/Link/Link.js +9 -0
- package/build/dist/UI/Components/Link/Link.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +2 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/UI/Components/Page/Page.js +24 -1
- package/build/dist/UI/Components/Page/Page.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +44 -94
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardTraceTableComponent.js +51 -0
- package/build/dist/Utils/Dashboard/Components/DashboardTraceTableComponent.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/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { GetReactElementFunction } from "../../../UI/Types/FunctionTypes";
|
|
|
7
7
|
import Icon from "../Icon/Icon";
|
|
8
8
|
import Tooltip from "../Tooltip/Tooltip";
|
|
9
9
|
import RangeStartAndEndDateEdit from "./RangeStartAndEndDateEdit";
|
|
10
|
-
import Modal from "../Modal/Modal";
|
|
10
|
+
import Modal, { ModalWidth } from "../Modal/Modal";
|
|
11
11
|
|
|
12
12
|
export interface ComponentProps {
|
|
13
13
|
dashboardStartAndEndDate: RangeStartAndEndDateTime;
|
|
@@ -25,17 +25,65 @@ const DashboardStartAndEndDateView: FunctionComponent<ComponentProps> = (
|
|
|
25
25
|
const isCustomRange: boolean =
|
|
26
26
|
props.dashboardStartAndEndDate.range === TimeRange.CUSTOM;
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
/*
|
|
29
|
+
* The Apply button should only commit a custom range when both ends are set
|
|
30
|
+
* and the start is strictly before the end.
|
|
31
|
+
*/
|
|
32
|
+
const isSelectionValid: (
|
|
33
|
+
selection: RangeStartAndEndDateTime | null,
|
|
34
|
+
) => boolean = (selection: RangeStartAndEndDateTime | null): boolean => {
|
|
35
|
+
if (!selection) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (selection.range !== TimeRange.CUSTOM) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const startAndEndDate: typeof selection.startAndEndDate =
|
|
44
|
+
selection.startAndEndDate;
|
|
45
|
+
|
|
46
|
+
if (!startAndEndDate) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return OneUptimeDate.isAfter(
|
|
51
|
+
startAndEndDate.endValue,
|
|
52
|
+
startAndEndDate.startValue,
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const closeModal: () => void = (): void => {
|
|
57
|
+
setTempStartAndEndDate(null);
|
|
58
|
+
setShowTimeSelectModal(false);
|
|
59
|
+
};
|
|
38
60
|
|
|
61
|
+
const commitSelection: (selection: RangeStartAndEndDateTime) => void = (
|
|
62
|
+
selection: RangeStartAndEndDateTime,
|
|
63
|
+
): void => {
|
|
64
|
+
props.onChange(selection);
|
|
65
|
+
closeModal();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const getButtonTitle: () => string = (): string => {
|
|
69
|
+
if (isCustomRange) {
|
|
70
|
+
return `${OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
71
|
+
props.dashboardStartAndEndDate.startAndEndDate?.startValue ||
|
|
72
|
+
OneUptimeDate.getCurrentDate(),
|
|
73
|
+
false,
|
|
74
|
+
true,
|
|
75
|
+
)} - ${OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
76
|
+
props.dashboardStartAndEndDate.startAndEndDate?.endValue ||
|
|
77
|
+
OneUptimeDate.getCurrentDate(),
|
|
78
|
+
false,
|
|
79
|
+
true,
|
|
80
|
+
)}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return props.dashboardStartAndEndDate.range;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const getContent: GetReactElementFunction = (): ReactElement => {
|
|
39
87
|
return (
|
|
40
88
|
<div>
|
|
41
89
|
<Tooltip text="Click to change the date and time range of data on this dashboard.">
|
|
@@ -48,30 +96,34 @@ const DashboardStartAndEndDateView: FunctionComponent<ComponentProps> = (
|
|
|
48
96
|
}}
|
|
49
97
|
>
|
|
50
98
|
<Icon icon={IconProp.Clock} className="w-3.5 h-3.5 text-gray-500" />
|
|
51
|
-
<span className="text-xs text-gray-500">{
|
|
99
|
+
<span className="text-xs text-gray-500">{getButtonTitle()}</span>
|
|
52
100
|
</button>
|
|
53
101
|
</Tooltip>
|
|
54
102
|
{showTimeSelectModal && (
|
|
55
103
|
<Modal
|
|
56
|
-
title="Select
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
104
|
+
title="Select Time Range"
|
|
105
|
+
description="Choose a quick range or set an exact start and end date & time."
|
|
106
|
+
icon={IconProp.Clock}
|
|
107
|
+
modalWidth={ModalWidth.Medium}
|
|
108
|
+
submitButtonText="Apply"
|
|
109
|
+
disableSubmitButton={!isSelectionValid(tempStartAndEndDate)}
|
|
110
|
+
onClose={closeModal}
|
|
61
111
|
onSubmit={() => {
|
|
62
112
|
if (tempStartAndEndDate) {
|
|
63
|
-
|
|
113
|
+
commitSelection(tempStartAndEndDate);
|
|
64
114
|
}
|
|
65
|
-
setShowTimeSelectModal(false);
|
|
66
|
-
setTempStartAndEndDate(null);
|
|
67
115
|
}}
|
|
68
116
|
>
|
|
69
|
-
<div className="mt-
|
|
117
|
+
<div className="mt-3">
|
|
70
118
|
<RangeStartAndEndDateEdit
|
|
71
119
|
value={tempStartAndEndDate || undefined}
|
|
72
120
|
onChange={(startAndEndDate: RangeStartAndEndDateTime) => {
|
|
73
121
|
setTempStartAndEndDate(startAndEndDate);
|
|
74
122
|
}}
|
|
123
|
+
onApply={(startAndEndDate: RangeStartAndEndDateTime) => {
|
|
124
|
+
// Quick ranges apply immediately and close the modal.
|
|
125
|
+
commitSelection(startAndEndDate);
|
|
126
|
+
}}
|
|
75
127
|
/>
|
|
76
128
|
</div>
|
|
77
129
|
</Modal>
|
|
@@ -462,7 +462,12 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
|
|
|
462
462
|
type="button"
|
|
463
463
|
onClick={openDialog}
|
|
464
464
|
className={`${pillClassName} ${props.className ? props.className : ""}`}
|
|
465
|
-
|
|
465
|
+
/*
|
|
466
|
+
* Accessible name must include the visible text (WCAG 2.5.3 Label in
|
|
467
|
+
* Name) so voice-control users can activate it by the words they see
|
|
468
|
+
* ("{editionName}" and "{ctaLabel}", e.g. "Learn more").
|
|
469
|
+
*/
|
|
470
|
+
aria-label={`${editionName}, ${ctaLabel}`}
|
|
466
471
|
>
|
|
467
472
|
{showAlertedPill && (
|
|
468
473
|
<Icon
|
|
@@ -66,7 +66,7 @@ const Footer: FunctionComponent<ComponentProps> = (
|
|
|
66
66
|
{/* Copyright: Show on mobile, hide on larger screens unless specified */}
|
|
67
67
|
<div className="mt-5 md:order-1 md:mt-0 block md:hidden lg:block">
|
|
68
68
|
{props.copyright && (
|
|
69
|
-
<p className="text-center text-sm text-gray-
|
|
69
|
+
<p className="text-center text-sm text-gray-500">
|
|
70
70
|
© {props.copyright}
|
|
71
71
|
</p>
|
|
72
72
|
)}
|
|
@@ -33,7 +33,14 @@ const ChartContainer: FunctionComponent<ComponentProps> = (
|
|
|
33
33
|
}, []);
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
|
-
|
|
36
|
+
/*
|
|
37
|
+
* `isolate` creates a new stacking context so the chart's internal
|
|
38
|
+
* z-indexes (e.g. a highlighted/matched bar uses z-30, see Bar/Index.tsx)
|
|
39
|
+
* stay trapped inside the chart. Without it those bars paint above
|
|
40
|
+
* page-level overlays like the span-details SideOver (z-10), making a
|
|
41
|
+
* matched span "show above" the sidebar.
|
|
42
|
+
*/
|
|
43
|
+
<div ref={divRef} className={"w-full"} style={{ isolation: "isolate" }}>
|
|
37
44
|
{props.children}
|
|
38
45
|
</div>
|
|
39
46
|
);
|
|
@@ -35,6 +35,14 @@ export interface ComponentProps {
|
|
|
35
35
|
onClick?: (() => void) | undefined;
|
|
36
36
|
type?: IconType | undefined;
|
|
37
37
|
style?: React.CSSProperties | undefined;
|
|
38
|
+
/*
|
|
39
|
+
* When the icon conveys meaning on its own (e.g. a standalone status or
|
|
40
|
+
* severity indicator that is not described by adjacent text), pass an
|
|
41
|
+
* ariaLabel so the icon is exposed to assistive technology as an image
|
|
42
|
+
* with an accessible name (WCAG 1.1.1). When omitted, the icon is treated
|
|
43
|
+
* as decorative and hidden from assistive technology.
|
|
44
|
+
*/
|
|
45
|
+
ariaLabel?: string | undefined;
|
|
38
46
|
"data-testid"?: string;
|
|
39
47
|
}
|
|
40
48
|
const Icon: FunctionComponent<ComponentProps> = ({
|
|
@@ -46,6 +54,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
|
|
|
46
54
|
onClick,
|
|
47
55
|
type,
|
|
48
56
|
style,
|
|
57
|
+
ariaLabel,
|
|
49
58
|
"data-testid": dataTestId,
|
|
50
59
|
}: ComponentProps): ReactElement => {
|
|
51
60
|
let sizeClassName: string = "";
|
|
@@ -113,7 +122,10 @@ const Icon: FunctionComponent<ComponentProps> = ({
|
|
|
113
122
|
| undefined,
|
|
114
123
|
): ReactElement => {
|
|
115
124
|
return (
|
|
116
|
-
<div
|
|
125
|
+
<div
|
|
126
|
+
role={ariaLabel ? "img" : undefined}
|
|
127
|
+
aria-label={ariaLabel || undefined}
|
|
128
|
+
>
|
|
117
129
|
<svg
|
|
118
130
|
onClick={() => {
|
|
119
131
|
if (onClick) {
|
|
@@ -41,7 +41,12 @@ const Image: FunctionComponent<ComponentProps> = (
|
|
|
41
41
|
props.onClick?.();
|
|
42
42
|
}}
|
|
43
43
|
data-testid={props["data-testid"]}
|
|
44
|
-
|
|
44
|
+
/*
|
|
45
|
+
* Always render an alt attribute so the <img> is never missing it
|
|
46
|
+
* (WCAG 1.1.1). Callers should pass a descriptive alt for meaningful
|
|
47
|
+
* images (e.g. avatars); an empty string marks the image decorative.
|
|
48
|
+
*/
|
|
49
|
+
alt={props.alt ?? ""}
|
|
45
50
|
src={url}
|
|
46
51
|
height={props.height}
|
|
47
52
|
className={props.className}
|
|
@@ -33,6 +33,28 @@ const Link: FunctionComponent<ComponentProps> = (
|
|
|
33
33
|
children = props.children;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/*
|
|
37
|
+
* Neither a destination nor an action: this is static text, not a link or a
|
|
38
|
+
* button. Render a <span> rather than an <a> with no href, which is
|
|
39
|
+
* ambiguous to assistive technology (is it a link, a button, or just text?
|
|
40
|
+
* WCAG 4.1.2 Name, Role, Value).
|
|
41
|
+
*/
|
|
42
|
+
if (!props.to && !props.onClick) {
|
|
43
|
+
return (
|
|
44
|
+
<span
|
|
45
|
+
id={props.id}
|
|
46
|
+
className={`cursor-default ${props.className || ""}`}
|
|
47
|
+
onMouseOver={props.onMouseOver}
|
|
48
|
+
onMouseOut={props.onMouseOut}
|
|
49
|
+
onMouseLeave={props.onMouseLeave}
|
|
50
|
+
style={props.style}
|
|
51
|
+
title={props.title}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</span>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
const linkProps: JSONObject = {};
|
|
37
59
|
|
|
38
60
|
/*
|
|
@@ -15,6 +15,12 @@ export interface ComponentProps {
|
|
|
15
15
|
className?: string | undefined;
|
|
16
16
|
hideHeader?: boolean | undefined;
|
|
17
17
|
makeTopSectionUnstick?: boolean | undefined;
|
|
18
|
+
/*
|
|
19
|
+
* Set when the consumer renders its own <main id="main-content"> landmark
|
|
20
|
+
* and skip link inside children (e.g. the status page), so this component
|
|
21
|
+
* does not add a second, nested main landmark or a duplicate skip link.
|
|
22
|
+
*/
|
|
23
|
+
disableMainContentWrapper?: boolean | undefined;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
const MasterPage: FunctionComponent<ComponentProps> = (
|
|
@@ -42,6 +48,19 @@ const MasterPage: FunctionComponent<ComponentProps> = (
|
|
|
42
48
|
<React.Fragment>
|
|
43
49
|
{isOnline && (
|
|
44
50
|
<div className={props.className}>
|
|
51
|
+
{/*
|
|
52
|
+
* Skip link so keyboard and screen-reader users can bypass the
|
|
53
|
+
* repeated header/navigation and jump straight to the page content
|
|
54
|
+
* (WCAG 2.4.1 Bypass Blocks). Visually hidden until focused.
|
|
55
|
+
*/}
|
|
56
|
+
{!props.disableMainContentWrapper && (
|
|
57
|
+
<a
|
|
58
|
+
href="#main-content"
|
|
59
|
+
className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4 focus:z-50 focus:rounded-md focus:bg-indigo-600 focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-white focus:shadow-lg"
|
|
60
|
+
>
|
|
61
|
+
Skip to main content
|
|
62
|
+
</a>
|
|
63
|
+
)}
|
|
45
64
|
<div
|
|
46
65
|
className={props.makeTopSectionUnstick ? "" : "sticky top-0 z-10"}
|
|
47
66
|
>
|
|
@@ -53,7 +72,17 @@ const MasterPage: FunctionComponent<ComponentProps> = (
|
|
|
53
72
|
/>
|
|
54
73
|
</div>
|
|
55
74
|
|
|
56
|
-
{props.
|
|
75
|
+
{props.disableMainContentWrapper ? (
|
|
76
|
+
props.children
|
|
77
|
+
) : (
|
|
78
|
+
<main
|
|
79
|
+
id="main-content"
|
|
80
|
+
tabIndex={-1}
|
|
81
|
+
className="focus:outline-none"
|
|
82
|
+
>
|
|
83
|
+
{props.children}
|
|
84
|
+
</main>
|
|
85
|
+
)}
|
|
57
86
|
|
|
58
87
|
{props.footer && props.footer}
|
|
59
88
|
</div>
|
|
@@ -40,6 +40,33 @@ const Page: FunctionComponent<ComponentProps> = (
|
|
|
40
40
|
}
|
|
41
41
|
}, [props.breadcrumbLinks]);
|
|
42
42
|
|
|
43
|
+
/*
|
|
44
|
+
* Give each page a unique, descriptive document title (WCAG 2.4.2 Page
|
|
45
|
+
* Titled). Without this, every page keeps the generic title set once at app
|
|
46
|
+
* startup (e.g. "OneUptime | Dashboard"), which does not describe the page.
|
|
47
|
+
* Prefer the breadcrumb trail (most specific page last) and fall back to the
|
|
48
|
+
* page title.
|
|
49
|
+
*/
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const breadcrumbTitle: string | undefined =
|
|
52
|
+
props.breadcrumbLinks && props.breadcrumbLinks.length > 0
|
|
53
|
+
? props.breadcrumbLinks
|
|
54
|
+
.map((link: Link) => {
|
|
55
|
+
return translateString(link.title);
|
|
56
|
+
})
|
|
57
|
+
.filter((value: string | undefined): value is string => {
|
|
58
|
+
return Boolean(value);
|
|
59
|
+
})
|
|
60
|
+
.join(" - ")
|
|
61
|
+
: undefined;
|
|
62
|
+
|
|
63
|
+
const pageTitle: string | undefined = breadcrumbTitle || translatedTitle;
|
|
64
|
+
|
|
65
|
+
if (pageTitle) {
|
|
66
|
+
document.title = `OneUptime | ${pageTitle}`;
|
|
67
|
+
}
|
|
68
|
+
}, [translatedTitle, props.breadcrumbLinks]);
|
|
69
|
+
|
|
43
70
|
if (props.error) {
|
|
44
71
|
return <ErrorMessage message={props.error} />;
|
|
45
72
|
}
|
|
@@ -104,7 +131,7 @@ const Page: FunctionComponent<ComponentProps> = (
|
|
|
104
131
|
)}
|
|
105
132
|
|
|
106
133
|
{props.sideMenu && (
|
|
107
|
-
<
|
|
134
|
+
<div className="mx-auto max-w-full pb-10">
|
|
108
135
|
<div className="flex flex-col md:flex-row md:gap-4 lg:gap-5">
|
|
109
136
|
{props.sideMenu}
|
|
110
137
|
|
|
@@ -117,7 +144,7 @@ const Page: FunctionComponent<ComponentProps> = (
|
|
|
117
144
|
</div>
|
|
118
145
|
)}
|
|
119
146
|
</div>
|
|
120
|
-
</
|
|
147
|
+
</div>
|
|
121
148
|
)}
|
|
122
149
|
|
|
123
150
|
{!props.sideMenu && !props.isLoading && props.children}
|