@oneuptime/common 10.4.14 → 10.4.15
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/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +49 -0
- package/Models/AnalyticsModels/AuditLog.ts +8 -0
- package/Models/AnalyticsModels/ExceptionInstance.ts +1 -0
- package/Models/AnalyticsModels/Log.ts +1 -0
- package/Models/AnalyticsModels/Metric.ts +10 -0
- package/Models/AnalyticsModels/MonitorLog.ts +1 -0
- package/Models/AnalyticsModels/Profile.ts +1 -0
- package/Models/AnalyticsModels/ProfileSample.ts +1 -0
- package/Models/AnalyticsModels/Span.ts +1 -0
- package/Models/DatabaseModels/AlertCustomField.ts +37 -0
- package/Models/DatabaseModels/IncidentCustomField.ts +37 -0
- package/Models/DatabaseModels/IncidentMember.ts +9 -0
- package/Models/DatabaseModels/MonitorCustomField.ts +37 -0
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +37 -0
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +37 -0
- package/Models/DatabaseModels/StatusPageCustomField.ts +37 -0
- package/Models/DatabaseModels/TableView.ts +40 -0
- package/Models/DatabaseModels/TeamMemberCustomField.ts +37 -0
- package/Server/API/BaseAnalyticsAPI.ts +128 -20
- package/Server/API/MetricAPI.ts +5 -138
- package/Server/API/StatusAPI.ts +103 -7
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.ts +34 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/AccessTokenService.ts +1 -1
- package/Server/Services/AnalyticsDatabaseService.ts +24 -4
- package/Server/Services/MetricService.ts +113 -0
- package/Server/Services/ProjectService.ts +21 -1
- package/Server/Utils/Response.ts +4 -1
- package/Server/Utils/UserPermission/UserPermission.ts +17 -1
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +2 -2
- package/Types/API/HTTPResponse.ts +16 -0
- package/Types/BaseDatabase/ListResult.ts +6 -0
- package/Types/CustomField/CustomFieldType.ts +2 -0
- package/Types/Date.ts +9 -1
- package/Types/ListData.ts +14 -0
- package/Types/Monitor/DnsMonitor/DnsMonitorResponse.ts +3 -0
- package/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.ts +5 -0
- package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +4 -0
- package/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.ts +4 -0
- package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +3 -0
- package/Types/Probe/ProbeAttempt.ts +9 -0
- package/Types/Probe/ProbeMonitorResponse.ts +3 -0
- package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +504 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +64 -54
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +38 -0
- package/UI/Components/CustomFields/DropdownOptionsInput.tsx +150 -0
- package/UI/Components/Detail/Detail.tsx +78 -11
- package/UI/Components/List/List.tsx +6 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +74 -2
- package/UI/Components/ModelTable/TableView.tsx +70 -30
- package/UI/Components/Pagination/Pagination.tsx +75 -33
- package/UI/Components/Table/Table.tsx +6 -0
- package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +33 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/AuditLog.js +8 -0
- package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +1 -0
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +10 -0
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MonitorLog.js +1 -0
- package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Profile.js +1 -0
- package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ProfileSample.js +1 -0
- package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +1 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +11 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TableView.js +40 -0
- package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js +105 -18
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/MetricAPI.js +5 -113
- package/build/dist/Server/API/MetricAPI.js.map +1 -1
- package/build/dist/Server/API/StatusAPI.js +75 -8
- package/build/dist/Server/API/StatusAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.js +27 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AccessTokenService.js +1 -1
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +22 -3
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +89 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +19 -1
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +6 -5
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js +13 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
- package/build/dist/Tests/Server/Services/AnalyticsDatabaseService.test.js +2 -2
- package/build/dist/Tests/Server/Services/AnalyticsDatabaseService.test.js.map +1 -1
- package/build/dist/Types/API/HTTPResponse.js +15 -0
- package/build/dist/Types/API/HTTPResponse.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldType.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Date.js +10 -1
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/ListData.js +4 -0
- package/build/dist/Types/ListData.js.map +1 -1
- package/build/dist/Types/Probe/ProbeAttempt.js +2 -0
- package/build/dist/Types/Probe/ProbeAttempt.js.map +1 -0
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +376 -0
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -0
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +32 -25
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +32 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js +84 -0
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +34 -3
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +1 -1
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +45 -5
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js +40 -19
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +62 -36
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +1 -1
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -0
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,11 +4,13 @@ import { ButtonStyleType } from "../Button/Button";
|
|
|
4
4
|
import Card from "../Card/Card";
|
|
5
5
|
import ComponentLoader from "../ComponentLoader/ComponentLoader";
|
|
6
6
|
import Detail from "../Detail/Detail";
|
|
7
|
+
import { DropdownOption } from "../Dropdown/Dropdown";
|
|
7
8
|
import ErrorMessage from "../ErrorMessage/ErrorMessage";
|
|
8
9
|
import BasicFormModal from "../FormModal/BasicFormModal";
|
|
9
10
|
import BaseModel, {
|
|
10
11
|
DatabaseBaseModelType,
|
|
11
12
|
} from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
13
|
+
import CustomFieldType from "../../../Types/CustomField/CustomFieldType";
|
|
12
14
|
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
13
15
|
import { PromiseVoidFunction } from "../../../Types/FunctionTypes";
|
|
14
16
|
import IconProp from "../../../Types/Icon/IconProp";
|
|
@@ -17,6 +19,25 @@ import ObjectID from "../../../Types/ObjectID";
|
|
|
17
19
|
import React, { FunctionComponent, ReactElement, useState } from "react";
|
|
18
20
|
import useAsyncEffect from "use-async-effect";
|
|
19
21
|
|
|
22
|
+
const parseDropdownOptions: (value: unknown) => Array<DropdownOption> = (
|
|
23
|
+
value: unknown,
|
|
24
|
+
): Array<DropdownOption> => {
|
|
25
|
+
if (typeof value !== "string" || !value) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return value
|
|
29
|
+
.split("\n")
|
|
30
|
+
.map((line: string) => {
|
|
31
|
+
return line.trim();
|
|
32
|
+
})
|
|
33
|
+
.filter((line: string) => {
|
|
34
|
+
return line.length > 0;
|
|
35
|
+
})
|
|
36
|
+
.map((line: string) => {
|
|
37
|
+
return { label: line, value: line };
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
20
41
|
export interface ComponentProps {
|
|
21
42
|
title: string;
|
|
22
43
|
description: string;
|
|
@@ -54,6 +75,7 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
54
75
|
name: true,
|
|
55
76
|
customFieldType: true,
|
|
56
77
|
description: true,
|
|
78
|
+
dropdownOptions: true,
|
|
57
79
|
} as any,
|
|
58
80
|
sort: {},
|
|
59
81
|
});
|
|
@@ -137,12 +159,20 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
137
159
|
id={props.name}
|
|
138
160
|
item={(model as any)["customFields"] || {}}
|
|
139
161
|
fields={schemaList.map((schemaItem: BaseModel) => {
|
|
162
|
+
const isDropdown: boolean =
|
|
163
|
+
(schemaItem as any).customFieldType ===
|
|
164
|
+
CustomFieldType.Dropdown ||
|
|
165
|
+
(schemaItem as any).customFieldType ===
|
|
166
|
+
CustomFieldType.MultiSelectDropdown;
|
|
140
167
|
return {
|
|
141
168
|
key: (schemaItem as any).name,
|
|
142
169
|
title: (schemaItem as any).name,
|
|
143
170
|
description: (schemaItem as any).description,
|
|
144
171
|
fieldType: (schemaItem as any).customFieldType,
|
|
145
172
|
placeholder: "No data entered",
|
|
173
|
+
dropdownOptions: isDropdown
|
|
174
|
+
? parseDropdownOptions((schemaItem as any).dropdownOptions)
|
|
175
|
+
: undefined,
|
|
146
176
|
};
|
|
147
177
|
})}
|
|
148
178
|
showDetailsInNumberOfColumns={1}
|
|
@@ -161,6 +191,11 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
161
191
|
formProps={{
|
|
162
192
|
initialValues: (model as any)["customFields"] || {},
|
|
163
193
|
fields: schemaList.map((schemaItem: BaseModel) => {
|
|
194
|
+
const isDropdown: boolean =
|
|
195
|
+
(schemaItem as any).customFieldType ===
|
|
196
|
+
CustomFieldType.Dropdown ||
|
|
197
|
+
(schemaItem as any).customFieldType ===
|
|
198
|
+
CustomFieldType.MultiSelectDropdown;
|
|
164
199
|
return {
|
|
165
200
|
field: {
|
|
166
201
|
[(schemaItem as any).name]: true,
|
|
@@ -170,6 +205,9 @@ const CustomFieldsDetail: FunctionComponent<ComponentProps> = (
|
|
|
170
205
|
fieldType: (schemaItem as any).customFieldType,
|
|
171
206
|
required: false,
|
|
172
207
|
placeholder: "",
|
|
208
|
+
dropdownOptions: isDropdown
|
|
209
|
+
? parseDropdownOptions((schemaItem as any).dropdownOptions)
|
|
210
|
+
: undefined,
|
|
173
211
|
};
|
|
174
212
|
}),
|
|
175
213
|
}}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import Button, { ButtonSize, ButtonStyleType } from "../Button/Button";
|
|
2
|
+
import Input, { InputType } from "../Input/Input";
|
|
3
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
4
|
+
import React, {
|
|
5
|
+
FunctionComponent,
|
|
6
|
+
MutableRefObject,
|
|
7
|
+
ReactElement,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from "react";
|
|
12
|
+
|
|
13
|
+
export interface ComponentProps {
|
|
14
|
+
initialValue?: string | undefined;
|
|
15
|
+
onChange?: ((value: string) => void) | undefined;
|
|
16
|
+
placeholder?: string | undefined;
|
|
17
|
+
error?: string | undefined;
|
|
18
|
+
onBlur?: (() => void) | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parseValue: (value: string | undefined) => Array<string> = (
|
|
22
|
+
value: string | undefined,
|
|
23
|
+
): Array<string> => {
|
|
24
|
+
if (!value) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
return value
|
|
28
|
+
.split("\n")
|
|
29
|
+
.map((line: string) => {
|
|
30
|
+
return line.trim();
|
|
31
|
+
})
|
|
32
|
+
.filter((line: string) => {
|
|
33
|
+
return line.length > 0;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const serializeValue: (options: Array<string>) => string = (
|
|
38
|
+
options: Array<string>,
|
|
39
|
+
): string => {
|
|
40
|
+
return options
|
|
41
|
+
.map((option: string) => {
|
|
42
|
+
return option.trim();
|
|
43
|
+
})
|
|
44
|
+
.filter((option: string) => {
|
|
45
|
+
return option.length > 0;
|
|
46
|
+
})
|
|
47
|
+
.join("\n");
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const DropdownOptionsInput: FunctionComponent<ComponentProps> = (
|
|
51
|
+
props: ComponentProps,
|
|
52
|
+
): ReactElement => {
|
|
53
|
+
const [options, setOptions] = useState<Array<string>>(() => {
|
|
54
|
+
const parsed: Array<string> = parseValue(props.initialValue);
|
|
55
|
+
return parsed.length > 0 ? parsed : [""];
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const lastEmittedRef: MutableRefObject<string> = useRef<string>(
|
|
59
|
+
serializeValue(parseValue(props.initialValue)),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const serialized: string = serializeValue(options);
|
|
64
|
+
if (serialized !== lastEmittedRef.current) {
|
|
65
|
+
lastEmittedRef.current = serialized;
|
|
66
|
+
if (props.onChange) {
|
|
67
|
+
props.onChange(serialized);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}, [options]);
|
|
71
|
+
|
|
72
|
+
type UpdateAtFn = (index: number, value: string) => void;
|
|
73
|
+
const updateAt: UpdateAtFn = (index: number, value: string): void => {
|
|
74
|
+
setOptions((prev: Array<string>) => {
|
|
75
|
+
const next: Array<string> = [...prev];
|
|
76
|
+
next[index] = value;
|
|
77
|
+
return next;
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type RemoveAtFn = (index: number) => void;
|
|
82
|
+
const removeAt: RemoveAtFn = (index: number): void => {
|
|
83
|
+
setOptions((prev: Array<string>) => {
|
|
84
|
+
const next: Array<string> = prev.filter((_: string, i: number) => {
|
|
85
|
+
return i !== index;
|
|
86
|
+
});
|
|
87
|
+
return next.length > 0 ? next : [""];
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
type AddOptionFn = () => void;
|
|
92
|
+
const addOption: AddOptionFn = (): void => {
|
|
93
|
+
setOptions((prev: Array<string>) => {
|
|
94
|
+
return [...prev, ""];
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div>
|
|
100
|
+
<div className="space-y-2">
|
|
101
|
+
{options.map((value: string, index: number) => {
|
|
102
|
+
return (
|
|
103
|
+
<div key={index} className="flex items-center gap-2">
|
|
104
|
+
<div className="flex h-7 w-7 items-center justify-center rounded-md bg-gray-100 text-xs font-medium text-gray-500">
|
|
105
|
+
{index + 1}
|
|
106
|
+
</div>
|
|
107
|
+
<div className="flex-1">
|
|
108
|
+
<Input
|
|
109
|
+
value={value}
|
|
110
|
+
placeholder={props.placeholder || `Option ${index + 1}`}
|
|
111
|
+
onChange={(newValue: string) => {
|
|
112
|
+
updateAt(index, newValue);
|
|
113
|
+
}}
|
|
114
|
+
onBlur={() => {
|
|
115
|
+
if (props.onBlur) {
|
|
116
|
+
props.onBlur();
|
|
117
|
+
}
|
|
118
|
+
}}
|
|
119
|
+
type={InputType.TEXT}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
<Button
|
|
123
|
+
title="Remove"
|
|
124
|
+
buttonStyle={ButtonStyleType.ICON}
|
|
125
|
+
icon={IconProp.Trash}
|
|
126
|
+
onClick={() => {
|
|
127
|
+
removeAt(index);
|
|
128
|
+
}}
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
})}
|
|
133
|
+
</div>
|
|
134
|
+
<div className="mt-3">
|
|
135
|
+
<Button
|
|
136
|
+
title="Add Option"
|
|
137
|
+
icon={IconProp.Add}
|
|
138
|
+
buttonSize={ButtonSize.Small}
|
|
139
|
+
buttonStyle={ButtonStyleType.NORMAL}
|
|
140
|
+
onClick={addOption}
|
|
141
|
+
/>
|
|
142
|
+
</div>
|
|
143
|
+
{props.error ? (
|
|
144
|
+
<p className="mt-2 text-sm text-red-500">{props.error}</p>
|
|
145
|
+
) : null}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export default DropdownOptionsInput;
|
|
@@ -113,25 +113,84 @@ const Detail: DetailFunction = <T extends GenericObject>(
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
return (
|
|
116
|
-
<span className="inline-flex items-center gap-1.5 px-
|
|
116
|
+
<span className="inline-flex items-center gap-x-1.5 px-2 py-1 rounded-md bg-indigo-50 text-indigo-700 text-xs font-medium ring-1 ring-inset ring-indigo-700/10">
|
|
117
117
|
<svg
|
|
118
|
-
className="
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
stroke="currentColor"
|
|
118
|
+
className="h-1.5 w-1.5 fill-indigo-500"
|
|
119
|
+
viewBox="0 0 6 6"
|
|
120
|
+
aria-hidden="true"
|
|
122
121
|
>
|
|
123
|
-
<
|
|
124
|
-
strokeLinecap="round"
|
|
125
|
-
strokeLinejoin="round"
|
|
126
|
-
strokeWidth={2}
|
|
127
|
-
d="M9 5l7 7-7 7"
|
|
128
|
-
/>
|
|
122
|
+
<circle cx={3} cy={3} r={3} />
|
|
129
123
|
</svg>
|
|
130
124
|
{selectedOption.label as string}
|
|
131
125
|
</span>
|
|
132
126
|
);
|
|
133
127
|
};
|
|
134
128
|
|
|
129
|
+
type GetMultiSelectDropdownViewerFunction = (
|
|
130
|
+
data: Array<string | number | boolean> | string,
|
|
131
|
+
options: Array<DropdownOption | DropdownOptionGroup>,
|
|
132
|
+
placeholder: string,
|
|
133
|
+
) => ReactElement;
|
|
134
|
+
|
|
135
|
+
const getMultiSelectDropdownViewer: GetMultiSelectDropdownViewerFunction = (
|
|
136
|
+
data: Array<string | number | boolean> | string,
|
|
137
|
+
options: Array<DropdownOption | DropdownOptionGroup>,
|
|
138
|
+
placeholder: string,
|
|
139
|
+
): ReactElement => {
|
|
140
|
+
const values: Array<string | number | boolean> = Array.isArray(data)
|
|
141
|
+
? data
|
|
142
|
+
: typeof data === "string" && data.length > 0
|
|
143
|
+
? [data]
|
|
144
|
+
: [];
|
|
145
|
+
|
|
146
|
+
if (values.length === 0) {
|
|
147
|
+
return (
|
|
148
|
+
<span className="text-gray-400 italic text-sm">{placeholder}</span>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const flatOptions: Array<DropdownOption> = (options || []).flatMap(
|
|
153
|
+
(item: DropdownOption | DropdownOptionGroup) => {
|
|
154
|
+
if ("options" in item && Array.isArray(item.options)) {
|
|
155
|
+
return item.options;
|
|
156
|
+
}
|
|
157
|
+
return [item as DropdownOption];
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div className="flex flex-wrap gap-1.5">
|
|
163
|
+
{values.map(
|
|
164
|
+
(value: string | number | boolean, index: number): ReactElement => {
|
|
165
|
+
const matched: DropdownOption | undefined = flatOptions.find(
|
|
166
|
+
(option: DropdownOption) => {
|
|
167
|
+
return option.value === value;
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
const label: string = matched
|
|
171
|
+
? (matched.label as string)
|
|
172
|
+
: String(value);
|
|
173
|
+
return (
|
|
174
|
+
<span
|
|
175
|
+
key={`${String(value)}-${index}`}
|
|
176
|
+
className="inline-flex items-center gap-x-1.5 px-2 py-1 rounded-md bg-indigo-50 text-indigo-700 text-xs font-medium ring-1 ring-inset ring-indigo-700/10"
|
|
177
|
+
>
|
|
178
|
+
<svg
|
|
179
|
+
className="h-1.5 w-1.5 fill-indigo-500"
|
|
180
|
+
viewBox="0 0 6 6"
|
|
181
|
+
aria-hidden="true"
|
|
182
|
+
>
|
|
183
|
+
<circle cx={3} cy={3} r={3} />
|
|
184
|
+
</svg>
|
|
185
|
+
{label}
|
|
186
|
+
</span>
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
135
194
|
type GetDictionaryOfStringsViewerFunction = (
|
|
136
195
|
data: Dictionary<string>,
|
|
137
196
|
) => ReactElement;
|
|
@@ -432,6 +491,14 @@ const Detail: DetailFunction = <T extends GenericObject>(
|
|
|
432
491
|
);
|
|
433
492
|
}
|
|
434
493
|
|
|
494
|
+
if (field.fieldType === FieldType.MultiSelectDropdown) {
|
|
495
|
+
data = getMultiSelectDropdownViewer(
|
|
496
|
+
data as Array<string | number | boolean> | string,
|
|
497
|
+
field.dropdownOptions || [],
|
|
498
|
+
field.placeholder as string,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
435
502
|
if (data && field.fieldType === FieldType.HiddenText) {
|
|
436
503
|
data = (
|
|
437
504
|
<HiddenText
|
|
@@ -21,6 +21,11 @@ export interface ComponentProps<T extends GenericObject> {
|
|
|
21
21
|
onNavigateToPage: (pageNumber: number, itemsOnPage: number) => void;
|
|
22
22
|
currentPageNumber: number;
|
|
23
23
|
totalItemsCount: number;
|
|
24
|
+
/*
|
|
25
|
+
* Optional. Forwarded to Pagination. When set, count is a lower
|
|
26
|
+
* bound and pagination switches to a prev/next-only UI.
|
|
27
|
+
*/
|
|
28
|
+
hasMore?: boolean | undefined;
|
|
24
29
|
itemsOnPage: number;
|
|
25
30
|
enableDragAndDrop?: boolean | undefined;
|
|
26
31
|
dragDropIndexField?: keyof T | undefined;
|
|
@@ -141,6 +146,7 @@ const List: ListFunction = <T extends GenericObject>(
|
|
|
141
146
|
pluralLabel={props.pluralLabel}
|
|
142
147
|
currentPageNumber={props.currentPageNumber}
|
|
143
148
|
totalItemsCount={props.totalItemsCount}
|
|
149
|
+
hasMore={props.hasMore}
|
|
144
150
|
itemsOnPage={props.itemsOnPage}
|
|
145
151
|
onNavigateToPage={props.onNavigateToPage}
|
|
146
152
|
isLoading={props.isLoading}
|
|
@@ -161,6 +161,11 @@ export interface BaseTableProps<
|
|
|
161
161
|
| undefined
|
|
162
162
|
| ((data: Array<TBaseModel>, totalCount: number) => void);
|
|
163
163
|
cardProps?: CardComponentProps | undefined;
|
|
164
|
+
/**
|
|
165
|
+
* Optional content rendered inside the card body, above the table rows.
|
|
166
|
+
* Useful for in-table filter chips, alerts, etc.
|
|
167
|
+
*/
|
|
168
|
+
topContent?: ReactElement | undefined;
|
|
164
169
|
helpContent?:
|
|
165
170
|
| {
|
|
166
171
|
title: string;
|
|
@@ -240,6 +245,19 @@ export interface BaseTableProps<
|
|
|
240
245
|
|
|
241
246
|
saveFilterProps?: SaveFilterProps | undefined;
|
|
242
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Extra serializable state to persist alongside the saved view (e.g. facet
|
|
250
|
+
* selections from a parent hook). Stored on `TableView.facets`. The shape
|
|
251
|
+
* is opaque to ModelTable.
|
|
252
|
+
*/
|
|
253
|
+
currentFacetState?: JSONObject | undefined;
|
|
254
|
+
/**
|
|
255
|
+
* Called when a saved view is loaded so the parent can restore its facet
|
|
256
|
+
* state. `null` means "no saved facet data" (e.g. the user reset to the
|
|
257
|
+
* default empty view).
|
|
258
|
+
*/
|
|
259
|
+
onFacetStateRestored?: ((state: JSONObject | null) => void) | undefined;
|
|
260
|
+
|
|
243
261
|
onFilterApplied?: ((isFilterApplied: boolean) => void) | undefined;
|
|
244
262
|
|
|
245
263
|
formSummary?: FormSummaryConfig | undefined;
|
|
@@ -363,6 +381,13 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
363
381
|
const [query, setQuery] = useState<Query<TBaseModel>>({});
|
|
364
382
|
const [currentPageNumber, setCurrentPageNumber] = useState<number>(1);
|
|
365
383
|
const [totalItemsCount, setTotalItemsCount] = useState<number>(0);
|
|
384
|
+
/*
|
|
385
|
+
* Analytics endpoints (Log/Span/Metric/...) skip COUNT(*) for
|
|
386
|
+
* performance and instead return `hasMore`. When `hasMore` is
|
|
387
|
+
* `undefined`, the endpoint emitted an exact `count` and the
|
|
388
|
+
* pagination falls back to the count-based UI.
|
|
389
|
+
*/
|
|
390
|
+
const [hasMore, setHasMore] = useState<boolean | undefined>(undefined);
|
|
366
391
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
367
392
|
|
|
368
393
|
const [error, setError] = useState<string>("");
|
|
@@ -1171,7 +1196,16 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
1171
1196
|
});
|
|
1172
1197
|
|
|
1173
1198
|
setTotalItemsCount(listResult.count);
|
|
1199
|
+
setHasMore(listResult.hasMore);
|
|
1174
1200
|
setData(listResult.data);
|
|
1201
|
+
/*
|
|
1202
|
+
* Fire onFetchSuccess so consumers (e.g. the resource-owners hook)
|
|
1203
|
+
* can react to the loaded page. Previously the prop was declared
|
|
1204
|
+
* but never invoked, which broke per-row owner enrichment.
|
|
1205
|
+
*/
|
|
1206
|
+
if (props.onFetchSuccess) {
|
|
1207
|
+
props.onFetchSuccess(listResult.data, listResult.count);
|
|
1208
|
+
}
|
|
1175
1209
|
} catch (err) {
|
|
1176
1210
|
setError(API.getFriendlyMessage(err));
|
|
1177
1211
|
}
|
|
@@ -1260,6 +1294,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
1260
1294
|
currentSortBy={sortBy}
|
|
1261
1295
|
currentItemsOnPage={itemsOnPage}
|
|
1262
1296
|
currentSortOrder={sortOrder}
|
|
1297
|
+
currentFacetState={props.currentFacetState}
|
|
1263
1298
|
onViewChange={async (tableView: TableView | null) => {
|
|
1264
1299
|
setTableView(tableView);
|
|
1265
1300
|
|
|
@@ -1286,12 +1321,22 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
1286
1321
|
if (classicTableFilters.length === 0) {
|
|
1287
1322
|
await getFilterDropdownItems();
|
|
1288
1323
|
}
|
|
1324
|
+
|
|
1325
|
+
if (props.onFacetStateRestored) {
|
|
1326
|
+
props.onFacetStateRestored(
|
|
1327
|
+
(tableView.facets as JSONObject | undefined) || null,
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1289
1330
|
} else {
|
|
1290
1331
|
setQuery({});
|
|
1291
1332
|
setSortBy(null);
|
|
1292
1333
|
setSortOrder(SortOrder.Descending);
|
|
1293
1334
|
setItemsOnPage(10);
|
|
1294
1335
|
setCurrentPageNumber(1);
|
|
1336
|
+
|
|
1337
|
+
if (props.onFacetStateRestored) {
|
|
1338
|
+
props.onFacetStateRestored(null);
|
|
1339
|
+
}
|
|
1295
1340
|
}
|
|
1296
1341
|
}}
|
|
1297
1342
|
/>
|
|
@@ -1577,6 +1622,17 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
1577
1622
|
serializeToTableColumns();
|
|
1578
1623
|
}, [data]);
|
|
1579
1624
|
|
|
1625
|
+
/*
|
|
1626
|
+
* Re-serialize whenever the parent passes a new `columns` reference. The
|
|
1627
|
+
* column array carries `getElement` closures that capture parent state
|
|
1628
|
+
* (e.g. an owners map populated asynchronously). Without this, the cell
|
|
1629
|
+
* renders are frozen at first paint and never see updated state — which
|
|
1630
|
+
* is what made the Owners column stick on "Loading…" forever.
|
|
1631
|
+
*/
|
|
1632
|
+
useEffect(() => {
|
|
1633
|
+
serializeToTableColumns();
|
|
1634
|
+
}, [props.columns]);
|
|
1635
|
+
|
|
1580
1636
|
const setActionSchema: VoidFunction = () => {
|
|
1581
1637
|
const permissions: Array<Permission> = PermissionUtil.getAllPermissions();
|
|
1582
1638
|
|
|
@@ -2063,6 +2119,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
2063
2119
|
dragDropIdField={"_id"}
|
|
2064
2120
|
dragDropIndexField={props.dragDropIndexField}
|
|
2065
2121
|
totalItemsCount={totalItemsCount}
|
|
2122
|
+
hasMore={hasMore}
|
|
2066
2123
|
data={data}
|
|
2067
2124
|
id={props.id}
|
|
2068
2125
|
columns={tableColumns}
|
|
@@ -2219,6 +2276,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
2219
2276
|
dragDropIndexField={props.dragDropIndexField}
|
|
2220
2277
|
isLoading={getTableLoadingState()}
|
|
2221
2278
|
totalItemsCount={totalItemsCount}
|
|
2279
|
+
hasMore={hasMore}
|
|
2222
2280
|
data={data}
|
|
2223
2281
|
id={props.id}
|
|
2224
2282
|
fields={fields}
|
|
@@ -2429,7 +2487,8 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
2429
2487
|
})
|
|
2430
2488
|
.filter((l: SearchLabelOption) => {
|
|
2431
2489
|
return (
|
|
2432
|
-
lowerPrefix.length === 0 ||
|
|
2490
|
+
lowerPrefix.length === 0 ||
|
|
2491
|
+
l.name.toLowerCase().startsWith(lowerPrefix)
|
|
2433
2492
|
);
|
|
2434
2493
|
})
|
|
2435
2494
|
.slice(0, 8);
|
|
@@ -2600,7 +2659,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
2600
2659
|
/>
|
|
2601
2660
|
</div>
|
|
2602
2661
|
)}
|
|
2603
|
-
{showMatchPill && totalItemsCount >= 0 && (
|
|
2662
|
+
{showMatchPill && totalItemsCount >= 0 && hasMore === undefined && (
|
|
2604
2663
|
<span
|
|
2605
2664
|
className="flex-none whitespace-nowrap rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700"
|
|
2606
2665
|
title={`${totalItemsCount} ${totalItemsCount === 1 ? "result" : "results"}`}
|
|
@@ -2608,6 +2667,17 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
2608
2667
|
{totalItemsCount} {totalItemsCount === 1 ? "match" : "matches"}
|
|
2609
2668
|
</span>
|
|
2610
2669
|
)}
|
|
2670
|
+
{showMatchPill && hasMore !== undefined && data.length > 0 && (
|
|
2671
|
+
<span
|
|
2672
|
+
className="flex-none whitespace-nowrap rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-700"
|
|
2673
|
+
title={`${data.length}${hasMore ? "+" : ""} ${
|
|
2674
|
+
data.length === 1 ? "result" : "results"
|
|
2675
|
+
} on this page`}
|
|
2676
|
+
>
|
|
2677
|
+
{data.length}
|
|
2678
|
+
{hasMore ? "+" : ""} {data.length === 1 ? "match" : "matches"}
|
|
2679
|
+
</span>
|
|
2680
|
+
)}
|
|
2611
2681
|
{searchText.length > 0 || selectedLabels.length > 0 ? (
|
|
2612
2682
|
<button
|
|
2613
2683
|
type="button"
|
|
@@ -3055,6 +3125,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
3055
3125
|
) : (
|
|
3056
3126
|
<></>
|
|
3057
3127
|
)}
|
|
3128
|
+
{props.topContent || <></>}
|
|
3058
3129
|
{tableColumns.length > 0 && showAs === ShowAs.Table ? (
|
|
3059
3130
|
getTable()
|
|
3060
3131
|
) : (
|
|
@@ -3081,6 +3152,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
3081
3152
|
) : (
|
|
3082
3153
|
<></>
|
|
3083
3154
|
)}
|
|
3155
|
+
{!props.cardProps && props.topContent}
|
|
3084
3156
|
{!props.cardProps && showAs === ShowAs.Table ? getTable() : <></>}
|
|
3085
3157
|
{!props.cardProps && showAs === ShowAs.List ? getList() : <></>}
|
|
3086
3158
|
</div>
|