From dcc22a53d2ac7e9601304155e05241c77c49de1d Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 15 Mar 2024 11:38:13 -0400 Subject: [PATCH] fix(topology): update for new GraphQL schema (#1224) --- .../AllTargetsArchivedRecordingsTable.tsx | 43 ++-- .../CreateRecording/CustomRecordingForm.tsx | 2 +- src/app/CreateRecording/types.ts | 2 + .../AutomatedAnalysisCard.tsx | 60 +++--- src/app/RecordingMetadata/BulkEditLabels.tsx | 6 +- .../Recordings/ArchivedRecordingsTable.tsx | 26 ++- src/app/Shared/Services/Api.service.tsx | 190 +++++++++++------- src/app/Shared/Services/api.types.ts | 37 ++-- src/app/Topology/Actions/utils.tsx | 81 ++++---- src/app/utils/fakeData.ts | 14 +- src/mirage/index.ts | 82 ++++---- ...AllTargetsArchivedRecordingsTable.test.tsx | 32 ++- .../CustomRecordingForm.test.tsx | 2 +- .../AutomatedAnalysisCard.test.tsx | 36 ++-- .../RecordingMetadata/BulkEditLabels.test.tsx | 4 +- .../ArchivedRecordingsTable.test.tsx | 28 ++- 16 files changed, 365 insertions(+), 280 deletions(-) diff --git a/src/app/Archives/AllTargetsArchivedRecordingsTable.tsx b/src/app/Archives/AllTargetsArchivedRecordingsTable.tsx index 6ddb90d8a..e9396ed54 100644 --- a/src/app/Archives/AllTargetsArchivedRecordingsTable.tsx +++ b/src/app/Archives/AllTargetsArchivedRecordingsTable.tsx @@ -123,7 +123,7 @@ export const AllTargetsArchivedRecordingsTable: React.FC( `query AllTargetsArchives { - targetNodes { - target { - connectUrl - alias - recordings { - archived { - aggregate { - count - } - } + targetNodes { + target { + connectUrl + alias + archivedRecordings { + aggregate { + count } } } - }`, + } + }`, ) .pipe(map((v) => v.data.targetNodes)) .subscribe({ @@ -175,20 +173,17 @@ export const AllTargetsArchivedRecordingsTable: React.FC( - ` - query ArchiveCountForTarget($connectUrl: String) { - targetNodes(filter: { name: $connectUrl }) { - target { - recordings { - archived { - aggregate { - count + `query ArchiveCountForTarget($connectUrl: String) { + targetNodes(filter: { name: $connectUrl }) { + target { + archivedRecordings { + aggregate { + count + } } } } - } - } - }`, + }`, { connectUrl: target.connectUrl }, ) .subscribe((v) => { @@ -198,7 +193,7 @@ export const AllTargetsArchivedRecordingsTable: React.FC { events: eventSpecifierString, duration: continuous ? undefined : duration * (durationUnit / 1000), archiveOnStop: archiveOnStop && !continuous, - restart: restart, + replace: restart ? 'ALWAYS' : 'NEVER', advancedOptions: { toDisk: toDisk, maxAge: toDisk ? (continuous ? maxAge * maxAgeUnit : undefined) : undefined, diff --git a/src/app/CreateRecording/types.ts b/src/app/CreateRecording/types.ts index e11288c17..00d9492b7 100644 --- a/src/app/CreateRecording/types.ts +++ b/src/app/CreateRecording/types.ts @@ -18,6 +18,8 @@ import { ValidatedOptions } from '@patternfly/react-core'; export type EventTemplateIdentifier = Pick; +export type RecordingReplace = 'ALWAYS' | 'NEVER' | 'STOPPED'; + interface _FormBaseData { name: string; template?: EventTemplateIdentifier; diff --git a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx index 4755d9578..ee43d593b 100644 --- a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx @@ -180,21 +180,19 @@ export const AutomatedAnalysisCard: DashboardCardFC query ActiveRecordingsForAutomatedAnalysis($connectUrl: String) { targetNodes(filter: { name: $connectUrl }) { target { - recordings { - active (filter: { - name: "${automatedAnalysisRecordingName}", - labels: ["origin=${automatedAnalysisRecordingName}"], - }) { - data { - state - name - downloadUrl - reportUrl - metadata { - labels { - key - value - } + activeRecordings(filter: { + name: "${automatedAnalysisRecordingName}", + labels: ["origin=${automatedAnalysisRecordingName}"], + }) { + data { + state + name + downloadUrl + reportUrl + metadata { + labels { + key + value } } } @@ -213,21 +211,25 @@ export const AutomatedAnalysisCard: DashboardCardFC /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ return context.api.graphql( `query ArchivedRecordingsForAutomatedAnalysis($connectUrl: String) { - archivedRecordings(filter: { sourceTarget: $connectUrl }) { - data { - name - downloadUrl - reportUrl - metadata { - labels { - key - value + targetNodes(filter: { name: $connectUrl }) { + target { + archivedRecordings { + data { + name + downloadUrl + reportUrl + metadata { + labels { + key + value + } + } + size + archivedTime + } } } - size - archivedTime } - } }`, { connectUrl }, ); @@ -311,7 +313,7 @@ export const AutomatedAnalysisCard: DashboardCardFC queryArchivedRecordings(connectUrl) .pipe( first(), - map((v) => v.data.archivedRecordings.data as ArchivedRecording[]), + map((v) => v.data.targetNodes[0].target.archivedRecordings.data as ArchivedRecording[]), ) .subscribe({ next: (recordings) => { @@ -366,7 +368,7 @@ export const AutomatedAnalysisCard: DashboardCardFC } } }), - map((v) => v.data.targetNodes[0].target.recordings.active.data[0] as Recording), + map((v) => v.data.targetNodes[0].target.activeRecordings.data[0] as Recording), tap((recording) => { if (recording === null || recording === undefined) { throw new Error(NO_RECORDINGS_MESSAGE); diff --git a/src/app/RecordingMetadata/BulkEditLabels.tsx b/src/app/RecordingMetadata/BulkEditLabels.tsx index 3ecb392ec..47ec48833 100644 --- a/src/app/RecordingMetadata/BulkEditLabels.tsx +++ b/src/app/RecordingMetadata/BulkEditLabels.tsx @@ -188,8 +188,8 @@ export const BulkEditLabels: React.FC = ({ context.api.graphql( `query ArchivedRecordingsForTarget($connectUrl: String) { targetNodes(filter: { name: $connectUrl }) { - recordings { - archived { + target { + archivedRecordings { data { name downloadUrl @@ -205,7 +205,7 @@ export const BulkEditLabels: React.FC = ({ { connectUrl: target.connectUrl }, ), ), - map((v) => v.data.targetNodes[0].recordings.archived.data as ArchivedRecording[]), + map((v) => v.data.targetNodes[0].target.archivedRecordings.data as ArchivedRecording[]), first(), ); } diff --git a/src/app/Recordings/ArchivedRecordingsTable.tsx b/src/app/Recordings/ArchivedRecordingsTable.tsx index 9c16de6e2..88ac40dcd 100644 --- a/src/app/Recordings/ArchivedRecordingsTable.tsx +++ b/src/app/Recordings/ArchivedRecordingsTable.tsx @@ -192,18 +192,22 @@ export const ArchivedRecordingsTable: React.FC = ( return context.api.graphql( ` query ArchivedRecordingsForTarget($connectUrl: String) { - archivedRecordings(filter: { sourceTarget: $connectUrl }) { - data { - name - downloadUrl - reportUrl - metadata { - labels { - key - value + targetNodes(filter: { name: $connectUrl }) { + target { + archivedRecordings { + data { + name + downloadUrl + reportUrl + metadata { + labels { + key + value + } + } + size } } - size } } }`, @@ -256,7 +260,7 @@ export const ArchivedRecordingsTable: React.FC = ( filter((target) => !!target), first(), concatMap((target: Target) => queryTargetRecordings(target.connectUrl)), - map((v) => v.data.archivedRecordings.data as ArchivedRecording[]), + map((v) => v.data.targetNodes[0].target.archivedRecordings.data as ArchivedRecording[]), ) .subscribe({ next: handleRecordings, diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 4f8b3b7a4..388a1d107 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -45,21 +45,23 @@ import { CredentialsResponse, RulesResponse, EnvironmentNode, - DiscoveryResponse, - ActiveRecordingFilterInput, + ActiveRecordingsFilterInput, RecordingCountResponse, MBeanMetrics, MBeanMetricsResponse, EventType, NotificationCategory, - NullableTarget, HttpError, SimpleResponse, XMLHttpError, XMLHttpRequestConfig, XMLHttpResponse, KeyValue, - CustomTargetStub, + TargetStub, + TargetForTest, + Metadata, + TargetMetadata, + isTargetMetadata, } from './api.types'; import { isHttpError, includesTarget, isHttpOk, isXMLHttpError } from './api.utils'; import { LoginService } from './Login.service'; @@ -171,7 +173,7 @@ export class ApiService { } createTarget( - target: CustomTargetStub, + target: TargetStub, credentials?: { username?: string; password?: string }, storeCredentials = false, dryrun = false, @@ -207,7 +209,7 @@ export class ApiService { ); } - deleteTarget(target: Target): Observable { + deleteTarget(target: TargetStub): Observable { return this.sendRequest('v2', `targets/${encodeURIComponent(target.connectUrl)}`, { method: 'DELETE', }).pipe( @@ -296,7 +298,7 @@ export class ApiService { name, events, duration, - restart, + replace, archiveOnStop, metadata, advancedOptions, @@ -310,19 +312,11 @@ export class ApiService { if (archiveOnStop != undefined) { form.append('archiveOnStop', String(archiveOnStop)); } - const transformedMetadata = { - labels: {}, - annotations: { - cryostat: {}, - platform: {}, - }, - }; - metadata?.labels.forEach((label) => (transformedMetadata.labels[label.key] = label.value)); if (metadata) { - form.append('metadata', JSON.stringify(transformedMetadata)); + form.append('metadata', JSON.stringify(this.transformMetadataToObject(metadata))); } - if (restart != undefined) { - form.append('restart', String(restart)); + if (replace != undefined) { + form.append('replace', String(replace)); } if (advancedOptions) { if (advancedOptions.toDisk != undefined) { @@ -489,7 +483,7 @@ export class ApiService { } uploadArchivedRecordingToGrafana( - sourceTarget: Observable, + sourceTarget: Observable, recordingName: string, ): Observable { return sourceTarget.pipe( @@ -531,7 +525,13 @@ export class ApiService { ); } - transformAndStringifyToRawLabels(labels: KeyValue[]) { + // FIXME remove this, all API endpoints that allow us to send labels in the request body should accept it in as-is JSON form + stringifyRecordingLabels(labels: KeyValue | KeyValue[]): string { + return JSON.stringify(labels).replace(/"([^"]+)":/g, '$1:'); + } + + // FIXME remove this, all API endpoints that allow us to send labels in the request body should accept it in as-is JSON form + transformAndStringifyToRawLabels(labels: KeyValue[]): string { const rawLabels = {}; for (const label of labels) { rawLabels[label.key] = label.value; @@ -539,6 +539,30 @@ export class ApiService { return JSON.stringify(rawLabels); } + transformMetadataToObject(metadata: Metadata | TargetMetadata): object { + if (isTargetMetadata(metadata)) { + return { + labels: this.transformLabelsToObject(metadata.labels), + annotations: { + cryostat: this.transformLabelsToObject(metadata?.annotations?.cryostat), + platform: this.transformLabelsToObject(metadata?.annotations?.platform), + }, + }; + } else { + return { + labels: this.transformLabelsToObject(metadata.labels), + }; + } + } + + transformLabelsToObject(labels: KeyValue[]): object { + const out = {}; + for (const label of labels) { + out[label.key] = label.value; + } + return out; + } + postRecordingMetadataFromPath(jvmId: string, recordingName: string, labels: KeyValue[]): Observable { return this.sendRequest( 'beta', @@ -734,7 +758,7 @@ export class ApiService { } getActiveProbesForTarget( - target: Target, + target: TargetStub, suppressNotifications = false, skipStatusCheck = false, ): Observable { @@ -906,13 +930,11 @@ export class ApiService { ` query PostRecordingMetadata($connectUrl: String, $recordingName: String, $labels: String) { targetNodes(filter: { name: $connectUrl }) { - recordings { - archived(filter: { name: $recordingName }) { - data { - doPutMetadata(metadata: { labels: $labels }) { - metadata { - labels - } + archivedRecordings(filter: { name: $recordingName }) { + data { + doPutMetadata(metadata: { labels: $labels }) { + metadata { + labels } } } @@ -922,7 +944,7 @@ export class ApiService { { connectUrl: target.connectUrl, recordingName, labels: this.stringifyRecordingLabels(labels) }, ), ), - map((v) => v.data.targetNodes[0].recordings.archived as ArchivedRecording[]), + map((v) => v.data.targetNodes[0].target.archivedRecordings as ArchivedRecording[]), ); } @@ -953,13 +975,11 @@ export class ApiService { ` query PostActiveRecordingMetadata($connectUrl: String, $recordingName: String, $labels: String) { targetNodes(filter: { name: $connectUrl }) { - recordings { - active(filter: { name: $recordingName }) { - data { - doPutMetadata(metadata: { labels: $labels }) { - metadata { - labels - } + activeRecordings(filter: { name: $recordingName }) { + data { + doPutMetadata(metadata: { labels: $labels }) { + metadata { + labels } } } @@ -969,7 +989,7 @@ export class ApiService { { connectUrl: target.connectUrl, recordingName, labels: this.stringifyRecordingLabels(labels) }, ), ), - map((v) => v.data.targetNodes[0].recordings.active as ActiveRecording[]), + map((v) => v.data.targetNodes[0].target.activeRecordings as ActiveRecording[]), ); } @@ -1043,11 +1063,10 @@ export class ApiService { } getDiscoveryTree(): Observable { - return this.sendRequest('v2.1', 'discovery', { + return this.sendRequest('v3', 'discovery', { method: 'GET', }).pipe( concatMap((resp) => resp.json()), - map((body: DiscoveryResponse) => body.data.result), first(), ); } @@ -1056,7 +1075,7 @@ export class ApiService { matchTargetsWithExpr(matchExpression: string, targets: Target[]): Observable { const body = JSON.stringify({ matchExpression, - targets, + targets: targets.map((t) => this.transformTarget(t)), }); const headers = new Headers(); headers.set('Content-Type', 'application/json'); @@ -1087,20 +1106,20 @@ export class ApiService { ); } - groupHasRecording(group: EnvironmentNode, filter: ActiveRecordingFilterInput): Observable { + groupHasRecording(group: EnvironmentNode, filter: ActiveRecordingsFilterInput): Observable { return this.graphql( ` - query GetRecordingForGroup ($groupFilter: EnvironmentNodeFilterInput, $recordingFilter: ActiveRecordingFilterInput){ + query GroupHasRecording ($groupFilter: DiscoveryNodeFilterInput, $recordingFilter: ActiveRecordingsFilterInput){ environmentNodes(filter: $groupFilter) { name descendantTargets { name - recordings { - active(filter: $recordingFilter) { - data { - name - } + target { + activeRecordings(filter: $recordingFilter) { + aggregate { + count } + } } } } @@ -1114,22 +1133,22 @@ export class ApiService { first(), map((body) => body.data.environmentNodes[0].descendantTargets.reduce( - (acc: Partial[], curr) => acc.concat(curr.recordings?.active?.data || []), - [] as Partial[], + (acc: number, curr) => acc + curr.target.activeRecordings.aggregate.count, + 0, ), ), catchError((_) => of([])), - map((recs: Partial[]) => recs.length > 0), // At least one + map((acc) => acc > 0), // At least one ); } - targetHasRecording(target: Target, filter: ActiveRecordingFilterInput = {}): Observable { + targetHasRecording(target: TargetStub, filter: ActiveRecordingsFilterInput = {}): Observable { return this.graphql( ` - query ActiveRecordingsForJFRMetrics($connectUrl: String, $recordingFilter: ActiveRecordingFilterInput) { + query ActiveRecordingsForJFRMetrics($connectUrl: String, $recordingFilter: ActiveRecordingsFilterInput) { targetNodes(filter: { name: $connectUrl }) { - recordings { - active (filter: $recordingFilter) { + target { + activeRecordings(filter: $recordingFilter) { aggregate { count } @@ -1149,7 +1168,7 @@ export class ApiService { if (nodes.length === 0) { return false; } - const count = nodes[0].recordings.active.aggregate.count; + const count = nodes[0].target.activeRecordings.aggregate.count; return count > 0; }), catchError((_) => of(false)), @@ -1157,7 +1176,7 @@ export class ApiService { } checkCredentialForTarget( - target: Target, + target: TargetStub, credentials: { username: string; password: string }, ): Observable< | { @@ -1210,7 +1229,7 @@ export class ApiService { ); } - getTargetMBeanMetrics(target: Target, queries: string[]): Observable { + getTargetMBeanMetrics(target: TargetStub, queries: string[]): Observable { return this.graphql( ` query MBeanMXMetricsForTarget($connectUrl: String) { @@ -1235,30 +1254,37 @@ export class ApiService { ); } - getTargetArchivedRecordings(target: Target): Observable { + getTargetArchivedRecordings(target: TargetStub): Observable { return this.graphql( ` - query ArchivedRecordingsForTarget($connectUrl: String) { - archivedRecordings(filter: { sourceTarget: $connectUrl }) { - data { - name - downloadUrl - reportUrl - metadata { - labels + query ArchivedRecordingsForTarget($connectUrl: String) { + targetNodes(filter: { name: $connectUrl }) { + target { + archivedRecordings { + data { + name + downloadUrl + reportUrl + metadata { + labels { + key + value + } + } + size + archivedTime } - size - archivedTime } } - }`, + } + }`, { connectUrl: target.connectUrl }, true, true, - ).pipe(map((v) => v.data.archivedRecordings.data as ArchivedRecording[])); + ).pipe(map((v) => v.data.targetNodes[0].target.archivedRecordings.data as ArchivedRecording[])); } - getTargetActiveRecordings(target: Target): Observable { + getTargetActiveRecordings(target: TargetStub): Observable { return this.doGet( `targets/${encodeURIComponent(target.connectUrl)}/recordings`, 'v1', @@ -1268,7 +1294,7 @@ export class ApiService { ); } - getTargetEventTemplates(target: Target): Observable { + getTargetEventTemplates(target: TargetStub): Observable { return this.doGet( `targets/${encodeURIComponent(target.connectUrl)}/templates`, 'v1', @@ -1278,7 +1304,7 @@ export class ApiService { ); } - getTargetEventTypes(target: Target): Observable { + getTargetEventTypes(target: TargetStub): Observable { return this.doGet( `targets/${encodeURIComponent(target.connectUrl)}/events`, 'v1', @@ -1317,8 +1343,22 @@ export class ApiService { anchor.remove(); } - stringifyRecordingLabels(labels: KeyValue[]): string { - return JSON.stringify(labels).replace(/"([^"]+)":/g, '$1:'); + private transformTarget(target: Target): TargetForTest { + const out: TargetForTest = { + alias: target.alias, + connectUrl: target.connectUrl, + labels: {}, + annotations: { cryostat: {}, platform: {} }, + }; + for (const l of target.labels) { + out.labels[l.key] = l.value; + } + for (const s of ['cryostat', 'platform']) { + for (const e of out.annotations[s]) { + target.annotations[s][e.key] = e.value; + } + } + return out; } private sendRequest( diff --git a/src/app/Shared/Services/api.types.ts b/src/app/Shared/Services/api.types.ts index 2bd3b0ba3..1f124d35c 100644 --- a/src/app/Shared/Services/api.types.ts +++ b/src/app/Shared/Services/api.types.ts @@ -14,10 +14,11 @@ * limitations under the License. */ +import { RecordingReplace } from '@app/CreateRecording/types'; import { AlertVariant } from '@patternfly/react-core'; import { Observable } from 'rxjs'; -export type ApiVersion = 'v1' | 'v2' | 'v2.1' | 'v2.2' | 'v2.3' | 'v2.4' | 'beta'; +export type ApiVersion = 'v1' | 'v2' | 'v2.1' | 'v2.2' | 'v2.3' | 'v2.4' | 'v3' | 'beta'; // ====================================== // Common Resources @@ -29,7 +30,17 @@ export interface KeyValue { export interface Metadata { labels: KeyValue[]; - annotations?: KeyValue[]; +} + +export type TargetMetadata = Metadata & { + annotations: { + cryostat: KeyValue[]; + platform: KeyValue[]; + }; +}; + +export function isTargetMetadata(metadata: Metadata | TargetMetadata): metadata is TargetMetadata { + return (metadata as TargetMetadata).annotations !== undefined; } export interface ApiV2Response { @@ -88,7 +99,12 @@ export class XMLHttpError extends Error { } } -export type CustomTargetStub = Omit; +export type TargetStub = Omit; + +export type TargetForTest = Pick & { + labels: object; + annotations: { cryostat: object; platform: object }; +}; // ====================================== // Health Resources @@ -210,7 +226,7 @@ export interface RecordingAttributes { events: string; duration?: number; archiveOnStop?: boolean; - restart?: boolean; + replace?: RecordingReplace; advancedOptions?: AdvancedRecordingOptions; metadata?: Metadata; } @@ -238,7 +254,7 @@ export interface ActiveRecording extends Recording { maxAge: number; } -export interface ActiveRecordingFilterInput { +export interface ActiveRecordingsFilterInput { name?: string; state?: string; continuous?: boolean; @@ -265,8 +281,8 @@ export interface RecordingResponse extends ApiV2Response { export interface RecordingCountResponse { data: { targetNodes: { - recordings: { - active: { + target: { + activeRecordings: { aggregate: { count: number; }; @@ -446,6 +462,7 @@ export const TEMPLATE_UNSUPPORTED_MESSAGE = 'The template type used in this reco // Discovery/Target resources // ====================================== export interface Target { + id?: number; // present in responses but we must not include it in requests to create targets jvmId?: string; // present in responses, but we do not need to provide it in requests connectUrl: string; alias: string; @@ -499,12 +516,6 @@ export interface TargetNode extends _AbstractNode { readonly target: Target; } -export interface DiscoveryResponse extends ApiV2Response { - data: { - result: EnvironmentNode; - }; -} - // ====================================== // Notification resources // ====================================== diff --git a/src/app/Topology/Actions/utils.tsx b/src/app/Topology/Actions/utils.tsx index aee7c9300..f7f358a16 100644 --- a/src/app/Topology/Actions/utils.tsx +++ b/src/app/Topology/Actions/utils.tsx @@ -122,23 +122,23 @@ export const nodeActions: NodeAction[] = [ services.api .graphql( ` - query StartRecordingForGroup($filter: EnvironmentNodeFilterInput, $recordingName: String!, $labels: String) { + query StartRecordingForGroup($filter: DiscoveryNodeFilterInput!, $recordingName: String!, $metadata: RecordingMetadataInput!) { environmentNodes(filter: $filter) { name descendantTargets { name - doStartRecording(recording: { - name: $recordingName, - template: "Continuous", - templateType: "TARGET", - duration: 0, - restart: true, - metadata: { - labels: $labels - }, - }) { - name - state + target { + doStartRecording(recording: { + name: $recordingName, + template: "Continuous", + templateType: "TARGET", + duration: 0, + replace: "STOPPED", + metadata: $metadata, + }) { + name + state + } } } } @@ -147,12 +147,9 @@ export const nodeActions: NodeAction[] = [ { filter: { id: group.id }, recordingName: QUICK_RECORDING_NAME, - labels: services.api.stringifyRecordingLabels([ - { - key: QUICK_RECORDING_LABEL_KEY, - value: group.name.replace(/[\s+-]/g, '_'), - }, - ]), + metadata: { + labels: [{ key: QUICK_RECORDING_LABEL_KEY, value: group.name.replace(/[\s+-]/g, '_') }], + }, }, false, true, @@ -171,19 +168,21 @@ export const nodeActions: NodeAction[] = [ services.api .graphql( ` - query DeleteRecordingForGroup ($groupFilter: EnvironmentNodeFilterInput, $recordingFilter: ActiveRecordingFilterInput){ + query DeleteRecordingForGroup ($groupFilter: DiscoveryNodeFilterInput, $recordingFilter: ActiveRecordingsFilterInput) { environmentNodes(filter: $groupFilter) { name descendantTargets { name - recordings { + target { + recordings { active(filter: $recordingFilter) { - data { - doArchive { - name - } + data { + doArchive { + name } + } } + } } } } @@ -213,20 +212,22 @@ export const nodeActions: NodeAction[] = [ services.api .graphql( ` - query StopRecordingForGroup ($groupFilter: EnvironmentNodeFilterInput, $recordingFilter: ActiveRecordingFilterInput){ + query StopRecordingForGroup ($groupFilter: DiscoveryNodeFilterInput, $recordingFilter: ActiveRecordingsFilterInput) { environmentNodes(filter: $groupFilter) { name descendantTargets { name - recordings { + target { + recordings { active(filter: $recordingFilter) { - data { - doStop { - name - state - } + data { + doStop { + name + state } + } } + } } } } @@ -257,20 +258,22 @@ export const nodeActions: NodeAction[] = [ services.api .graphql( ` - query DeleteRecordingForGroup ($groupFilter: EnvironmentNodeFilterInput, $recordingFilter: ActiveRecordingFilterInput){ + query DeleteRecordingForGroup ($groupFilter: DiscoveryNodeFilterInput, $recordingFilter: ActiveRecordingsFilterInput) { environmentNodes(filter: $groupFilter) { name descendantTargets { name - recordings { + target { + recordings { active(filter: $recordingFilter) { - data { - doDelete { - name - state - } + data { + doDelete { + name + state } + } } + } } } } diff --git a/src/app/utils/fakeData.ts b/src/app/utils/fakeData.ts index 0075d0fa0..1723e190c 100644 --- a/src/app/utils/fakeData.ts +++ b/src/app/utils/fakeData.ts @@ -23,7 +23,7 @@ import { RecordingState, Recording, MBeanMetrics, - ActiveRecordingFilterInput, + ActiveRecordingsFilterInput, ArchivedRecording, EventTemplate, EventProbe, @@ -208,7 +208,7 @@ export const fakeEvaluations: AnalysisResult[] = [ export const fakeCachedReport: CachedReportValue = { report: fakeEvaluations, - timestamp: 1663027200000, + timestamp: Date.now() - 1000 * 60 * 60, }; class FakeTargetService extends TargetService { @@ -301,7 +301,7 @@ class FakeApiService extends ApiService { } // JFR Metrics card - targetHasRecording(_target: Target, _filter?: ActiveRecordingFilterInput): Observable { + targetHasRecording(_target: Target, _filter?: ActiveRecordingsFilterInput): Observable { return of(true); } @@ -347,8 +347,8 @@ class FakeApiService extends ApiService { return of([]); } - // Automatic Analysis Card - // This fakes the fetch for Automatic Analysis recording to return available. + // Automated Analysis Card + // This fakes the fetch for Automated Analysis recording to return available. // Then subsequent graphql call for archived recording is ignored graphql( _query: string, @@ -360,8 +360,8 @@ class FakeApiService extends ApiService { data: { targetNodes: [ { - recordings: { - active: { + target: { + activeRecordings: { data: [fakeAARecording], }, }, diff --git a/src/mirage/index.ts b/src/mirage/index.ts index d42a8b0f1..d78719320 100644 --- a/src/mirage/index.ts +++ b/src/mirage/index.ts @@ -126,35 +126,27 @@ export const startMirage = ({ environment = 'development' } = {}) => { }; }); this.get('api/v1/targets', (schema) => schema.all(Resource.TARGET).models); - this.get('api/v2.1/discovery', (schema) => { + this.get('api/v3/discovery', (schema) => { const models = schema.all(Resource.TARGET).models; const realmTypes = models.map((t) => t.annotations.cryostat['REALM']); return { - meta: { - status: 'OK', - type: 'application/json', - }, - data: { - result: { - name: 'Universe', - nodeType: 'Universe', - labels: [], - children: realmTypes.map((r: string) => ({ - name: r, - nodeType: 'Realm', - labels: [], - id: r, - children: models - .filter((t) => t.annotations.cryostat['REALM'] === r) - .map((t) => ({ - id: t.alias, - name: t.alias, - nodeType: r === 'Custom Targets' ? 'CustomTarget' : 'JVM', - target: t, - })), + name: 'Universe', + nodeType: 'Universe', + labels: [], + children: realmTypes.map((r: string) => ({ + name: r, + nodeType: 'Realm', + labels: [], + id: r, + children: models + .filter((t) => t.annotations.cryostat['REALM'] === r) + .map((t) => ({ + id: t.alias, + name: t.alias, + nodeType: r === 'Custom Targets' ? 'CustomTarget' : 'JVM', + target: t, })), - }, - }, + })), }; }); this.get('api/v1/recordings', (schema) => schema.all(Resource.ARCHIVE).models); @@ -505,17 +497,23 @@ export const startMirage = ({ environment = 'development' } = {}) => { case 'ArchivedRecordingsForTarget': case 'UploadedRecordings': data = { - archivedRecordings: { - data: schema.all(Resource.ARCHIVE).models, - }, + targetNodes: [ + { + target: { + archivedRecordings: { + data: schema.all(Resource.ARCHIVE).models, + }, + }, + }, + ], }; break; case 'ActiveRecordingsForTarget': data = { targetNodes: [ { - recordings: { - archived: { + target: { + archivedRecordings: { data: schema.all(Resource.ARCHIVE).models, }, }, @@ -525,17 +523,23 @@ export const startMirage = ({ environment = 'development' } = {}) => { break; case 'ArchivedRecordingsForAutomatedAnalysis': data = { - archivedRecordings: { - data: schema.all(Resource.ARCHIVE).models, - }, + targetNodes: [ + { + target: { + archivedRecordings: { + data: schema.all(Resource.ARCHIVE).models, + }, + }, + }, + ], }; break; case 'ActiveRecordingsForAutomatedAnalysis': data = { targetNodes: [ { - recordings: { - active: { + target: { + activeRecordings: { data: schema.all(Resource.RECORDING).models, }, }, @@ -556,8 +560,8 @@ export const startMirage = ({ environment = 'development' } = {}) => { data = { targetNodes: [ { - recordings: { - archived: { + target: { + archivedRecordings: { data: [ { doPutMetadata: { @@ -602,8 +606,8 @@ export const startMirage = ({ environment = 'development' } = {}) => { data = { targetNodes: [ { - recordings: { - active: { + target: { + activeRecordings: { data: [ { doPutMetadata: { diff --git a/src/test/Archives/AllTargetsArchivedRecordingsTable.test.tsx b/src/test/Archives/AllTargetsArchivedRecordingsTable.test.tsx index 0fdd8e029..8a021df10 100644 --- a/src/test/Archives/AllTargetsArchivedRecordingsTable.test.tsx +++ b/src/test/Archives/AllTargetsArchivedRecordingsTable.test.tsx @@ -79,11 +79,9 @@ const mockTargetsAndCountsResponse = { target: { alias: mockAlias1, connectUrl: mockConnectUrl1, - recordings: { - archived: { - aggregate: { - count: mockCount1, - }, + archivedRecordings: { + aggregate: { + count: mockCount1, }, }, }, @@ -92,11 +90,9 @@ const mockTargetsAndCountsResponse = { target: { alias: mockAlias2, connectUrl: mockConnectUrl2, - recordings: { - archived: { - aggregate: { - count: mockCount2, - }, + archivedRecordings: { + aggregate: { + count: mockCount2, }, }, }, @@ -105,11 +101,9 @@ const mockTargetsAndCountsResponse = { target: { alias: mockAlias3, connectUrl: mockConnectUrl3, - recordings: { - archived: { - aggregate: { - count: mockCount3, - }, + archivedRecordings: { + aggregate: { + count: mockCount3, }, }, }, @@ -123,11 +117,9 @@ const mockNewTargetCountResponse = { targetNodes: [ { target: { - recordings: { - archived: { - aggregate: { - count: mockNewCount, - }, + archivedRecordings: { + aggregate: { + count: mockNewCount, }, }, }, diff --git a/src/test/CreateRecording/CustomRecordingForm.test.tsx b/src/test/CreateRecording/CustomRecordingForm.test.tsx index bdc185501..9392a3094 100644 --- a/src/test/CreateRecording/CustomRecordingForm.test.tsx +++ b/src/test/CreateRecording/CustomRecordingForm.test.tsx @@ -118,7 +118,7 @@ describe('', () => { events: 'template=someEventTemplate,type=CUSTOM', duration: 30, archiveOnStop: true, - restart: false, + replace: 'NEVER', advancedOptions: { maxAge: undefined, maxSize: 0, diff --git a/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.test.tsx b/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.test.tsx index 32b6bd382..6b27417d6 100644 --- a/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.test.tsx +++ b/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.test.tsx @@ -153,10 +153,8 @@ const mockCachedReport: CachedReportValue = { }; const mockTargetNode = { - recordings: { - active: { - data: [mockRecording], - }, + activeRecordings: { + data: [mockRecording], }, }; @@ -171,10 +169,8 @@ const mockEmptyActiveRecordingsResponse = { targetNodes: [ { target: { - recordings: { - active: { - data: [], - }, + activeRecordings: { + data: [], }, }, }, @@ -184,17 +180,29 @@ const mockEmptyActiveRecordingsResponse = { const mockArchivedRecordingsResponse = { data: { - archivedRecordings: { - data: [mockArchivedRecording], - }, + targetNodes: [ + { + target: { + archivedRecordings: { + data: [mockArchivedRecording], + }, + }, + }, + ], }, }; const mockEmptyArchivedRecordingsResponse = { data: { - archivedRecordings: { - data: [], - }, + targetNodes: [ + { + target: { + archivedRecordings: { + data: [], + }, + }, + }, + ], }, }; diff --git a/src/test/RecordingMetadata/BulkEditLabels.test.tsx b/src/test/RecordingMetadata/BulkEditLabels.test.tsx index cec202ce1..4745612ec 100644 --- a/src/test/RecordingMetadata/BulkEditLabels.test.tsx +++ b/src/test/RecordingMetadata/BulkEditLabels.test.tsx @@ -107,8 +107,8 @@ const mockArchivedRecordingsResponse = { data: { targetNodes: [ { - recordings: { - archived: { + target: { + archivedRecordings: { data: [mockArchivedRecording] as ArchivedRecording[], }, }, diff --git a/src/test/Recordings/ArchivedRecordingsTable.test.tsx b/src/test/Recordings/ArchivedRecordingsTable.test.tsx index e167c84c1..59a9509b9 100644 --- a/src/test/Recordings/ArchivedRecordingsTable.test.tsx +++ b/src/test/Recordings/ArchivedRecordingsTable.test.tsx @@ -90,9 +90,27 @@ const mockRecording: ArchivedRecording = { }; const mockArchivedRecordingsResponse = { + data: { + targetNodes: [ + { + target: { + archivedRecordings: { + data: [mockRecording], + }, + }, + }, + ], + }, +}; + +const mockAllArchivedRecordingsResponse = { data: { archivedRecordings: { - data: [mockRecording] as ArchivedRecording[], + data: [mockRecording], + aggregate: { + count: 1, + size: mockRecording.size, + }, }, }, }; @@ -135,7 +153,13 @@ jest.spyOn(defaultServices.api, 'deleteArchivedRecording').mockReturnValue(of(tr jest.spyOn(defaultServices.api, 'downloadRecording').mockReturnValue(); jest.spyOn(defaultServices.api, 'grafanaDatasourceUrl').mockReturnValue(of('/datasource')); jest.spyOn(defaultServices.api, 'grafanaDashboardUrl').mockReturnValue(of('/grafanaUrl')); -jest.spyOn(defaultServices.api, 'graphql').mockReturnValue(of(mockArchivedRecordingsResponse)); +jest.spyOn(defaultServices.api, 'graphql').mockImplementation((query: string) => { + if (query.includes('ArchivedRecordingsForTarget')) { + return of(mockArchivedRecordingsResponse); + } else { + return of(mockAllArchivedRecordingsResponse); + } +}); jest.spyOn(defaultServices.api, 'uploadArchivedRecordingToGrafana').mockReturnValue(of(true)); jest