@oneuptime/common 11.7.2 → 11.7.3
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/KubernetesCostAllocation.ts +103 -0
- package/Models/DatabaseModels/TableView.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
- package/Tests/Types/JSONFunctions.test.ts +103 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +572 -0
- package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
- package/Tests/Utils/UserPreferences.test.ts +563 -0
- package/Types/JSONFunctions.ts +7 -2
- package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
- package/UI/Components/MasterPage/MasterPage.tsx +65 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +387 -4
- package/UI/Components/ModelTable/Column.ts +17 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +420 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
- package/UI/Components/ModelTable/TableView.tsx +24 -2
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
- package/UI/Components/SideMenu/SideMenu.tsx +24 -4
- package/UI/Components/Table/Table.tsx +14 -1
- package/UI/Components/Table/TableRow.tsx +180 -175
- package/UI/Components/Table/Types/Column.ts +2 -0
- package/Utils/UserPreferences.ts +53 -0
- package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
- package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.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/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +8 -3
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +239 -7
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +189 -0
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
- package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
- package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +15 -2
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +11 -6
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/Utils/UserPreferences.js +33 -0
- package/build/dist/Utils/UserPreferences.js.map +1 -1
- package/package.json +1 -1
|
@@ -77,6 +77,13 @@ const TableRow: TableRowFunction = <T extends GenericObject>(
|
|
|
77
77
|
};
|
|
78
78
|
}, []);
|
|
79
79
|
|
|
80
|
+
// The columns this row will actually put on screen.
|
|
81
|
+
const renderedColumns: Array<Column<T>> = (props.columns || []).filter(
|
|
82
|
+
(column: Column<T>) => {
|
|
83
|
+
return !(column.hideOnMobile && isMobile);
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
80
87
|
type GetRowFunction = (provided?: DraggableProvided) => ReactElement;
|
|
81
88
|
|
|
82
89
|
const getRow: GetRowFunction = (
|
|
@@ -339,186 +346,184 @@ const TableRow: TableRowFunction = <T extends GenericObject>(
|
|
|
339
346
|
</td>
|
|
340
347
|
)}
|
|
341
348
|
{props.columns &&
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
) : (
|
|
365
|
-
column.noValueMessage || ""
|
|
366
|
-
)
|
|
367
|
-
) : column.type === FieldType.DateTime ? (
|
|
368
|
-
props.item[column.key] ? (
|
|
369
|
-
OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
370
|
-
props.item[column.key] as string,
|
|
371
|
-
false,
|
|
372
|
-
)
|
|
373
|
-
) : (
|
|
374
|
-
column.noValueMessage || ""
|
|
375
|
-
)
|
|
376
|
-
) : column.type === FieldType.USDCents ? (
|
|
377
|
-
props.item[column.key] ? (
|
|
378
|
-
((props.item[column.key] as number) || 0) / 100 + " USD"
|
|
379
|
-
) : (
|
|
380
|
-
column.noValueMessage || "0 USD"
|
|
381
|
-
)
|
|
382
|
-
) : column.type === FieldType.Percent ? (
|
|
383
|
-
props.item[column.key] ? (
|
|
384
|
-
props.item[column.key] + "%"
|
|
385
|
-
) : (
|
|
386
|
-
column.noValueMessage || "0%"
|
|
349
|
+
renderedColumns.map((column: Column<T>, i: number) => {
|
|
350
|
+
let className: string =
|
|
351
|
+
"whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-500 sm:pl-6 align-top";
|
|
352
|
+
/*
|
|
353
|
+
* Compared against the rendered count, not the declared one:
|
|
354
|
+
* with any column filtered out the two differ, and the extra
|
|
355
|
+
* right padding would land on the wrong cell - or on none.
|
|
356
|
+
*/
|
|
357
|
+
if (i === renderedColumns.length - 1) {
|
|
358
|
+
className =
|
|
359
|
+
"whitespace-nowrap py-4 pl-4 pr-6 text-sm font-medium text-gray-500 sm:pl-6 align-top";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
let columnContent: React.ReactNode = null;
|
|
363
|
+
|
|
364
|
+
if (column.key && !column.getElement) {
|
|
365
|
+
columnContent =
|
|
366
|
+
column.type === FieldType.Date ? (
|
|
367
|
+
props.item[column.key] ? (
|
|
368
|
+
OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
369
|
+
props.item[column.key] as string,
|
|
370
|
+
true,
|
|
387
371
|
)
|
|
388
|
-
) :
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
<LongTextViewer
|
|
397
|
-
text={props.item[column.key] as string}
|
|
398
|
-
/>
|
|
399
|
-
) : (
|
|
400
|
-
column.noValueMessage || ""
|
|
401
|
-
)
|
|
402
|
-
) : column.type === FieldType.Boolean ? (
|
|
403
|
-
props.item[column.key] ? (
|
|
404
|
-
<Icon
|
|
405
|
-
icon={IconProp.Check}
|
|
406
|
-
className={"h-5 w-5 text-gray-500"}
|
|
407
|
-
thick={ThickProp.Thick}
|
|
408
|
-
/>
|
|
409
|
-
) : (
|
|
410
|
-
<Icon
|
|
411
|
-
icon={IconProp.False}
|
|
412
|
-
className={"h-5 w-5 text-gray-500"}
|
|
413
|
-
thick={ThickProp.Thick}
|
|
414
|
-
/>
|
|
372
|
+
) : (
|
|
373
|
+
column.noValueMessage || ""
|
|
374
|
+
)
|
|
375
|
+
) : column.type === FieldType.DateTime ? (
|
|
376
|
+
props.item[column.key] ? (
|
|
377
|
+
OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
378
|
+
props.item[column.key] as string,
|
|
379
|
+
false,
|
|
415
380
|
)
|
|
416
381
|
) : (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
column.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
382
|
+
column.noValueMessage || ""
|
|
383
|
+
)
|
|
384
|
+
) : column.type === FieldType.USDCents ? (
|
|
385
|
+
props.item[column.key] ? (
|
|
386
|
+
((props.item[column.key] as number) || 0) / 100 + " USD"
|
|
387
|
+
) : (
|
|
388
|
+
column.noValueMessage || "0 USD"
|
|
389
|
+
)
|
|
390
|
+
) : column.type === FieldType.Percent ? (
|
|
391
|
+
props.item[column.key] ? (
|
|
392
|
+
props.item[column.key] + "%"
|
|
393
|
+
) : (
|
|
394
|
+
column.noValueMessage || "0%"
|
|
395
|
+
)
|
|
396
|
+
) : column.type === FieldType.Color ? (
|
|
397
|
+
props.item[column.key] ? (
|
|
398
|
+
<ColorInput value={props.item[column.key] as Color} />
|
|
399
|
+
) : (
|
|
400
|
+
column.noValueMessage || "0%"
|
|
401
|
+
)
|
|
402
|
+
) : column.type === FieldType.LongText ? (
|
|
403
|
+
props.item[column.key] ? (
|
|
404
|
+
<LongTextViewer text={props.item[column.key] as string} />
|
|
405
|
+
) : (
|
|
406
|
+
column.noValueMessage || ""
|
|
407
|
+
)
|
|
408
|
+
) : column.type === FieldType.Boolean ? (
|
|
409
|
+
props.item[column.key] ? (
|
|
410
|
+
<Icon
|
|
411
|
+
icon={IconProp.Check}
|
|
412
|
+
className={"h-5 w-5 text-gray-500"}
|
|
413
|
+
thick={ThickProp.Thick}
|
|
414
|
+
/>
|
|
415
|
+
) : (
|
|
416
|
+
<Icon
|
|
417
|
+
icon={IconProp.False}
|
|
418
|
+
className={"h-5 w-5 text-gray-500"}
|
|
419
|
+
thick={ThickProp.Thick}
|
|
420
|
+
/>
|
|
421
|
+
)
|
|
422
|
+
) : (
|
|
423
|
+
getNestedValue(
|
|
424
|
+
props.item,
|
|
425
|
+
String(column.key),
|
|
426
|
+
)?.toString() ||
|
|
427
|
+
column.noValueMessage ||
|
|
428
|
+
""
|
|
429
|
+
);
|
|
430
|
+
} else if (column.key && column.getElement) {
|
|
431
|
+
columnContent = column.getElement(props.item);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const contentWrapperClassName: string = column.contentClassName
|
|
435
|
+
? column.contentClassName
|
|
436
|
+
: "";
|
|
437
|
+
|
|
438
|
+
const actionsContainerClassName: string = column.contentClassName
|
|
439
|
+
? `flex justify-end ${column.contentClassName}`
|
|
440
|
+
: "flex justify-end";
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<td
|
|
444
|
+
key={i}
|
|
445
|
+
className={className}
|
|
446
|
+
style={{
|
|
447
|
+
textAlign:
|
|
448
|
+
column.type === FieldType.Actions ? "right" : "left",
|
|
449
|
+
}}
|
|
450
|
+
onClick={() => {
|
|
451
|
+
if (column.tooltipText) {
|
|
452
|
+
setTooltipModalText(column.tooltipText(props.item));
|
|
453
|
+
}
|
|
454
|
+
}}
|
|
455
|
+
>
|
|
456
|
+
{columnContent !== null && columnContent !== undefined && (
|
|
457
|
+
<div className={contentWrapperClassName}>
|
|
458
|
+
{columnContent}
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
{column.type === FieldType.Actions && (
|
|
462
|
+
<div className={actionsContainerClassName}>
|
|
463
|
+
{error && (
|
|
464
|
+
<div className="text-align-left">
|
|
465
|
+
<ConfirmModal
|
|
466
|
+
title={`Error`}
|
|
467
|
+
description={error}
|
|
468
|
+
submitButtonText={"Close"}
|
|
469
|
+
onSubmit={() => {
|
|
470
|
+
return setError("");
|
|
471
|
+
}}
|
|
472
|
+
/>
|
|
473
|
+
</div>
|
|
474
|
+
)}
|
|
475
|
+
{props.actionButtons?.map(
|
|
476
|
+
(button: ActionButtonSchema<T>, i: number) => {
|
|
477
|
+
if (
|
|
478
|
+
button.isVisible &&
|
|
479
|
+
!button.isVisible(props.item)
|
|
480
|
+
) {
|
|
481
|
+
return <div key={i}></div>;
|
|
482
|
+
}
|
|
436
483
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
style={{
|
|
442
|
-
textAlign:
|
|
443
|
-
column.type === FieldType.Actions ? "right" : "left",
|
|
444
|
-
}}
|
|
445
|
-
onClick={() => {
|
|
446
|
-
if (column.tooltipText) {
|
|
447
|
-
setTooltipModalText(column.tooltipText(props.item));
|
|
448
|
-
}
|
|
449
|
-
}}
|
|
450
|
-
>
|
|
451
|
-
{columnContent !== null && columnContent !== undefined && (
|
|
452
|
-
<div className={contentWrapperClassName}>
|
|
453
|
-
{columnContent}
|
|
454
|
-
</div>
|
|
455
|
-
)}
|
|
456
|
-
{column.type === FieldType.Actions && (
|
|
457
|
-
<div className={actionsContainerClassName}>
|
|
458
|
-
{error && (
|
|
459
|
-
<div className="text-align-left">
|
|
460
|
-
<ConfirmModal
|
|
461
|
-
title={`Error`}
|
|
462
|
-
description={error}
|
|
463
|
-
submitButtonText={"Close"}
|
|
464
|
-
onSubmit={() => {
|
|
465
|
-
return setError("");
|
|
466
|
-
}}
|
|
467
|
-
/>
|
|
468
|
-
</div>
|
|
469
|
-
)}
|
|
470
|
-
{props.actionButtons?.map(
|
|
471
|
-
(button: ActionButtonSchema<T>, i: number) => {
|
|
472
|
-
if (
|
|
473
|
-
button.isVisible &&
|
|
474
|
-
!button.isVisible(props.item)
|
|
475
|
-
) {
|
|
476
|
-
return <div key={i}></div>;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Hide button on mobile if hideOnMobile is true
|
|
480
|
-
if (button.hideOnMobile && isMobile) {
|
|
481
|
-
return <div key={i}></div>;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
return (
|
|
485
|
-
<div key={i}>
|
|
486
|
-
<Button
|
|
487
|
-
buttonSize={ButtonSize.Small}
|
|
488
|
-
title={button.title}
|
|
489
|
-
icon={button.icon}
|
|
490
|
-
buttonStyle={button.buttonStyleType}
|
|
491
|
-
isLoading={isButtonLoading[i]}
|
|
492
|
-
onClick={() => {
|
|
493
|
-
if (button.onClick) {
|
|
494
|
-
isButtonLoading[i] = true;
|
|
495
|
-
setIsButtonLoading(isButtonLoading);
|
|
484
|
+
// Hide button on mobile if hideOnMobile is true
|
|
485
|
+
if (button.hideOnMobile && isMobile) {
|
|
486
|
+
return <div key={i}></div>;
|
|
487
|
+
}
|
|
496
488
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
489
|
+
return (
|
|
490
|
+
<div key={i}>
|
|
491
|
+
<Button
|
|
492
|
+
buttonSize={ButtonSize.Small}
|
|
493
|
+
title={button.title}
|
|
494
|
+
icon={button.icon}
|
|
495
|
+
buttonStyle={button.buttonStyleType}
|
|
496
|
+
isLoading={isButtonLoading[i]}
|
|
497
|
+
onClick={() => {
|
|
498
|
+
if (button.onClick) {
|
|
499
|
+
isButtonLoading[i] = true;
|
|
500
|
+
setIsButtonLoading(isButtonLoading);
|
|
501
|
+
|
|
502
|
+
button.onClick(
|
|
503
|
+
props.item,
|
|
504
|
+
() => {
|
|
505
|
+
// on action complete
|
|
506
|
+
isButtonLoading[i] = false;
|
|
507
|
+
setIsButtonLoading(isButtonLoading);
|
|
508
|
+
},
|
|
509
|
+
(err: Error) => {
|
|
510
|
+
isButtonLoading[i] = false;
|
|
511
|
+
setIsButtonLoading(isButtonLoading);
|
|
512
|
+
setError((err as Error).message);
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
}}
|
|
517
|
+
/>
|
|
518
|
+
</div>
|
|
519
|
+
);
|
|
520
|
+
},
|
|
521
|
+
)}
|
|
522
|
+
</div>
|
|
523
|
+
)}
|
|
524
|
+
</td>
|
|
525
|
+
);
|
|
526
|
+
})}
|
|
522
527
|
</tr>
|
|
523
528
|
{tooltipModalText && (
|
|
524
529
|
<ConfirmModal
|
|
@@ -5,6 +5,8 @@ import { ReactElement } from "react";
|
|
|
5
5
|
|
|
6
6
|
export default interface Column<T extends GenericObject> {
|
|
7
7
|
title: string;
|
|
8
|
+
// Stable identity carried over from the ModelTable column, if it had one.
|
|
9
|
+
id?: string | undefined;
|
|
8
10
|
description?: string | undefined;
|
|
9
11
|
disableSort?: boolean | undefined;
|
|
10
12
|
tooltipText?: ((item: T) => string) | undefined;
|
package/Utils/UserPreferences.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import LocalStorage from "../UI/Utils/LocalStorage";
|
|
2
|
+
import { JSONObject } from "../Types/JSON";
|
|
2
3
|
|
|
3
4
|
export enum UserPreferenceType {
|
|
4
5
|
BaseModelTablePageSize = "BaseModelTablePageSize",
|
|
6
|
+
BaseModelTableColumns = "BaseModelTableColumns",
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
export default class UserPreferences {
|
|
@@ -30,4 +32,55 @@ export default class UserPreferences {
|
|
|
30
32
|
const finalKey: string = `${userPreferenceType}.${key}`;
|
|
31
33
|
LocalStorage.setItem(finalKey, value);
|
|
32
34
|
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Object-shaped preferences (e.g. a table's column layout). LocalStorage
|
|
38
|
+
* already JSON-serializes objects on the way in and parses them on the way
|
|
39
|
+
* out, so this only has to reject anything that came back as something other
|
|
40
|
+
* than a plain object - a half-written or hand-edited entry must degrade to
|
|
41
|
+
* "no preference" rather than reaching the caller as a string or an array.
|
|
42
|
+
*/
|
|
43
|
+
public static getUserPreferenceByTypeAsJSON(data: {
|
|
44
|
+
key: string;
|
|
45
|
+
userPreferenceType: UserPreferenceType;
|
|
46
|
+
}): JSONObject | null {
|
|
47
|
+
const { key, userPreferenceType } = data;
|
|
48
|
+
|
|
49
|
+
const finalKey: string = `${userPreferenceType}.${key}`;
|
|
50
|
+
|
|
51
|
+
let value: unknown = null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
value = LocalStorage.getItem(finalKey);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return value as JSONObject;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public static saveUserPreferenceByTypeAsJSON(data: {
|
|
67
|
+
key: string;
|
|
68
|
+
userPreferenceType: UserPreferenceType;
|
|
69
|
+
value: JSONObject;
|
|
70
|
+
}): void {
|
|
71
|
+
const { key, userPreferenceType, value } = data;
|
|
72
|
+
|
|
73
|
+
const finalKey: string = `${userPreferenceType}.${key}`;
|
|
74
|
+
LocalStorage.setItem(finalKey, value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public static removeUserPreferenceByType(data: {
|
|
78
|
+
key: string;
|
|
79
|
+
userPreferenceType: UserPreferenceType;
|
|
80
|
+
}): void {
|
|
81
|
+
const { key, userPreferenceType } = data;
|
|
82
|
+
|
|
83
|
+
const finalKey: string = `${userPreferenceType}.${key}`;
|
|
84
|
+
LocalStorage.removeItem(finalKey);
|
|
85
|
+
}
|
|
33
86
|
}
|
|
@@ -305,6 +305,11 @@ export default class KubernetesCostAllocation extends AnalyticsBaseModel {
|
|
|
305
305
|
title: "CPU Core Usage Average",
|
|
306
306
|
description: "Average CPU cores actually used over the window.",
|
|
307
307
|
},
|
|
308
|
+
{
|
|
309
|
+
key: "cpuCoreLimitAverage",
|
|
310
|
+
title: "CPU Core Limit Average",
|
|
311
|
+
description: "Average CPU core limit over the window. 0 when no limit is set — which is also what a row ingested before this column reads back.",
|
|
312
|
+
},
|
|
308
313
|
{
|
|
309
314
|
key: "cpuCost",
|
|
310
315
|
title: "CPU Cost",
|
|
@@ -335,6 +340,16 @@ export default class KubernetesCostAllocation extends AnalyticsBaseModel {
|
|
|
335
340
|
title: "RAM Bytes Usage Average",
|
|
336
341
|
description: "Average RAM bytes actually used over the window.",
|
|
337
342
|
},
|
|
343
|
+
{
|
|
344
|
+
key: "ramBytesLimitAverage",
|
|
345
|
+
title: "RAM Bytes Limit Average",
|
|
346
|
+
description: "Average RAM byte limit over the window. 0 when no limit is set — which is also what a row ingested before this column reads back.",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
key: "ramBytesUsageMax",
|
|
350
|
+
title: "RAM Bytes Usage Max",
|
|
351
|
+
description: "Peak RAM bytes used during the window, from Prometheus rather than the cost engine — the engine only reports averages, and an hourly mean hides the burst that OOMKills a container. 0 when the agent has no Prometheus configured, when the scrape had no data, or on rows ingested before this column; treat 0 as 'unknown', never as a real peak.",
|
|
352
|
+
},
|
|
338
353
|
{
|
|
339
354
|
key: "ramCost",
|
|
340
355
|
title: "RAM Cost",
|
|
@@ -420,6 +435,45 @@ export default class KubernetesCostAllocation extends AnalyticsBaseModel {
|
|
|
420
435
|
update: [],
|
|
421
436
|
},
|
|
422
437
|
});
|
|
438
|
+
/*
|
|
439
|
+
* Delivery bookkeeping, not workload data. A window wider than the
|
|
440
|
+
* agent's batch size arrives as several independent ingest jobs, so the
|
|
441
|
+
* ingest service needs to tell "another chunk of the delivery I am
|
|
442
|
+
* already ingesting" (accept) from "a window a previous delivery already
|
|
443
|
+
* ingested" (drop, or a restarted agent double-counts spend). shipmentId
|
|
444
|
+
* is the agent's content hash of the window — identical across restarts,
|
|
445
|
+
* different when the engine's answer for that window changed.
|
|
446
|
+
*
|
|
447
|
+
* Empty / 0 on rows written before these columns existed and on rows
|
|
448
|
+
* from agents older than the shipment contract; the ingest service falls
|
|
449
|
+
* back to the whole-window guard for those.
|
|
450
|
+
*/
|
|
451
|
+
const shipmentIdColumn = new AnalyticsTableColumn({
|
|
452
|
+
key: "shipmentId",
|
|
453
|
+
title: "Shipment ID",
|
|
454
|
+
description: "Content hash identifying the agent delivery this row arrived in. Used to deduplicate re-shipped windows; empty for rows ingested before the shipment contract.",
|
|
455
|
+
required: true,
|
|
456
|
+
defaultValue: "",
|
|
457
|
+
type: TableColumnType.Text,
|
|
458
|
+
accessControl: {
|
|
459
|
+
read: readPermissions,
|
|
460
|
+
create: createPermissions,
|
|
461
|
+
update: [],
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
const shipmentChunkColumn = new AnalyticsTableColumn({
|
|
465
|
+
key: "shipmentChunk",
|
|
466
|
+
title: "Shipment Chunk",
|
|
467
|
+
description: "Index of the request within its shipment that carried this row. 0 for single-request shipments and for rows ingested before the shipment contract.",
|
|
468
|
+
required: true,
|
|
469
|
+
defaultValue: 0,
|
|
470
|
+
type: TableColumnType.Number,
|
|
471
|
+
accessControl: {
|
|
472
|
+
read: readPermissions,
|
|
473
|
+
create: createPermissions,
|
|
474
|
+
update: [],
|
|
475
|
+
},
|
|
476
|
+
});
|
|
423
477
|
const retentionDateColumn = new AnalyticsTableColumn({
|
|
424
478
|
key: "retentionDate",
|
|
425
479
|
codec: [{ codec: "DoubleDelta" }, { codec: "ZSTD", level: 1 }],
|
|
@@ -462,6 +516,8 @@ export default class KubernetesCostAllocation extends AnalyticsBaseModel {
|
|
|
462
516
|
labelKeysColumn,
|
|
463
517
|
...measureColumns,
|
|
464
518
|
currencyColumn,
|
|
519
|
+
shipmentIdColumn,
|
|
520
|
+
shipmentChunkColumn,
|
|
465
521
|
retentionDateColumn,
|
|
466
522
|
],
|
|
467
523
|
sortKeys: [
|
|
@@ -595,6 +651,12 @@ export default class KubernetesCostAllocation extends AnalyticsBaseModel {
|
|
|
595
651
|
set cpuCoreUsageAverage(v) {
|
|
596
652
|
this.setColumnValue("cpuCoreUsageAverage", v);
|
|
597
653
|
}
|
|
654
|
+
get cpuCoreLimitAverage() {
|
|
655
|
+
return this.getColumnValue("cpuCoreLimitAverage");
|
|
656
|
+
}
|
|
657
|
+
set cpuCoreLimitAverage(v) {
|
|
658
|
+
this.setColumnValue("cpuCoreLimitAverage", v);
|
|
659
|
+
}
|
|
598
660
|
get cpuCost() {
|
|
599
661
|
return this.getColumnValue("cpuCost");
|
|
600
662
|
}
|
|
@@ -631,6 +693,18 @@ export default class KubernetesCostAllocation extends AnalyticsBaseModel {
|
|
|
631
693
|
set ramBytesUsageAverage(v) {
|
|
632
694
|
this.setColumnValue("ramBytesUsageAverage", v);
|
|
633
695
|
}
|
|
696
|
+
get ramBytesLimitAverage() {
|
|
697
|
+
return this.getColumnValue("ramBytesLimitAverage");
|
|
698
|
+
}
|
|
699
|
+
set ramBytesLimitAverage(v) {
|
|
700
|
+
this.setColumnValue("ramBytesLimitAverage", v);
|
|
701
|
+
}
|
|
702
|
+
get ramBytesUsageMax() {
|
|
703
|
+
return this.getColumnValue("ramBytesUsageMax");
|
|
704
|
+
}
|
|
705
|
+
set ramBytesUsageMax(v) {
|
|
706
|
+
this.setColumnValue("ramBytesUsageMax", v);
|
|
707
|
+
}
|
|
634
708
|
get ramCost() {
|
|
635
709
|
return this.getColumnValue("ramCost");
|
|
636
710
|
}
|
|
@@ -703,6 +777,18 @@ export default class KubernetesCostAllocation extends AnalyticsBaseModel {
|
|
|
703
777
|
set currency(v) {
|
|
704
778
|
this.setColumnValue("currency", v);
|
|
705
779
|
}
|
|
780
|
+
get shipmentId() {
|
|
781
|
+
return this.getColumnValue("shipmentId");
|
|
782
|
+
}
|
|
783
|
+
set shipmentId(v) {
|
|
784
|
+
this.setColumnValue("shipmentId", v);
|
|
785
|
+
}
|
|
786
|
+
get shipmentChunk() {
|
|
787
|
+
return this.getColumnValue("shipmentChunk");
|
|
788
|
+
}
|
|
789
|
+
set shipmentChunk(v) {
|
|
790
|
+
this.setColumnValue("shipmentChunk", v);
|
|
791
|
+
}
|
|
706
792
|
get retentionDate() {
|
|
707
793
|
return this.getColumnValue("retentionDate");
|
|
708
794
|
}
|