From a2a8815de7452a069b06d65062578b1f8d031b0b Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 1 Mar 2024 16:24:26 -0500 Subject: [PATCH] chore(mbeans): adjust MBeanMetrics GraphQL query for updated schema (#1219) --- .../AllTargetsArchivedRecordingsTable.tsx | 10 ++ src/app/Archives/ArchiveUploadModal.tsx | 6 +- src/app/Archives/Archives.tsx | 5 + src/app/Archives/utils.tsx | 5 + .../CreateRecording/CustomRecordingForm.tsx | 27 ++-- src/app/CreateRecording/types.ts | 5 +- .../mbean/MBeanMetricsChartController.tsx | 3 + .../Dashboard/JvmDetails/JvmDetailsCard.tsx | 2 +- src/app/RecordingMetadata/BulkEditLabels.tsx | 16 +-- src/app/RecordingMetadata/ClickableLabel.tsx | 6 +- src/app/RecordingMetadata/LabelCell.tsx | 10 +- .../RecordingLabelFields.tsx | 14 +- src/app/RecordingMetadata/types.tsx | 20 --- src/app/RecordingMetadata/utils.ts | 22 +--- src/app/Recordings/ActiveRecordingsTable.tsx | 8 +- .../Recordings/ArchivedRecordingsTable.tsx | 8 +- src/app/Recordings/Filters/LabelFilter.tsx | 4 +- .../MatchExpression/MatchExpressionHint.tsx | 6 +- .../Components/MatchExpression/utils.tsx | 2 +- src/app/Shared/Services/Api.service.tsx | 25 ++-- src/app/Shared/Services/api.types.ts | 31 +++-- src/app/Shared/Services/api.utils.ts | 4 +- src/app/Shared/Services/service.utils.ts | 9 +- src/app/TargetView/TargetContextSelector.tsx | 5 +- src/app/TargetView/TargetSelect.tsx | 5 +- src/app/Topology/Actions/CreateTarget.tsx | 11 +- src/app/Topology/Entity/EntityAnnotations.tsx | 5 +- src/app/Topology/Entity/EntityDetails.tsx | 41 +++--- src/app/Topology/Entity/types.ts | 6 + src/app/Topology/Entity/utils.tsx | 3 +- src/app/utils/fakeData.ts | 64 ++++++--- src/app/utils/utils.ts | 13 ++ src/mirage/factories.ts | 18 ++- src/mirage/index.ts | 121 ++++++++++-------- src/test/Agent/AgentLiveProbes.test.tsx | 8 +- .../AllArchivedRecordingsTable.test.tsx | 9 +- ...AllTargetsArchivedRecordingsTable.test.tsx | 6 + .../CustomRecordingForm.test.tsx | 14 +- .../SnapshotRecordingForm.test.tsx | 8 +- .../AutomatedAnalysisCard.test.tsx | 10 +- .../AutomatedAnalysisConfigForm.test.tsx | 8 +- .../Charts/jfr/JFRMetricsChartCard.test.tsx | 8 +- .../mbean/MBeanMetricsChartCard.test.tsx | 8 +- src/test/Dashboard/Dashboard.test.tsx | 5 +- src/test/Events/EventTemplates.test.tsx | 12 +- src/test/Events/EventTypes.test.tsx | 12 +- .../RecordingMetadata/BulkEditLabels.test.tsx | 18 ++- .../RecordingMetadata/ClickableLabel.test.tsx | 6 +- src/test/RecordingMetadata/LabelCell.test.tsx | 12 +- .../RecordingLabelFields.test.tsx | 18 +-- .../Recordings/ActiveRecordingsTable.test.tsx | 19 ++- .../ArchivedRecordingsTable.test.tsx | 31 ++++- .../Filters/DurationFilter.test.tsx | 9 +- .../Recordings/Filters/LabelFilter.test.tsx | 20 ++- .../Recordings/Filters/NameFilter.test.tsx | 9 +- .../Filters/RecordingStateFilter.test.tsx | 9 +- src/test/Recordings/RecordingFilters.test.tsx | 14 +- .../Recordings/RecordingLabelsPanel.test.tsx | 9 +- src/test/Recordings/Recordings.test.tsx | 5 +- src/test/Rules/CreateRule.test.tsx | 5 +- .../Credentials/StoreCredentials.test.tsx | 18 ++- src/test/TargetView/TargetSelect.test.tsx | 12 +- 62 files changed, 541 insertions(+), 321 deletions(-) delete mode 100644 src/app/RecordingMetadata/types.tsx diff --git a/src/app/Archives/AllTargetsArchivedRecordingsTable.tsx b/src/app/Archives/AllTargetsArchivedRecordingsTable.tsx index 32a7a16b8..a52104a5e 100644 --- a/src/app/Archives/AllTargetsArchivedRecordingsTable.tsx +++ b/src/app/Archives/AllTargetsArchivedRecordingsTable.tsx @@ -114,6 +114,11 @@ export const AllTargetsArchivedRecordingsTable: React.FC = ({ onClose, const [uploading, setUploading] = React.useState(false); const [numOfFiles, setNumOfFiles] = React.useState(0); const [allOks, setAllOks] = React.useState(false); - const [labels, setLabels] = React.useState([] as RecordingLabel[]); + const [labels, setLabels] = React.useState([] as KeyValue[]); const [valid, setValid] = React.useState(ValidatedOptions.success); const getFormattedLabels = React.useCallback(() => { @@ -66,7 +66,7 @@ export const ArchiveUploadModal: React.FC = ({ onClose, const reset = React.useCallback(() => { setUploading(false); - setLabels([] as RecordingLabel[]); + setLabels([] as KeyValue[]); setValid(ValidatedOptions.success); setNumOfFiles(0); }, [setUploading, setLabels, setValid, setNumOfFiles]); diff --git a/src/app/Archives/Archives.tsx b/src/app/Archives/Archives.tsx index dfd9d9e23..b81e6954a 100644 --- a/src/app/Archives/Archives.tsx +++ b/src/app/Archives/Archives.tsx @@ -36,6 +36,11 @@ import { AllTargetsArchivedRecordingsTable } from './AllTargetsArchivedRecording export const uploadAsTarget: Target = { connectUrl: UPLOADS_SUBDIRECTORY, alias: '', + labels: [], + annotations: { + cryostat: [], + platform: [], + }, }; enum ArchiveTab { diff --git a/src/app/Archives/utils.tsx b/src/app/Archives/utils.tsx index 377347f90..b69d72bf6 100644 --- a/src/app/Archives/utils.tsx +++ b/src/app/Archives/utils.tsx @@ -34,5 +34,10 @@ export const getTargetFromDirectory = (dir: RecordingDirectory): Target => { return { connectUrl: dir.connectUrl, alias: dir.jvmId, + labels: [], + annotations: { + cryostat: [], + platform: [], + }, }; }; diff --git a/src/app/CreateRecording/CustomRecordingForm.tsx b/src/app/CreateRecording/CustomRecordingForm.tsx index 6b93cbc65..ee34aa0ea 100644 --- a/src/app/CreateRecording/CustomRecordingForm.tsx +++ b/src/app/CreateRecording/CustomRecordingForm.tsx @@ -17,10 +17,15 @@ import { DurationPicker } from '@app/DurationPicker/DurationPicker'; import { ErrorView } from '@app/ErrorView/ErrorView'; import { authFailMessage, isAuthFail } from '@app/ErrorView/types'; import { RecordingLabelFields } from '@app/RecordingMetadata/RecordingLabelFields'; -import { RecordingLabel } from '@app/RecordingMetadata/types'; import { SelectTemplateSelectorForm } from '@app/Shared/Components/SelectTemplateSelectorForm'; import { LoadingProps } from '@app/Shared/Components/types'; -import { EventTemplate, RecordingAttributes, AdvancedRecordingOptions, Target } from '@app/Shared/Services/api.types'; +import { + EventTemplate, + RecordingAttributes, + AdvancedRecordingOptions, + Target, + KeyValue, +} from '@app/Shared/Services/api.types'; import { isTargetAgentHttp } from '@app/Shared/Services/api.utils'; import { NotificationsContext } from '@app/Shared/Services/Notifications.service'; import { ServiceContext } from '@app/Shared/Services/Services'; @@ -150,18 +155,6 @@ export const CustomRecordingForm: React.FC = () => { return str; }, [formData]); - const getFormattedLabels = React.useCallback(() => { - const obj = {}; - - formData.labels.forEach((l) => { - if (l.key && l.value) { - obj[l.key] = l.value; - } - }); - - return obj; - }, [formData]); - const handleRecordingNameChange = React.useCallback( (name: string) => setFormData((old) => ({ @@ -198,7 +191,7 @@ export const CustomRecordingForm: React.FC = () => { ); const handleLabelsChange = React.useCallback( - (labels: RecordingLabel[]) => { + (labels: KeyValue[]) => { setFormData((old) => ({ ...old, labels })); }, [setFormData], @@ -266,10 +259,10 @@ export const CustomRecordingForm: React.FC = () => { maxAge: toDisk ? (continuous ? maxAge * maxAgeUnit : undefined) : undefined, maxSize: toDisk ? maxSize * maxSizeUnit : undefined, }, - metadata: { labels: getFormattedLabels() }, + metadata: { labels: formData.labels }, }; handleCreateRecording(recordingAttributes); - }, [eventSpecifierString, getFormattedLabels, formData, notifications, handleCreateRecording]); + }, [eventSpecifierString, formData, notifications, handleCreateRecording]); const refreshFormOptions = React.useCallback( (target: Target) => { diff --git a/src/app/CreateRecording/types.ts b/src/app/CreateRecording/types.ts index 1e5766949..e11288c17 100644 --- a/src/app/CreateRecording/types.ts +++ b/src/app/CreateRecording/types.ts @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { RecordingLabel } from '@app/RecordingMetadata/types'; -import { EventTemplate } from '@app/Shared/Services/api.types'; +import { EventTemplate, KeyValue } from '@app/Shared/Services/api.types'; import { ValidatedOptions } from '@patternfly/react-core'; export type EventTemplateIdentifier = Pick; @@ -22,7 +21,7 @@ export type EventTemplateIdentifier = Pick; interface _FormBaseData { name: string; template?: EventTemplateIdentifier; - labels: RecordingLabel[]; + labels: KeyValue[]; continuous: boolean; archiveOnStop: boolean; restart: boolean; diff --git a/src/app/Dashboard/Charts/mbean/MBeanMetricsChartController.tsx b/src/app/Dashboard/Charts/mbean/MBeanMetricsChartController.tsx index 151a9398f..142831c4b 100644 --- a/src/app/Dashboard/Charts/mbean/MBeanMetricsChartController.tsx +++ b/src/app/Dashboard/Charts/mbean/MBeanMetricsChartController.tsx @@ -147,6 +147,9 @@ export class MBeanMetricsChartController { l += '}'; q.push(l); }); + if (q.length === 0) { + return of({}); + } return this._api.getTargetMBeanMetrics(target, q); } } diff --git a/src/app/Dashboard/JvmDetails/JvmDetailsCard.tsx b/src/app/Dashboard/JvmDetails/JvmDetailsCard.tsx index 0adc1ebc4..1bd73c01c 100644 --- a/src/app/Dashboard/JvmDetails/JvmDetailsCard.tsx +++ b/src/app/Dashboard/JvmDetails/JvmDetailsCard.tsx @@ -48,7 +48,7 @@ export const JvmDetailsCard: DashboardCardFC = (props) => { name: target.alias, target, nodeType: NodeType.JVM, - labels: {}, + labels: [], }), }; }, [target]); diff --git a/src/app/RecordingMetadata/BulkEditLabels.tsx b/src/app/RecordingMetadata/BulkEditLabels.tsx index e47f92a3f..b88a78a73 100644 --- a/src/app/RecordingMetadata/BulkEditLabels.tsx +++ b/src/app/RecordingMetadata/BulkEditLabels.tsx @@ -24,6 +24,7 @@ import { UPLOADS_SUBDIRECTORY, NotificationCategory, Target, + KeyValue, } from '@app/Shared/Services/api.types'; import { ServiceContext } from '@app/Shared/Services/Services'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; @@ -33,8 +34,7 @@ import { HelpIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { combineLatest, concatMap, filter, first, forkJoin, map, Observable, of } from 'rxjs'; import { RecordingLabelFields } from './RecordingLabelFields'; -import { RecordingLabel } from './types'; -import { includesLabel, parseLabels } from './utils'; +import { includesLabel } from './utils'; export interface BulkEditLabelsProps { isTargetRecording: boolean; @@ -54,8 +54,8 @@ export const BulkEditLabels: React.FC = ({ const context = React.useContext(ServiceContext); const [recordings, setRecordings] = React.useState([] as Recording[]); const [editing, setEditing] = React.useState(false); - const [commonLabels, setCommonLabels] = React.useState([] as RecordingLabel[]); - const [savedCommonLabels, setSavedCommonLabels] = React.useState([] as RecordingLabel[]); + const [commonLabels, setCommonLabels] = React.useState([] as KeyValue[]); + const [savedCommonLabels, setSavedCommonLabels] = React.useState([] as KeyValue[]); const [valid, setValid] = React.useState(ValidatedOptions.default); const [loading, setLoading] = React.useState(false); const addSubscription = useSubscriptions(); @@ -78,7 +78,7 @@ export const BulkEditLabels: React.FC = ({ recordings.forEach((r: Recording) => { const idx = getIdxFromRecording(r); if (checkedIndices.includes(idx)) { - let updatedLabels = [...parseLabels(r.metadata.labels), ...commonLabels]; + let updatedLabels = [...r.metadata.labels, ...commonLabels]; updatedLabels = updatedLabels.filter((label) => { return !includesLabel(toDelete, label); }); @@ -124,13 +124,13 @@ export const BulkEditLabels: React.FC = ({ }, [setEditing, setCommonLabels, savedCommonLabels]); const updateCommonLabels = React.useCallback( - (setLabels: (l: RecordingLabel[]) => void) => { - const allRecordingLabels = [] as RecordingLabel[][]; + (setLabels: (l: KeyValue[]) => void) => { + const allRecordingLabels = [] as KeyValue[][]; recordings.forEach((r: Recording) => { const idx = getIdxFromRecording(r); if (checkedIndices.includes(idx)) { - allRecordingLabels.push(parseLabels(r.metadata.labels)); + allRecordingLabels.push(r.metadata.labels); } }); diff --git a/src/app/RecordingMetadata/ClickableLabel.tsx b/src/app/RecordingMetadata/ClickableLabel.tsx index f49e4210f..72113c72f 100644 --- a/src/app/RecordingMetadata/ClickableLabel.tsx +++ b/src/app/RecordingMetadata/ClickableLabel.tsx @@ -14,14 +14,14 @@ * limitations under the License. */ +import { KeyValue } from '@app/Shared/Services/api.types'; import { Label } from '@patternfly/react-core'; import * as React from 'react'; -import { RecordingLabel } from './types'; export interface ClickableLabelCellProps { - label: RecordingLabel; + label: KeyValue; isSelected: boolean; - onLabelClick: (label: RecordingLabel) => void; + onLabelClick: (label: KeyValue) => void; } export const ClickableLabel: React.FC = ({ label, isSelected, onLabelClick }) => { diff --git a/src/app/RecordingMetadata/LabelCell.tsx b/src/app/RecordingMetadata/LabelCell.tsx index dab08476d..7e5a2a42a 100644 --- a/src/app/RecordingMetadata/LabelCell.tsx +++ b/src/app/RecordingMetadata/LabelCell.tsx @@ -15,15 +15,15 @@ */ import { UpdateFilterOptions } from '@app/Shared/Redux/Filters/Common'; +import { KeyValue } from '@app/Shared/Services/api.types'; import { Label, Text } from '@patternfly/react-core'; import * as React from 'react'; import { ClickableLabel } from './ClickableLabel'; -import { RecordingLabel } from './types'; import { getLabelDisplay } from './utils'; export interface LabelCellProps { target: string; - labels: RecordingLabel[]; + labels: KeyValue[]; // If undefined, labels are not clickable (i.e. display only) and only displayed in grey. clickableOptions?: { labelFilters: string[]; @@ -33,7 +33,7 @@ export interface LabelCellProps { export const LabelCell: React.FC = ({ target, labels, clickableOptions }) => { const isLabelSelected = React.useCallback( - (label: RecordingLabel) => { + (label: KeyValue) => { if (clickableOptions) { const labelFilterSet = new Set(clickableOptions.labelFilters); return labelFilterSet.has(getLabelDisplay(label)); @@ -44,11 +44,11 @@ export const LabelCell: React.FC = ({ target, labels, clickableO ); const getLabelColor = React.useCallback( - (label: RecordingLabel) => (isLabelSelected(label) ? 'blue' : 'grey'), + (label: KeyValue) => (isLabelSelected(label) ? 'blue' : 'grey'), [isLabelSelected], ); const onLabelSelectToggle = React.useCallback( - (clickedLabel: RecordingLabel) => { + (clickedLabel: KeyValue) => { if (clickableOptions) { clickableOptions.updateFilters(target, { filterKey: 'Label', diff --git a/src/app/RecordingMetadata/RecordingLabelFields.tsx b/src/app/RecordingMetadata/RecordingLabelFields.tsx index f891bb238..bb241e0c5 100644 --- a/src/app/RecordingMetadata/RecordingLabelFields.tsx +++ b/src/app/RecordingMetadata/RecordingLabelFields.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ import { LoadingView } from '@app/Shared/Components/LoadingView'; +import { KeyValue } from '@app/Shared/Services/api.types'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { portalRoot } from '@app/utils/utils'; import { @@ -34,12 +35,11 @@ import { CloseIcon, ExclamationCircleIcon, FileIcon, PlusCircleIcon, UploadIcon import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { catchError, Observable, of, zip } from 'rxjs'; -import { RecordingLabel } from './types'; import { matchesLabelSyntax, getValidatedOption, LabelPattern, parseLabelsFromFile } from './utils'; export interface RecordingLabelFieldsProps { - labels: RecordingLabel[]; - setLabels: (labels: RecordingLabel[]) => void; + labels: KeyValue[]; + setLabels: (labels: KeyValue[]) => void; setValid: (isValid: ValidatedOptions) => void; isUploadable?: boolean; isDisabled?: boolean; @@ -78,7 +78,7 @@ export const RecordingLabelFields: React.FC = ({ ); const handleAddLabelButtonClick = React.useCallback(() => { - setLabels([...labels, { key: '', value: '' } as RecordingLabel]); + setLabels([...labels, { key: '', value: '' } as KeyValue]); }, [labels, setLabels]); const handleDeleteLabelButtonClick = React.useCallback( @@ -91,7 +91,7 @@ export const RecordingLabelFields: React.FC = ({ ); const isDuplicateKey = React.useCallback( - (key: string, labels: RecordingLabel[]) => labels.filter((label) => label.key === key).length > 1, + (key: string, labels: KeyValue[]) => labels.filter((label) => label.key === key).length > 1, [], ); @@ -127,7 +127,7 @@ export const RecordingLabelFields: React.FC = ({ (e: React.ChangeEvent) => { const files = e.target.files; if (files && files.length) { - const tasks: Observable[] = []; + const tasks: Observable[] = []; setLoading(true); for (const labelFile of Array.from(files)) { tasks.push( @@ -140,7 +140,7 @@ export const RecordingLabelFields: React.FC = ({ ); } addSubscription( - zip(tasks).subscribe((labelArrays: RecordingLabel[][]) => { + zip(tasks).subscribe((labelArrays: KeyValue[][]) => { setLoading(false); const newLabels = labelArrays.reduce((acc, next) => acc.concat(next), []); setLabels([...labels, ...newLabels]); diff --git a/src/app/RecordingMetadata/types.tsx b/src/app/RecordingMetadata/types.tsx deleted file mode 100644 index 810222ff1..000000000 --- a/src/app/RecordingMetadata/types.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright The Cryostat Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export interface RecordingLabel { - key: string; - value: string; -} diff --git a/src/app/RecordingMetadata/utils.ts b/src/app/RecordingMetadata/utils.ts index a5b8c3b17..d66f8cf2b 100644 --- a/src/app/RecordingMetadata/utils.ts +++ b/src/app/RecordingMetadata/utils.ts @@ -14,33 +14,25 @@ * limitations under the License. */ +import { KeyValue } from '@app/Shared/Services/api.types'; import { ValidatedOptions } from '@patternfly/react-core'; import { Observable, from } from 'rxjs'; -import { RecordingLabel } from './types'; -export const parseLabels = (jsonLabels?: { [key: string]: string }) => { - if (!jsonLabels) return []; - - return Object.entries(jsonLabels).map(([k, v]) => { - return { key: k, value: v } as RecordingLabel; - }); -}; - -export const isEqualLabel = (a: RecordingLabel, b: RecordingLabel) => { +export const isEqualLabel = (a: KeyValue, b: KeyValue) => { return a.key === b.key && a.value === b.value; }; -export const includesLabel = (arr: RecordingLabel[], searchLabel: RecordingLabel) => { +export const includesLabel = (arr: KeyValue[], searchLabel: KeyValue) => { return arr.some((l) => isEqualLabel(searchLabel, l)); }; -export const parseLabelsFromFile = (file: File): Observable => { +export const parseLabelsFromFile = (file: File): Observable => { return from( file .text() .then(JSON.parse) .then((obj) => { - const labels: RecordingLabel[] = []; + const labels: KeyValue[] = []; const labelObj = obj['labels']; if (labelObj) { Object.keys(labelObj).forEach((key) => { @@ -56,7 +48,7 @@ export const parseLabelsFromFile = (file: File): Observable => ); }; -export const getLabelDisplay = (label: RecordingLabel) => `${label.key}:${label.value}`; +export const getLabelDisplay = (label: KeyValue) => `${label.key}:${label.value}`; export const LabelPattern = /^\S+$/; @@ -64,6 +56,6 @@ export const getValidatedOption = (isValid: boolean) => { return isValid ? ValidatedOptions.success : ValidatedOptions.error; }; -export const matchesLabelSyntax = (l: RecordingLabel) => { +export const matchesLabelSyntax = (l: KeyValue) => { return l && LabelPattern.test(l.key) && LabelPattern.test(l.value); }; diff --git a/src/app/Recordings/ActiveRecordingsTable.tsx b/src/app/Recordings/ActiveRecordingsTable.tsx index eb473920e..5a6369d47 100644 --- a/src/app/Recordings/ActiveRecordingsTable.tsx +++ b/src/app/Recordings/ActiveRecordingsTable.tsx @@ -20,7 +20,6 @@ import { } from '@app/Dashboard/AutomatedAnalysis/ClickableAutomatedAnalysisLabel'; import { authFailMessage } from '@app/ErrorView/types'; import { DeleteOrDisableWarningType } from '@app/Modal/types'; -import { parseLabels } from '@app/RecordingMetadata/utils'; import { LoadingProps } from '@app/Shared/Components/types'; import { UpdateFilterOptions } from '@app/Shared/Redux/Filters/Common'; import { emptyActiveRecordingFilters, TargetRecordingFilters } from '@app/Shared/Redux/Filters/RecordingFilterSlice'; @@ -861,10 +860,6 @@ export const ActiveRecordingRow: React.FC = ({ return expandedRows.includes(expandedRowId); }, [expandedRowId, expandedRows]); - const parsedLabels = React.useMemo(() => { - return parseLabels(recording.metadata.labels); - }, [recording]); - const handleToggle = React.useCallback(() => toggleExpanded(expandedRowId), [expandedRowId, toggleExpanded]); const handleCheck = React.useCallback( @@ -974,7 +969,7 @@ export const ActiveRecordingRow: React.FC = ({ updateFilters: updateFilters, labelFilters: labelFilters, }} - labels={parsedLabels} + labels={recording.metadata.labels} /> = ({ recording, labelFilters, currentSelectedTargetURL, - parsedLabels, context.api, handleCheck, handleToggle, diff --git a/src/app/Recordings/ArchivedRecordingsTable.tsx b/src/app/Recordings/ArchivedRecordingsTable.tsx index c2f9a470d..3d23450f8 100644 --- a/src/app/Recordings/ArchivedRecordingsTable.tsx +++ b/src/app/Recordings/ArchivedRecordingsTable.tsx @@ -21,7 +21,6 @@ import { } from '@app/Dashboard/AutomatedAnalysis/ClickableAutomatedAnalysisLabel'; import { DeleteWarningModal } from '@app/Modal/DeleteWarningModal'; import { DeleteOrDisableWarningType } from '@app/Modal/types'; -import { parseLabels } from '@app/RecordingMetadata/utils'; import { LoadingProps } from '@app/Shared/Components/types'; import { UpdateFilterOptions } from '@app/Shared/Redux/Filters/Common'; import { emptyArchivedRecordingFilters, TargetRecordingFilters } from '@app/Shared/Redux/Filters/RecordingFilterSlice'; @@ -772,10 +771,6 @@ export const ArchivedRecordingRow: React.FC = ({ const [loadingAnalysis, setLoadingAnalysis] = React.useState(false); const [analyses, setAnalyses] = React.useState([]); - const parsedLabels = React.useMemo(() => { - return parseLabels(recording.metadata.labels); - }, [recording]); - const expandedRowId = React.useMemo(() => `archived-table-row-${index}-exp`, [index]); const handleToggle = React.useCallback(() => { @@ -849,7 +844,7 @@ export const ArchivedRecordingRow: React.FC = ({ updateFilters: updateFilters, labelFilters: labelFilters, }} - labels={parsedLabels} + labels={recording.metadata.labels} /> @@ -874,7 +869,6 @@ export const ArchivedRecordingRow: React.FC = ({ index, checkedIndices, isExpanded, - parsedLabels, labelFilters, currentSelectedTargetURL, sourceTarget, diff --git a/src/app/Recordings/Filters/LabelFilter.tsx b/src/app/Recordings/Filters/LabelFilter.tsx index 2db3c30bc..67fc3a7e2 100644 --- a/src/app/Recordings/Filters/LabelFilter.tsx +++ b/src/app/Recordings/Filters/LabelFilter.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { parseLabels, getLabelDisplay } from '@app/RecordingMetadata/utils'; +import { getLabelDisplay } from '@app/RecordingMetadata/utils'; import { Recording } from '@app/Shared/Services/api.types'; import { Label, Select, SelectOption, SelectVariant } from '@patternfly/react-core'; import * as React from 'react'; @@ -42,7 +42,7 @@ export const LabelFilter: React.FC = ({ recordings, filteredLa const labels = new Set(); recordings.forEach((r) => { if (!r || !r.metadata || !r.metadata.labels) return; - parseLabels(r.metadata.labels).map((label) => labels.add(getLabelDisplay(label))); + r.metadata.labels.map((label) => labels.add(getLabelDisplay(label))); }); return Array.from(labels) .filter((l) => !filteredLabels.includes(l)) diff --git a/src/app/Shared/Components/MatchExpression/MatchExpressionHint.tsx b/src/app/Shared/Components/MatchExpression/MatchExpressionHint.tsx index 5efff8e5b..d219fbab2 100644 --- a/src/app/Shared/Components/MatchExpression/MatchExpressionHint.tsx +++ b/src/app/Shared/Components/MatchExpression/MatchExpressionHint.tsx @@ -15,6 +15,7 @@ */ import { Target } from '@app/Shared/Services/api.types'; +import { getAnnotation } from '@app/utils/utils'; import { ClipboardCopyButton, CodeBlock, CodeBlockAction, CodeBlockCode } from '@patternfly/react-core'; import * as React from 'react'; @@ -30,7 +31,10 @@ export const MatchExpressionHint: React.FC = ({ target if (!target || !target.alias || !target.connectUrl) { body = 'true'; } else { - body = `target.alias == '${target.alias}' || target.annotations.cryostat['PORT'] == ${target.annotations?.cryostat['PORT']}`; + body = `target.alias == '${target.alias}' || target.annotations.cryostat['PORT'] == ${getAnnotation( + target.annotations.cryostat, + 'PORT', + )}`; } body = JSON.stringify(body, null, 2); body = body.substring(1, body.length - 1); diff --git a/src/app/Shared/Components/MatchExpression/utils.tsx b/src/app/Shared/Components/MatchExpression/utils.tsx index 0ff572697..e1590757a 100644 --- a/src/app/Shared/Components/MatchExpression/utils.tsx +++ b/src/app/Shared/Components/MatchExpression/utils.tsx @@ -72,7 +72,7 @@ export const createTargetNode = (target: Target): TargetNode => { id: hashCode(JSON.stringify(target)), name: target.connectUrl, nodeType: NodeType.TARGET, - labels: {}, + labels: [], target: target, }; }; diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 0714a8e2f..2bded2658 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -15,7 +15,6 @@ */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { LayoutTemplate, SerialLayoutTemplate } from '@app/Dashboard/types'; -import { RecordingLabel } from '@app/RecordingMetadata/types'; import { createBlobURL } from '@app/utils/utils'; import { ValidatedOptions } from '@patternfly/react-core'; import { EMPTY, forkJoin, from, Observable, ObservableInput, of, ReplaySubject, shareReplay, throwError } from 'rxjs'; @@ -59,6 +58,8 @@ import { XMLHttpError, XMLHttpRequestConfig, XMLHttpResponse, + KeyValue, + CustomTargetStub, } from './api.types'; import { isHttpError, includesTarget, isHttpOk, isXMLHttpError } from './api.utils'; import { LoginService } from './Login.service'; @@ -170,7 +171,7 @@ export class ApiService { } createTarget( - target: Target, + target: CustomTargetStub, credentials?: { username?: string; password?: string }, storeCredentials = false, dryrun = false, @@ -522,7 +523,7 @@ export class ApiService { ); } - transformAndStringifyToRawLabels(labels: RecordingLabel[]) { + transformAndStringifyToRawLabels(labels: KeyValue[]) { const rawLabels = {}; for (const label of labels) { rawLabels[label.key] = label.value; @@ -530,7 +531,7 @@ export class ApiService { return JSON.stringify(rawLabels); } - postRecordingMetadataFromPath(jvmId: string, recordingName: string, labels: RecordingLabel[]): Observable { + postRecordingMetadataFromPath(jvmId: string, recordingName: string, labels: KeyValue[]): Observable { return this.sendRequest( 'beta', `fs/recordings/${encodeURIComponent(jvmId)}/${encodeURIComponent(recordingName)}/metadata/labels`, @@ -888,7 +889,7 @@ export class ApiService { ); } - postRecordingMetadata(recordingName: string, labels: RecordingLabel[]): Observable { + postRecordingMetadata(recordingName: string, labels: KeyValue[]): Observable { return this.target.target().pipe( filter((target: Target) => !!target), first(), @@ -917,7 +918,7 @@ export class ApiService { ); } - postUploadedRecordingMetadata(recordingName: string, labels: RecordingLabel[]): Observable { + postUploadedRecordingMetadata(recordingName: string, labels: KeyValue[]): Observable { return this.graphql( ` query PostUploadedRecordingMetadata($connectUrl: String, $recordingName: String, $labels: String){ @@ -935,7 +936,7 @@ export class ApiService { ).pipe(map((v) => v.data.archivedRecordings.data as ArchivedRecording[])); } - postTargetRecordingMetadata(recordingName: string, labels: RecordingLabel[]): Observable { + postTargetRecordingMetadata(recordingName: string, labels: KeyValue[]): Observable { return this.target.target().pipe( filter((target) => !!target), first(), @@ -1206,8 +1207,10 @@ export class ApiService { ` query MBeanMXMetricsForTarget($connectUrl: String) { targetNodes(filter: { name: $connectUrl }) { - mbeanMetrics { - ${queries.join('\n')} + target { + mbeanMetrics { + ${queries.join('\n')} + } } } }`, @@ -1218,7 +1221,7 @@ export class ApiService { if (!nodes || nodes.length === 0) { return {}; } - return nodes[0]?.mbeanMetrics; + return nodes[0]?.target.mbeanMetrics; }), catchError((_) => of({})), ); @@ -1306,7 +1309,7 @@ export class ApiService { anchor.remove(); } - stringifyRecordingLabels(labels: RecordingLabel[]): string { + stringifyRecordingLabels(labels: KeyValue[]): string { return JSON.stringify(labels).replace(/"([^"]+)":/g, '$1:'); } diff --git a/src/app/Shared/Services/api.types.ts b/src/app/Shared/Services/api.types.ts index 86cd9f6d7..2bd3b0ba3 100644 --- a/src/app/Shared/Services/api.types.ts +++ b/src/app/Shared/Services/api.types.ts @@ -23,12 +23,13 @@ export type ApiVersion = 'v1' | 'v2' | 'v2.1' | 'v2.2' | 'v2.3' | 'v2.4' | 'beta // Common Resources // ====================================== export interface KeyValue { - readonly [key: string]: string; + key: string; + value: string; } export interface Metadata { - labels: KeyValue; - annotations?: KeyValue; + labels: KeyValue[]; + annotations?: KeyValue[]; } export interface ApiV2Response { @@ -87,6 +88,8 @@ export class XMLHttpError extends Error { } } +export type CustomTargetStub = Omit; + // ====================================== // Health Resources // ====================================== @@ -122,7 +125,7 @@ export interface AuthV2Response extends ApiV2Response { // ====================================== // MBean metric resources // ====================================== -export interface MemoryUsage { +export interface MemoryUtilization { init: number; used: number; committed: number; @@ -147,8 +150,8 @@ export interface MBeanMetrics { totalSwapSpaceSize?: number; }; memory?: { - heapMemoryUsage?: MemoryUsage; - nonHeapMemoryUsage?: MemoryUsage; + heapMemoryUsage?: MemoryUtilization; + nonHeapMemoryUsage?: MemoryUtilization; heapMemoryUsagePercent?: number; }; runtime?: { @@ -161,7 +164,7 @@ export interface MBeanMetrics { specName?: string; specVendor?: string; startTime?: number; - systemProperties?: object; + systemProperties?: KeyValue[]; uptime?: number; vmName?: string; vmVendor?: string; @@ -173,7 +176,9 @@ export interface MBeanMetrics { export interface MBeanMetricsResponse { data: { targetNodes: { - mbeanMetrics: MBeanMetrics; + target: { + mbeanMetrics: MBeanMetrics; + }; }[]; }; } @@ -444,10 +449,10 @@ export interface Target { jvmId?: string; // present in responses, but we do not need to provide it in requests connectUrl: string; alias: string; - labels?: KeyValue; - annotations?: { - cryostat: KeyValue; - platform: KeyValue; + labels: KeyValue[]; + annotations: { + cryostat: KeyValue[]; + platform: KeyValue[]; }; } @@ -483,7 +488,7 @@ interface _AbstractNode { readonly id: number; readonly name: string; readonly nodeType: NodeType; - readonly labels: KeyValue; + readonly labels: KeyValue[]; } export interface EnvironmentNode extends _AbstractNode { diff --git a/src/app/Shared/Services/api.utils.ts b/src/app/Shared/Services/api.utils.ts index 5eb3caf73..af376af02 100644 --- a/src/app/Shared/Services/api.utils.ts +++ b/src/app/Shared/Services/api.utils.ts @@ -112,7 +112,7 @@ export const DEFAULT_EMPTY_UNIVERSE: EnvironmentNode = { id: 0, name: 'Universe', nodeType: NodeType.UNIVERSE, - labels: {}, + labels: [], children: [], }; @@ -140,7 +140,7 @@ export const getTargetRepresentation = (t: Target) => export const isTargetAgentHttp = (t: Target) => t.connectUrl.startsWith('http'); export const isTargetNode = (node: EnvironmentNode | TargetNode): node is TargetNode => { - return node['target'] !== undefined && node['children'] === undefined; + return node['target'] !== undefined; }; export const getAllLeaves = (root: EnvironmentNode | TargetNode): TargetNode[] => { diff --git a/src/app/Shared/Services/service.utils.ts b/src/app/Shared/Services/service.utils.ts index a43f0171d..40fa88ce1 100644 --- a/src/app/Shared/Services/service.utils.ts +++ b/src/app/Shared/Services/service.utils.ts @@ -48,9 +48,12 @@ export const automatedAnalysisConfigToRecordingAttributes = ( maxSize: config.maxSize, }, metadata: { - labels: { - origin: automatedAnalysisRecordingName, - }, + labels: [ + { + key: 'origin', + value: automatedAnalysisRecordingName, + }, + ], }, }; }; diff --git a/src/app/TargetView/TargetContextSelector.tsx b/src/app/TargetView/TargetContextSelector.tsx index 4ab01089c..b27ac69a6 100644 --- a/src/app/TargetView/TargetContextSelector.tsx +++ b/src/app/TargetView/TargetContextSelector.tsx @@ -19,6 +19,7 @@ import { isEqualTarget, getTargetRepresentation } from '@app/Shared/Services/api import { ServiceContext } from '@app/Shared/Services/Services'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { getFromLocalStorage, removeFromLocalStorage, saveToLocalStorage } from '@app/utils/LocalStorage'; +import { getAnnotation } from '@app/utils/utils'; import { Button, Divider, Select, SelectGroup, SelectOption, SelectVariant } from '@patternfly/react-core'; import * as React from 'react'; import { Link } from 'react-router-dom'; @@ -113,13 +114,13 @@ export const TargetContextSelector: React.FC = ({ cl const favSet = new Set(favorites); const groupNames = new Set(); - targets.forEach((t) => groupNames.add(t.annotations?.cryostat['REALM'] || 'Others')); + targets.forEach((t) => groupNames.add(getAnnotation(t.annotations.cryostat, 'REALM') || 'Others')); const options = Array.from(groupNames) .map((name) => ( {targets - .filter((t) => (t.annotations?.cryostat['REALM'] || 'Others') === name) + .filter((t) => getAnnotation(t.annotations.cryostat, 'REALM') === name) .map((t: Target) => ( = ({ onSelect, simple, .. let options = [] as JSX.Element[]; const groupNames = new Set(); - targets.forEach((t) => groupNames.add(t.annotations?.cryostat['REALM'] || 'Others')); + targets.forEach((t) => groupNames.add(getAnnotation(t.annotations.cryostat, 'REALM') || 'Others')); options = options.concat( Array.from(groupNames) .map((name) => ( {targets - .filter((t) => (t.annotations?.cryostat['REALM'] || 'Others') === name) + .filter((t) => (getAnnotation(t.annotations.cryostat, 'REALM') || 'Others') === name) .map((t: Target) => ( {!t.alias || t.alias === t.connectUrl ? `${t.connectUrl}` : `${t.alias} (${t.connectUrl})`} diff --git a/src/app/Topology/Actions/CreateTarget.tsx b/src/app/Topology/Actions/CreateTarget.tsx index e4e8645e0..cc62f41fd 100644 --- a/src/app/Topology/Actions/CreateTarget.tsx +++ b/src/app/Topology/Actions/CreateTarget.tsx @@ -25,7 +25,7 @@ import { ServiceContext } from '@app/Shared/Services/Services'; import '@app/Topology/styles/base.css'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { getFromLocalStorage } from '@app/utils/LocalStorage'; -import { portalRoot } from '@app/utils/utils'; +import { getAnnotation, portalRoot } from '@app/utils/utils'; import { Accordion, AccordionContent, @@ -241,7 +241,7 @@ export const CreateTarget: React.FC = ({ prefilled }) => { React.useEffect(() => { addSubscription( context.targets.targets().subscribe((ts) => { - const discoveredTargets = ts.filter((t) => t.annotations?.cryostat['REALM'] !== 'Custom Targets'); + const discoveredTargets = ts.filter((t) => getAnnotation(t.annotations.cryostat, 'REALM') !== 'Custom Targets'); if (discoveredTargets.length) { setExample(discoveredTargets[0].connectUrl); } @@ -400,7 +400,12 @@ export const CreateTarget: React.FC = ({ prefilled }) => { - + diff --git a/src/app/Topology/Entity/EntityAnnotations.tsx b/src/app/Topology/Entity/EntityAnnotations.tsx index 916762317..416804732 100644 --- a/src/app/Topology/Entity/EntityAnnotations.tsx +++ b/src/app/Topology/Entity/EntityAnnotations.tsx @@ -16,8 +16,9 @@ import { Label, LabelGroup } from '@patternfly/react-core'; import * as React from 'react'; import { EmptyText } from '../../Shared/Components/EmptyText'; +import { Annotations } from './types'; -export const EntityAnnotations: React.FC<{ annotations?: object; maxDisplay?: number }> = ({ +export const EntityAnnotations: React.FC<{ annotations?: Annotations; maxDisplay?: number }> = ({ annotations, maxDisplay, ...props @@ -26,7 +27,7 @@ export const EntityAnnotations: React.FC<{ annotations?: object; maxDisplay?: nu return annotations ? Object.keys(annotations).map((groupK) => ({ groupLabel: groupK, - annotations: Object.keys(annotations[groupK]).map((k) => `${k}=${annotations[groupK][k]}`), + annotations: annotations[groupK].map((kv) => `${kv.key}=${kv.value}`), })) : []; }, [annotations]); diff --git a/src/app/Topology/Entity/EntityDetails.tsx b/src/app/Topology/Entity/EntityDetails.tsx index 8fd419964..01814c76e 100644 --- a/src/app/Topology/Entity/EntityDetails.tsx +++ b/src/app/Topology/Entity/EntityDetails.tsx @@ -271,23 +271,28 @@ const MBeanDetails: React.FC<{ ` query MBeanMXMetricsForTarget($connectUrl: String) { targetNodes(filter: { name: $connectUrl }) { - mbeanMetrics { - runtime { - startTime - vmVendor - vmVersion - classPath - libraryPath - inputArguments - systemProperties - } - os { - name - version - arch - availableProcessors - totalPhysicalMemorySize - totalSwapSpaceSize + target { + mbeanMetrics { + runtime { + startTime + vmVendor + vmVersion + classPath + libraryPath + inputArguments + systemProperties { + key + value + } + } + os { + name + version + arch + availableProcessors + totalPhysicalMemorySize + totalSwapSpaceSize + } } } } @@ -295,7 +300,7 @@ const MBeanDetails: React.FC<{ { connectUrl }, ) .pipe( - map((resp) => resp.data.targetNodes[0].mbeanMetrics || {}), + map((resp) => resp.data.targetNodes[0].target.mbeanMetrics || {}), catchError((_) => of({})), ) .subscribe(setMbeanMetrics), diff --git a/src/app/Topology/Entity/types.ts b/src/app/Topology/Entity/types.ts index 5dc1cea00..631c06436 100644 --- a/src/app/Topology/Entity/types.ts +++ b/src/app/Topology/Entity/types.ts @@ -17,6 +17,7 @@ import type { EventProbe, EventTemplate, EventType, + KeyValue, NotificationMessage, Recording, Rule, @@ -48,6 +49,11 @@ export const TargetOwnedResourceTypeAsArray = [ 'agentProbes', ] as const; +export type Annotations = { + cryostat: KeyValue[]; + platform: KeyValue[]; +}; + export const TargetRelatedResourceTypeAsArray = ['automatedRules', 'credentials'] as const; export type TargetOwnedResourceType = (typeof TargetOwnedResourceTypeAsArray)[number]; diff --git a/src/app/Topology/Entity/utils.tsx b/src/app/Topology/Entity/utils.tsx index 4df3a52ff..9e25102a1 100644 --- a/src/app/Topology/Entity/utils.tsx +++ b/src/app/Topology/Entity/utils.tsx @@ -53,7 +53,8 @@ import { import { ActiveRecDetail, Nothing } from './ResourceDetails'; import { DescriptionConfig, TargetOwnedResourceType, TargetRelatedResourceType, ResourceTypes, PatchFn } from './types'; -export const keyValueEntryTransformer = (kv: object): string[] => Object.entries(kv).map(([k, v]) => `${k}=${v}`); +export const keyValueEntryTransformer = (kv: { key: string; value: string }[]): string[] => + kv.map((k) => `${k.key}=${k.value}`); export const valuesEntryTransformer: (kv: string[] | object) => string[] = Object.values; diff --git a/src/app/utils/fakeData.ts b/src/app/utils/fakeData.ts index f1d5544fd..0075d0fa0 100644 --- a/src/app/utils/fakeData.ts +++ b/src/app/utils/fakeData.ts @@ -49,19 +49,40 @@ export const fakeTarget: Target = { jvmId: 'rpZeYNB9wM_TEnXoJvAFuR0jdcUBXZgvkXiKhjQGFvY=', connectUrl: 'service:jmx:rmi:///jndi/rmi://10-128-2-25.my-namespace.pod:9097/jmxrmi', alias: 'quarkus-test-77f556586c-25bkv', - labels: { - 'pod-template-hash': '77f556586c', - deployment: 'quarkus-test', - }, - annotations: { - cryostat: { - HOST: '10.128.2.25', - PORT: '9097', - POD_NAME: 'quarkus-test-77f556586c-25bkv', - REALM: 'KubernetesApi', - NAMESPACE: 'my-namespace', + labels: [ + { + key: 'pod-template-hash', + value: '77f556586c', }, - platform: {}, + { + key: 'deployment', + value: 'quarkus-test', + }, + ], + annotations: { + cryostat: [ + { + key: 'HOST', + value: '10.128.2.25', + }, + { + key: 'PORT', + value: '9097', + }, + { + key: 'POD_NAME', + value: 'quarkus-test-77f556586c-25bkv', + }, + { + key: 'REALM', + value: 'KubernetesApi', + }, + { + key: 'NAMESPACE', + value: 'my-namespace', + }, + ], + platform: [], }, }; @@ -72,11 +93,20 @@ export const fakeAARecording: ActiveRecording = { reportUrl: 'https://clustercryostat-sample-default.apps.ci-ln-25fg5f2-76ef8.origin-ci-int-aws.dev.rhcloud.com:443/api/v1/targets/service:jmx:rmi:%2F%2F%2Fjndi%2Frmi:%2F%2F10-128-2-27.my-namespace.pod:9097%2Fjmxrmi/reports/automated-analysis', metadata: { - labels: { - 'template.name': 'Profiling', - 'template.type': 'TARGET', - origin: 'automated-analysis', - }, + labels: [ + { + key: 'template.name', + value: 'Profiling', + }, + { + key: 'template.type', + value: 'TARGET', + }, + { + key: 'origin', + value: 'automated-analysis', + }, + ], }, startTime: 1680732807, id: 0, diff --git a/src/app/utils/utils.ts b/src/app/utils/utils.ts index a684c48fe..dd80a8811 100644 --- a/src/app/utils/utils.ts +++ b/src/app/utils/utils.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { KeyValue } from '@app/Shared/Services/api.types'; import { ISortBy, SortByDirection } from '@patternfly/react-table'; import _ from 'lodash'; import { NavigateFunction } from 'react-router-dom'; @@ -181,6 +182,18 @@ export interface TableColumn { width?: number; } +export const getAnnotation = (kv: KeyValue[], key: string, def?: string): string | undefined => { + if (!kv) { + return def; + } + for (const k of kv) { + if (k.key === key) { + return k.value; + } + } + return def; +}; + const mapper = (tableColumns: TableColumn[], index?: number) => { if (index === undefined) { return undefined; diff --git a/src/mirage/factories.ts b/src/mirage/factories.ts index 687ab86a5..939791695 100644 --- a/src/mirage/factories.ts +++ b/src/mirage/factories.ts @@ -22,8 +22,22 @@ export const targetFactory: FactoryDefinition = Factory.extend({ connectUrl: 'http://fake-target.local:1234', jvmId: '1234', annotations: { - platform: { 'io.cryostat.demo': 'this-is-not-real' }, - cryostat: { hello: 'world', REALM: 'Some Realm' }, + platform: [ + { + key: 'io.cryostat.demo', + value: 'this-is-not-real', + }, + ], + cryostat: [ + { + key: 'hello', + value: 'world', + }, + { + key: 'REALM', + value: 'Some Realm', + }, + ], }, }); diff --git a/src/mirage/index.ts b/src/mirage/index.ts index 5de3574e1..d42a8b0f1 100644 --- a/src/mirage/index.ts +++ b/src/mirage/index.ts @@ -101,10 +101,13 @@ export const startMirage = ({ environment = 'development' } = {}) => { alias: attrs.get('alias'), connectUrl: attrs.get('connectUrl'), annotations: { - platform: {}, - cryostat: { - REALM: 'Custom Targets', - }, + platform: [], + cryostat: [ + { + key: 'REALM', + value: 'Custom Targets', + }, + ], }, }); websocket.send( @@ -135,11 +138,11 @@ export const startMirage = ({ environment = 'development' } = {}) => { result: { name: 'Universe', nodeType: 'Universe', - labels: {}, + labels: [], children: realmTypes.map((r: string) => ({ name: r, nodeType: 'Realm', - labels: {}, + labels: [], id: r, children: models .filter((t) => t.annotations.cryostat['REALM'] === r) @@ -211,11 +214,17 @@ export const startMirage = ({ environment = 'development' } = {}) => { maxSize: attrs.get('maxSize') || 0, maxAge: attrs.get('maxAge') || 0, metadata: { - labels: { - ...(attrs.labels || {}), - 'template.type': 'TARGET', - 'template.name': 'Demo_Template', - }, + labels: [ + ...(attrs.labels || []), + { + key: 'template.type', + value: 'TARGET', + }, + { + key: 'template.name', + value: 'Demo_Template', + }, + ], }, }); websocket.send( @@ -630,52 +639,54 @@ export const startMirage = ({ environment = 'development' } = {}) => { data = { targetNodes: [ { - mbeanMetrics: { - thread: { - threadCount: Math.ceil(Math.random() * 5), - daemonThreadCount: Math.ceil(Math.random() * 5), - }, - os: { - arch: 'x86_64', - availableProcessors: Math.ceil(Math.random() * 8), - version: '10.0.1', - systemCpuLoad: Math.random(), - systemLoadAverage: Math.random(), - processCpuLoad: Math.random(), - totalPhysicalMemorySize: Math.ceil(Math.random() * 64), - freePhysicalMemorySize: Math.ceil(Math.random() * 64), - }, - memory: { - heapMemoryUsage: { - init: Math.ceil(Math.random() * 64), - used: Math.ceil(Math.random() * 64), - committed: Math.ceil(Math.random() * 64), - max: Math.ceil(Math.random() * 64), + target: { + mbeanMetrics: { + thread: { + threadCount: Math.ceil(Math.random() * 5), + daemonThreadCount: Math.ceil(Math.random() * 5), }, - nonHeapMemoryUsage: { - init: Math.ceil(Math.random() * 64), - used: Math.ceil(Math.random() * 64), - committed: Math.ceil(Math.random() * 64), - max: Math.ceil(Math.random() * 64), + os: { + arch: 'x86_64', + availableProcessors: Math.ceil(Math.random() * 8), + version: '10.0.1', + systemCpuLoad: Math.random(), + systemLoadAverage: Math.random(), + processCpuLoad: Math.random(), + totalPhysicalMemorySize: Math.ceil(Math.random() * 64), + freePhysicalMemorySize: Math.ceil(Math.random() * 64), + }, + memory: { + heapMemoryUsage: { + init: Math.ceil(Math.random() * 64), + used: Math.ceil(Math.random() * 64), + committed: Math.ceil(Math.random() * 64), + max: Math.ceil(Math.random() * 64), + }, + nonHeapMemoryUsage: { + init: Math.ceil(Math.random() * 64), + used: Math.ceil(Math.random() * 64), + committed: Math.ceil(Math.random() * 64), + max: Math.ceil(Math.random() * 64), + }, + heapMemoryUsagePercent: Math.random(), + }, + runtime: { + bootClassPath: '/path/to/boot/classpath', + classPath: '/path/to/classpath', + inputArguments: ['-Xmx1g', '-Djava.security.policy=...'], + libraryPath: '/path/to/library/path', + managementSpecVersion: '1.0', + name: 'Java Virtual Machine', + specName: 'Java Virtual Machine Specification', + specVendor: 'Oracle Corporation', + startTime: Date.now(), + // systemProperties: {...} + uptime: Date.now(), + vmName: 'Java HotSpot(TM) 64-Bit Server VM', + vmVendor: 'Oracle Corporation', + vmVersion: '25.131-b11', + bootClassPathSupported: true, }, - heapMemoryUsagePercent: Math.random(), - }, - runtime: { - bootClassPath: '/path/to/boot/classpath', - classPath: '/path/to/classpath', - inputArguments: ['-Xmx1g', '-Djava.security.policy=...'], - libraryPath: '/path/to/library/path', - managementSpecVersion: '1.0', - name: 'Java Virtual Machine', - specName: 'Java Virtual Machine Specification', - specVendor: 'Oracle Corporation', - startTime: Date.now(), - // systemProperties: {...} - uptime: Date.now(), - vmName: 'Java HotSpot(TM) 64-Bit Server VM', - vmVendor: 'Oracle Corporation', - vmVersion: '25.131-b11', - bootClassPathSupported: true, }, }, }, diff --git a/src/test/Agent/AgentLiveProbes.test.tsx b/src/test/Agent/AgentLiveProbes.test.tsx index eb4f9197c..b366d05f5 100644 --- a/src/test/Agent/AgentLiveProbes.test.tsx +++ b/src/test/Agent/AgentLiveProbes.test.tsx @@ -30,7 +30,13 @@ import { render, renderSnapshot } from '../utils'; const mockConnectUrl = 'service:jmx:rmi://someUrl'; const mockJvmId = 'id'; -const mockTarget = { connectUrl: mockConnectUrl, alias: 'fooTarget', jvmId: mockJvmId }; +const mockTarget = { + connectUrl: mockConnectUrl, + alias: 'fooTarget', + jvmId: mockJvmId, + labels: [], + annotations: { cryostat: [], platform: [] }, +}; const mockMessageType = { type: 'application', subtype: 'json' } as MessageType; diff --git a/src/test/Archives/AllArchivedRecordingsTable.test.tsx b/src/test/Archives/AllArchivedRecordingsTable.test.tsx index 5acbb2939..d6ddc0d33 100644 --- a/src/test/Archives/AllArchivedRecordingsTable.test.tsx +++ b/src/test/Archives/AllArchivedRecordingsTable.test.tsx @@ -43,9 +43,12 @@ const mockRecordingDeletedNotification = { }, } as NotificationMessage; -const mockRecordingLabels = { - someLabel: 'someValue', -}; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; const mockRecording: ArchivedRecording = { name: 'someRecording', diff --git a/src/test/Archives/AllTargetsArchivedRecordingsTable.test.tsx b/src/test/Archives/AllTargetsArchivedRecordingsTable.test.tsx index 1c12b36da..6af7d3070 100644 --- a/src/test/Archives/AllTargetsArchivedRecordingsTable.test.tsx +++ b/src/test/Archives/AllTargetsArchivedRecordingsTable.test.tsx @@ -26,6 +26,9 @@ const mockAlias1 = 'fooTarget1'; const mockTarget1: Target = { connectUrl: mockConnectUrl1, alias: mockAlias1, + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, }; const mockConnectUrl2 = 'service:jmx:rmi://someUrl2'; const mockAlias2 = 'fooTarget2'; @@ -36,6 +39,9 @@ const mockNewAlias = 'newTarget'; const mockNewTarget: Target = { connectUrl: mockNewConnectUrl, alias: mockNewAlias, + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, }; const mockCount1 = 1; const mockCount2 = 3; diff --git a/src/test/CreateRecording/CustomRecordingForm.test.tsx b/src/test/CreateRecording/CustomRecordingForm.test.tsx index bb28193ba..bdc185501 100644 --- a/src/test/CreateRecording/CustomRecordingForm.test.tsx +++ b/src/test/CreateRecording/CustomRecordingForm.test.tsx @@ -15,7 +15,7 @@ */ import { CustomRecordingForm } from '@app/CreateRecording/CustomRecordingForm'; import { authFailMessage } from '@app/ErrorView/types'; -import { EventTemplate, AdvancedRecordingOptions, RecordingAttributes } from '@app/Shared/Services/api.types'; +import { EventTemplate, AdvancedRecordingOptions, RecordingAttributes, Target } from '@app/Shared/Services/api.types'; import { ServiceContext, Services, defaultServices } from '@app/Shared/Services/Services'; import { TargetService } from '@app/Shared/Services/Target.service'; import { screen, cleanup, act as doAct } from '@testing-library/react'; @@ -36,7 +36,13 @@ jest.mock('react-router-dom', () => ({ })); const mockConnectUrl = 'service:jmx:rmi://someUrl'; -const mockTarget = { connectUrl: mockConnectUrl, alias: 'fooTarget' }; +const mockTarget = { + connectUrl: mockConnectUrl, + alias: 'fooTarget', + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; const mockCustomEventTemplate: EventTemplate = { name: 'someEventTemplate', @@ -118,7 +124,7 @@ describe('', () => { maxSize: 0, toDisk: true, }, - metadata: { labels: {} }, + metadata: { labels: [] }, } as RecordingAttributes); expect(mockNavigate).toHaveBeenCalledWith('..', { relative: 'path' }); }); @@ -150,7 +156,7 @@ describe('', () => { it('should show error view if failing to retrieve templates or recording options', async () => { const subj = new Subject(); const mockTargetSvc = { - target: () => of(mockTarget), + target: () => of(mockTarget as Target), authFailure: () => subj.asObservable(), } as TargetService; const services: Services = { diff --git a/src/test/CreateRecording/SnapshotRecordingForm.test.tsx b/src/test/CreateRecording/SnapshotRecordingForm.test.tsx index 8b8914bfc..1c8b6cf4f 100644 --- a/src/test/CreateRecording/SnapshotRecordingForm.test.tsx +++ b/src/test/CreateRecording/SnapshotRecordingForm.test.tsx @@ -23,7 +23,13 @@ import { of, Subject } from 'rxjs'; import { render, renderSnapshot } from '../utils'; const mockConnectUrl = 'service:jmx:rmi://someUrl'; -const mockTarget = { connectUrl: mockConnectUrl, alias: 'fooTarget' }; +const mockTarget = { + connectUrl: mockConnectUrl, + alias: 'fooTarget', + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; jest.spyOn(defaultServices.target, 'authFailure').mockReturnValue(of()); jest.spyOn(defaultServices.target, 'target').mockReturnValue(of(mockTarget)); diff --git a/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.test.tsx b/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.test.tsx index e9dcd0c10..8f6d9a313 100644 --- a/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.test.tsx +++ b/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.test.tsx @@ -40,7 +40,13 @@ jest.mock('@app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCardList', () => { }; }); -const mockTarget = { connectUrl: 'service:jmx:rmi://someUrl', alias: 'fooTarget' }; +const mockTarget = { + connectUrl: 'service:jmx:rmi://someUrl', + alias: 'fooTarget', + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; const mockEmptyCachedReport: CachedReportValue = { report: [], @@ -136,7 +142,7 @@ const mockArchivedRecording: ArchivedRecording = { name: 'someArchivedRecording', downloadUrl: '', reportUrl: '', - metadata: { labels: {} }, + metadata: { labels: [] }, size: 0, archivedTime: 1663027200000, // 2022-09-13T00:00:00.000Z in milliseconds }; diff --git a/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.test.tsx b/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.test.tsx index 76f56848c..fcfed6b1f 100644 --- a/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.test.tsx +++ b/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.test.tsx @@ -22,7 +22,13 @@ import { cleanup, screen } from '@testing-library/react'; import { of } from 'rxjs'; import { render, testT } from '../../utils'; -const mockTarget = { connectUrl: 'service:jmx:rmi://someUrl', alias: 'fooTarget' }; +const mockTarget = { + connectUrl: 'service:jmx:rmi://someUrl', + alias: 'fooTarget', + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; const mockTemplate1: EventTemplate = { name: 'template1', diff --git a/src/test/Dashboard/Charts/jfr/JFRMetricsChartCard.test.tsx b/src/test/Dashboard/Charts/jfr/JFRMetricsChartCard.test.tsx index 5dec865e2..e0b8ef384 100644 --- a/src/test/Dashboard/Charts/jfr/JFRMetricsChartCard.test.tsx +++ b/src/test/Dashboard/Charts/jfr/JFRMetricsChartCard.test.tsx @@ -29,7 +29,13 @@ import { mockMediaQueryList, render, renderSnapshot } from '../../../utils'; const mockDashboardUrl = 'http://localhost:3000'; jest.spyOn(defaultServices.api, 'grafanaDashboardUrl').mockReturnValue(of(mockDashboardUrl)); -const mockTarget = { connectUrl: 'service:jmx:rmi://someUrl', alias: 'fooTarget' }; +const mockTarget = { + connectUrl: 'service:jmx:rmi://someUrl', + alias: 'fooTarget', + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; jest.spyOn(defaultServices.target, 'target').mockReturnValue(of(mockTarget)); jest.spyOn(defaultServices.settings, 'themeSetting').mockReturnValue(of(ThemeSetting.LIGHT)); diff --git a/src/test/Dashboard/Charts/mbean/MBeanMetricsChartCard.test.tsx b/src/test/Dashboard/Charts/mbean/MBeanMetricsChartCard.test.tsx index 8a8acf9c7..fe55eb5f1 100644 --- a/src/test/Dashboard/Charts/mbean/MBeanMetricsChartCard.test.tsx +++ b/src/test/Dashboard/Charts/mbean/MBeanMetricsChartCard.test.tsx @@ -34,7 +34,13 @@ jest.spyOn(defaultServices.settings, 'datetimeFormat').mockReturnValue(of(defaul jest.spyOn(defaultServices.settings, 'themeSetting').mockReturnValue(of(ThemeSetting.DARK)); jest.spyOn(defaultServices.settings, 'media').mockReturnValue(of(mockMediaQueryList)); -const mockTarget = { connectUrl: 'service:jmx:rmi://someUrl', alias: 'fooTarget' }; +const mockTarget = { + connectUrl: 'service:jmx:rmi://someUrl', + alias: 'fooTarget', + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; jest.spyOn(defaultServices.target, 'target').mockReturnValue(of(mockTarget)); const mockJfrController = new JFRMetricsChartController( diff --git a/src/test/Dashboard/Dashboard.test.tsx b/src/test/Dashboard/Dashboard.test.tsx index 6ee344f75..15dd83c2a 100644 --- a/src/test/Dashboard/Dashboard.test.tsx +++ b/src/test/Dashboard/Dashboard.test.tsx @@ -30,9 +30,10 @@ const mockFooConnectUrl = 'service:jmx:rmi://someFooUrl'; const mockFooTarget: Target = { connectUrl: mockFooConnectUrl, alias: 'fooTarget', + labels: [], annotations: { - cryostat: {}, - platform: {}, + cryostat: [], + platform: [], }, }; diff --git a/src/test/Events/EventTemplates.test.tsx b/src/test/Events/EventTemplates.test.tsx index 736f28e8b..915705da6 100644 --- a/src/test/Events/EventTemplates.test.tsx +++ b/src/test/Events/EventTemplates.test.tsx @@ -16,7 +16,7 @@ import { authFailMessage } from '@app/ErrorView/types'; import { EventTemplates } from '@app/Events/EventTemplates'; import { DeleteOrDisableWarningType } from '@app/Modal/types'; -import { MessageType, EventTemplate, MessageMeta, NotificationMessage } from '@app/Shared/Services/api.types'; +import { MessageType, EventTemplate, MessageMeta, NotificationMessage, Target } from '@app/Shared/Services/api.types'; import { ServiceContext, defaultServices, Services } from '@app/Shared/Services/Services'; import { TargetService } from '@app/Shared/Services/Target.service'; import '@testing-library/jest-dom'; @@ -25,7 +25,13 @@ import { of, Subject } from 'rxjs'; import { render, renderSnapshot } from '../utils'; const mockConnectUrl = 'service:jmx:rmi://someUrl'; -const mockTarget = { connectUrl: mockConnectUrl, alias: 'fooTarget' }; +const mockTarget = { + connectUrl: mockConnectUrl, + alias: 'fooTarget', + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; const mockMessageType = { type: 'application', subtype: 'json' } as MessageType; @@ -255,7 +261,7 @@ describe('', () => { it('should show error view if failing to retrieve event templates', async () => { const subj = new Subject(); const mockTargetSvc = { - target: () => of(mockTarget), + target: () => of(mockTarget as Target), authFailure: () => subj.asObservable(), } as TargetService; const services: Services = { diff --git a/src/test/Events/EventTypes.test.tsx b/src/test/Events/EventTypes.test.tsx index df8cd0518..8016f6709 100644 --- a/src/test/Events/EventTypes.test.tsx +++ b/src/test/Events/EventTypes.test.tsx @@ -15,7 +15,7 @@ */ import { authFailMessage } from '@app/ErrorView/types'; import { EventTypes } from '@app/Events/EventTypes'; -import { EventType } from '@app/Shared/Services/api.types'; +import { EventType, Target } from '@app/Shared/Services/api.types'; import { ServiceContext, defaultServices, Services } from '@app/Shared/Services/Services'; import { TargetService } from '@app/Shared/Services/Target.service'; import { act as doAct, cleanup, screen } from '@testing-library/react'; @@ -24,7 +24,13 @@ import { of, Subject } from 'rxjs'; import { render, renderSnapshot } from '../utils'; const mockConnectUrl = 'service:jmx:rmi://someUrl'; -const mockTarget = { connectUrl: mockConnectUrl, alias: 'fooTarget' }; +const mockTarget = { + connectUrl: mockConnectUrl, + alias: 'fooTarget', + jvmId: 'foo', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; const mockEventType: EventType = { name: 'Some Event', @@ -58,7 +64,7 @@ describe('', () => { it('should show error view if failing to retrieve event types', async () => { const subj = new Subject(); const mockTargetSvc = { - target: () => of(mockTarget), + target: () => of(mockTarget as Target), authFailure: () => subj.asObservable(), } as TargetService; const services: Services = { diff --git a/src/test/RecordingMetadata/BulkEditLabels.test.tsx b/src/test/RecordingMetadata/BulkEditLabels.test.tsx index 7354520c1..69d73eb08 100644 --- a/src/test/RecordingMetadata/BulkEditLabels.test.tsx +++ b/src/test/RecordingMetadata/BulkEditLabels.test.tsx @@ -19,6 +19,7 @@ import { ActiveRecording, RecordingState, NotificationMessage, + Target, } from '@app/Shared/Services/api.types'; import { defaultServices } from '@app/Shared/Services/Services'; import '@testing-library/jest-dom'; @@ -33,12 +34,21 @@ jest.mock('@patternfly/react-core', () => ({ const mockConnectUrl = 'service:jmx:rmi://someUrl'; const mockJvmId = 'id'; -const mockTarget = { connectUrl: mockConnectUrl, alias: 'fooTarget', jvmId: mockJvmId }; - -const mockRecordingLabels = { - someLabel: 'someValue', +const mockTarget: Target = { + connectUrl: mockConnectUrl, + alias: 'fooTarget', + jvmId: mockJvmId, + labels: [], + annotations: { cryostat: [], platform: [] }, }; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; + const mockArchivedRecording: ArchivedRecording = { name: 'someArchivedRecording_some_random', downloadUrl: 'http://downloadUrl', diff --git a/src/test/RecordingMetadata/ClickableLabel.test.tsx b/src/test/RecordingMetadata/ClickableLabel.test.tsx index e38cb4f12..beb9c64a6 100644 --- a/src/test/RecordingMetadata/ClickableLabel.test.tsx +++ b/src/test/RecordingMetadata/ClickableLabel.test.tsx @@ -14,18 +14,18 @@ * limitations under the License. */ import { ClickableLabel } from '@app/RecordingMetadata/ClickableLabel'; -import { RecordingLabel } from '@app/RecordingMetadata/types'; import '@testing-library/jest-dom'; +import { KeyValue } from '@app/Shared/Services/api.types'; import { cleanup, screen } from '@testing-library/react'; import { render, renderSnapshot } from '../utils'; const mockLabel = { key: 'someLabel', value: 'someValue', -} as RecordingLabel; +} as KeyValue; const mockLabelAsString = 'someLabel: someValue'; -const onLabelClick = jest.fn((_label: RecordingLabel) => { +const onLabelClick = jest.fn((_label: KeyValue) => { /**Do nothing. Used for checking renders */ }); diff --git a/src/test/RecordingMetadata/LabelCell.test.tsx b/src/test/RecordingMetadata/LabelCell.test.tsx index 572772be9..67fb6f9fa 100644 --- a/src/test/RecordingMetadata/LabelCell.test.tsx +++ b/src/test/RecordingMetadata/LabelCell.test.tsx @@ -15,9 +15,8 @@ */ import { LabelCell } from '@app/RecordingMetadata/LabelCell'; -import { RecordingLabel } from '@app/RecordingMetadata/types'; import { UpdateFilterOptions } from '@app/Shared/Redux/Filters/Common'; -import { Target } from '@app/Shared/Services/api.types'; +import { KeyValue, Target } from '@app/Shared/Services/api.types'; import '@testing-library/jest-dom'; import { cleanup, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -26,14 +25,15 @@ import { render, renderSnapshot } from '../utils'; const mockFooTarget: Target = { connectUrl: 'service:jmx:rmi://someFooUrl', alias: 'fooTarget', + labels: [], annotations: { - cryostat: {}, - platform: {}, + cryostat: [], + platform: [], }, }; -const mockLabel = { key: 'someLabel', value: 'someValue' } as RecordingLabel; -const mockAnotherLabel = { key: 'anotherLabel', value: 'anotherValue' } as RecordingLabel; +const mockLabel = { key: 'someLabel', value: 'someValue' } as KeyValue; +const mockAnotherLabel = { key: 'anotherLabel', value: 'anotherValue' } as KeyValue; const mockLabelList = [mockLabel, mockAnotherLabel]; // For display diff --git a/src/test/RecordingMetadata/RecordingLabelFields.test.tsx b/src/test/RecordingMetadata/RecordingLabelFields.test.tsx index 9449dfa60..13a48d7e3 100644 --- a/src/test/RecordingMetadata/RecordingLabelFields.test.tsx +++ b/src/test/RecordingMetadata/RecordingLabelFields.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { RecordingLabelFields, RecordingLabelFieldsProps } from '@app/RecordingMetadata/RecordingLabelFields'; -import { RecordingLabel } from '@app/RecordingMetadata/types'; +import { KeyValue } from '@app/Shared/Services/api.types'; import { ValidatedOptions } from '@patternfly/react-core'; import '@testing-library/jest-dom'; import * as tlr from '@testing-library/react'; @@ -37,12 +37,12 @@ mockMetadataFile.text = jest.fn( describe('', () => { // RecordingLabelFields component modifies labels in-place, so we need to reinitialize mocks // after every tests. - let mockLabels: RecordingLabel[]; + let mockLabels: KeyValue[]; let mockValid: ValidatedOptions; let mockProps: RecordingLabelFieldsProps; - let mockLabel1: RecordingLabel; - let mockLabel2: RecordingLabel; - let mockEmptyLabel: RecordingLabel; + let mockLabel1: KeyValue; + let mockLabel2: KeyValue; + let mockEmptyLabel: KeyValue; afterEach(cleanup); @@ -50,20 +50,20 @@ describe('', () => { mockLabel1 = { key: 'someLabel', value: 'someValue', - } as RecordingLabel; + } as KeyValue; mockLabel2 = { key: 'anotherLabel', value: 'anotherValue', - } as RecordingLabel; + } as KeyValue; mockEmptyLabel = { key: '', value: '', - } as RecordingLabel; + } as KeyValue; mockLabels = [mockLabel1, mockLabel2]; mockValid = ValidatedOptions.default; mockProps = { labels: mockLabels, - setLabels: jest.fn((labels: RecordingLabel[]) => (mockLabels = labels.slice())), + setLabels: jest.fn((labels: KeyValue[]) => (mockLabels = labels.slice())), setValid: jest.fn((state: ValidatedOptions) => (mockValid = state)), }; }); diff --git a/src/test/Recordings/ActiveRecordingsTable.test.tsx b/src/test/Recordings/ActiveRecordingsTable.test.tsx index 38e98fb0c..865cefc71 100644 --- a/src/test/Recordings/ActiveRecordingsTable.test.tsx +++ b/src/test/Recordings/ActiveRecordingsTable.test.tsx @@ -23,7 +23,7 @@ import { TargetRecordingFilters, } from '@app/Shared/Redux/Filters/RecordingFilterSlice'; import { RootState } from '@app/Shared/Redux/ReduxStore'; -import { ActiveRecording, RecordingState, NotificationMessage } from '@app/Shared/Services/api.types'; +import { ActiveRecording, RecordingState, NotificationMessage, Target } from '@app/Shared/Services/api.types'; import { defaultServices, ServiceContext, Services } from '@app/Shared/Services/Services'; import { TargetService } from '@app/Shared/Services/Target.service'; import dayjs, { defaultDatetimeFormat } from '@i18n/datetime'; @@ -33,10 +33,19 @@ import { basePreloadedState, DEFAULT_DIMENSIONS, render, resize } from '../utils const mockConnectUrl = 'service:jmx:rmi://someUrl'; const mockJvmId = 'id'; -const mockTarget = { connectUrl: mockConnectUrl, alias: 'fooTarget', jvmId: mockJvmId }; -const mockRecordingLabels = { - someLabel: 'someValue', +const mockTarget = { + connectUrl: mockConnectUrl, + alias: 'fooTarget', + jvmId: mockJvmId, + labels: [], + annotations: { cryostat: [], platform: [] }, }; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; const mockRecording: ActiveRecording = { name: 'someRecording', downloadUrl: 'http://downloadUrl', @@ -527,7 +536,7 @@ describe('', () => { it('should show error view if failing to retrieve recordings', async () => { const subj = new Subject(); const mockTargetSvc = { - target: () => of(mockTarget), + target: () => of(mockTarget as Target), authFailure: () => subj.asObservable(), } as TargetService; const services: Services = { diff --git a/src/test/Recordings/ArchivedRecordingsTable.test.tsx b/src/test/Recordings/ArchivedRecordingsTable.test.tsx index 4a492c6ab..b8d028bf9 100644 --- a/src/test/Recordings/ArchivedRecordingsTable.test.tsx +++ b/src/test/Recordings/ArchivedRecordingsTable.test.tsx @@ -21,7 +21,7 @@ import { TargetRecordingFilters, } from '@app/Shared/Redux/Filters/RecordingFilterSlice'; import { RootState } from '@app/Shared/Redux/ReduxStore'; -import { UPLOADS_SUBDIRECTORY, ArchivedRecording, NotificationMessage } from '@app/Shared/Services/api.types'; +import { UPLOADS_SUBDIRECTORY, ArchivedRecording, NotificationMessage, Target } from '@app/Shared/Services/api.types'; import { defaultServices } from '@app/Shared/Services/Services'; import { Text } from '@patternfly/react-core'; import '@testing-library/jest-dom'; @@ -32,14 +32,31 @@ import { basePreloadedState, DEFAULT_DIMENSIONS, render, resize } from '../utils const mockConnectUrl = 'service:jmx:rmi://someUrl'; const mockJvmId = 'id'; -const mockTarget = { connectUrl: mockConnectUrl, alias: 'fooTarget', jvmId: mockJvmId }; -const mockUploadsTarget = { connectUrl: UPLOADS_SUBDIRECTORY, alias: '' }; -const mockRecordingLabels = { - someLabel: 'someValue', +const mockTarget: Target = { + connectUrl: mockConnectUrl, + alias: 'fooTarget', + jvmId: mockJvmId, + labels: [], + annotations: { cryostat: [], platform: [] }, }; -const mockUploadedRecordingLabels = { - someUploaded: 'someUploadedValue', +const mockUploadsTarget = { + connectUrl: UPLOADS_SUBDIRECTORY, + alias: '', + labels: [], + annotations: { cryostat: [], platform: [] }, }; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; +const mockUploadedRecordingLabels = [ + { + key: 'someUploaded', + value: 'someUpdatedValue', + }, +]; const mockMetadataFileName = 'mock.metadata.json'; const mockMetadataFile = new File( [JSON.stringify({ labels: { ...mockUploadedRecordingLabels } })], diff --git a/src/test/Recordings/Filters/DurationFilter.test.tsx b/src/test/Recordings/Filters/DurationFilter.test.tsx index a6eb14b2b..a9ab62117 100644 --- a/src/test/Recordings/Filters/DurationFilter.test.tsx +++ b/src/test/Recordings/Filters/DurationFilter.test.tsx @@ -19,9 +19,12 @@ import { ActiveRecording, RecordingState } from '@app/Shared/Services/api.types' import { cleanup, screen } from '@testing-library/react'; import { render, renderSnapshot } from '../../utils'; -const mockRecordingLabels = { - someLabel: 'someValue', -}; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; const mockRecording: ActiveRecording = { name: 'someRecording', downloadUrl: 'http://downloadUrl', diff --git a/src/test/Recordings/Filters/LabelFilter.test.tsx b/src/test/Recordings/Filters/LabelFilter.test.tsx index 1bcb48391..117a0960f 100644 --- a/src/test/Recordings/Filters/LabelFilter.test.tsx +++ b/src/test/Recordings/Filters/LabelFilter.test.tsx @@ -19,12 +19,18 @@ import { ActiveRecording, RecordingState } from '@app/Shared/Services/api.types' import { cleanup, screen, within } from '@testing-library/react'; import { render, renderSnapshot } from '../../utils'; -const mockRecordingLabels = { - someLabel: 'someValue', -}; -const mockAnotherRecordingLabels = { - anotherLabel: 'anotherValue', -}; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; +const mockAnotherRecordingLabels = [ + { + key: 'anotherLabel', + value: 'anotherValue', + }, +]; const mockRecordingLabelList = ['someLabel:someValue', 'anotherLabel:anotherValue']; const mockRecording: ActiveRecording = { @@ -49,7 +55,7 @@ const mockAnotherRecording = { const mockRecordingWithoutLabel = { ...mockRecording, name: 'noLabelRecording', - metadata: { labels: {} }, + metadata: { labels: [] }, } as ActiveRecording; const mockRecordingList = [mockRecording, mockAnotherRecording, mockRecordingWithoutLabel]; diff --git a/src/test/Recordings/Filters/NameFilter.test.tsx b/src/test/Recordings/Filters/NameFilter.test.tsx index b32477b31..2d04b37b5 100644 --- a/src/test/Recordings/Filters/NameFilter.test.tsx +++ b/src/test/Recordings/Filters/NameFilter.test.tsx @@ -19,9 +19,12 @@ import { ActiveRecording, RecordingState } from '@app/Shared/Services/api.types' import { cleanup, screen, within } from '@testing-library/react'; import { render, renderSnapshot } from '../../utils'; -const mockRecordingLabels = { - someLabel: 'someValue', -}; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; const mockRecording: ActiveRecording = { name: 'someRecording', downloadUrl: 'http://downloadUrl', diff --git a/src/test/Recordings/Filters/RecordingStateFilter.test.tsx b/src/test/Recordings/Filters/RecordingStateFilter.test.tsx index bf94a3aea..d576b621c 100644 --- a/src/test/Recordings/Filters/RecordingStateFilter.test.tsx +++ b/src/test/Recordings/Filters/RecordingStateFilter.test.tsx @@ -19,9 +19,12 @@ import { ActiveRecording, RecordingState } from '@app/Shared/Services/api.types' import { cleanup, screen, within } from '@testing-library/react'; import { render, renderSnapshot } from '../../utils'; -const mockRecordingLabels = { - someLabel: 'someValue', -}; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; const mockRecording: ActiveRecording = { name: 'someRecording', downloadUrl: 'http://downloadUrl', diff --git a/src/test/Recordings/RecordingFilters.test.tsx b/src/test/Recordings/RecordingFilters.test.tsx index 547d932bc..2d4370602 100644 --- a/src/test/Recordings/RecordingFilters.test.tsx +++ b/src/test/Recordings/RecordingFilters.test.tsx @@ -37,15 +37,19 @@ import { basePreloadedState, render } from '../utils'; const mockFooTarget: Target = { connectUrl: 'service:jmx:rmi://someFooUrl', alias: 'fooTarget', + labels: [], annotations: { - cryostat: {}, - platform: {}, + cryostat: [], + platform: [], }, }; -const mockRecordingLabels = { - someLabel: 'someValue', -}; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; const mockActiveRecording: ActiveRecording = { name: 'someRecording', diff --git a/src/test/Recordings/RecordingLabelsPanel.test.tsx b/src/test/Recordings/RecordingLabelsPanel.test.tsx index 8b845e2fc..3478435aa 100644 --- a/src/test/Recordings/RecordingLabelsPanel.test.tsx +++ b/src/test/Recordings/RecordingLabelsPanel.test.tsx @@ -33,9 +33,12 @@ jest.mock('@app/RecordingMetadata/BulkEditLabels', () => { }; }); -const mockRecordingLabels = { - someLabel: 'someValue', -}; +const mockRecordingLabels = [ + { + key: 'someLabel', + value: 'someValue', + }, +]; const mockRecording: ArchivedRecording = { name: 'someRecording', diff --git a/src/test/Recordings/Recordings.test.tsx b/src/test/Recordings/Recordings.test.tsx index bdf062a7f..4c21e6b8c 100644 --- a/src/test/Recordings/Recordings.test.tsx +++ b/src/test/Recordings/Recordings.test.tsx @@ -53,9 +53,10 @@ jest.mock('@app/TargetView/TargetView', () => { const mockFooTarget: Target = { connectUrl: 'service:jmx:rmi://someFooUrl', alias: 'fooTarget', + labels: [], annotations: { - cryostat: {}, - platform: {}, + cryostat: [], + platform: [], }, }; diff --git a/src/test/Rules/CreateRule.test.tsx b/src/test/Rules/CreateRule.test.tsx index 634a215e4..5bb566850 100644 --- a/src/test/Rules/CreateRule.test.tsx +++ b/src/test/Rules/CreateRule.test.tsx @@ -30,9 +30,10 @@ const mockConnectUrl = 'service:jmx:rmi://someUrl'; const mockTarget: Target = { connectUrl: mockConnectUrl, alias: 'io.cryostat.Cryostat', + labels: [], annotations: { - cryostat: { PORT: '9091' }, - platform: {}, + cryostat: [{ key: 'PORT', value: '9091' }], + platform: [], }, }; const mockEventTemplate: EventTemplate = { diff --git a/src/test/SecurityPanel/Credentials/StoreCredentials.test.tsx b/src/test/SecurityPanel/Credentials/StoreCredentials.test.tsx index 741451f8a..ce7bfe824 100644 --- a/src/test/SecurityPanel/Credentials/StoreCredentials.test.tsx +++ b/src/test/SecurityPanel/Credentials/StoreCredentials.test.tsx @@ -35,15 +35,29 @@ const mockAnotherCredential: StoredCredential = { numMatchingTargets: 2, }; -const mockTarget: Target = { connectUrl: 'service:jmx:rmi://someUrl', alias: 'someAlias' }; -const mockAnotherTarget: Target = { connectUrl: 'service:jmx:rmi://anotherUrl', alias: 'anotherAlias' }; +const mockTarget: Target = { + connectUrl: 'service:jmx:rmi://someUrl', + alias: 'someAlias', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; +const mockAnotherTarget: Target = { + connectUrl: 'service:jmx:rmi://anotherUrl', + alias: 'anotherAlias', + labels: [], + annotations: { cryostat: [], platform: [] }, +}; const mockAnotherMatchingTarget: Target = { connectUrl: 'service:jmx:rmi://anotherMatchUrl', alias: 'anotherMatchAlias', + labels: [], + annotations: { cryostat: [], platform: [] }, }; const mockYetAnotherMatchingTarget: Target = { connectUrl: 'service:jmx:rmi://yetAnotherMatchUrl', alias: 'yetAnotherMatchAlias', + labels: [], + annotations: { cryostat: [], platform: [] }, }; const mockMatchedCredentialResponse: MatchedCredential = { diff --git a/src/test/TargetView/TargetSelect.test.tsx b/src/test/TargetView/TargetSelect.test.tsx index c31a1b4d7..f30c19ab0 100644 --- a/src/test/TargetView/TargetSelect.test.tsx +++ b/src/test/TargetView/TargetSelect.test.tsx @@ -26,16 +26,20 @@ const mockBarConnectUrl = 'service:jmx:rmi://someBarUrl'; const CUSTOM_TARGET_REALM = 'Custom Targets'; -const cryostatAnnotation = { - REALM: CUSTOM_TARGET_REALM, -}; +const cryostatAnnotation = [ + { + key: 'REALM', + value: CUSTOM_TARGET_REALM, + }, +]; const mockFooTarget: Target = { jvmId: 'abcd', connectUrl: mockFooConnectUrl, alias: 'fooTarget', + labels: [], annotations: { cryostat: cryostatAnnotation, - platform: {}, + platform: [], }, }; const mockBarTarget: Target = { ...mockFooTarget, jvmId: 'efgh', connectUrl: mockBarConnectUrl, alias: 'barTarget' };