From 771ecf472f8fa7b5aa66aa7ec320508fdfff4983 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 20 Aug 2024 15:50:47 -0400 Subject: [PATCH 01/25] update web-client to use v4 API endpoint --- src/app/Shared/Services/Api.service.tsx | 6 +-- src/app/Shared/Services/Login.service.tsx | 4 +- src/app/utils/fakeData.ts | 4 +- src/mirage/index.ts | 51 +++++++++---------- .../Shared/Services/Login.service.test.tsx | 10 ++-- 5 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 8a43d8ba1..4c0063685 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -111,10 +111,10 @@ export class ApiService { .subscribe(); const getDatasourceURL: Observable = fromFetch( - `${this.login.authority}/api/v1/grafana_datasource_url`, + `${this.login.authority}/api/v4/grafana_datasource_url`, ).pipe(concatMap((resp) => from(resp.json()))); const getDashboardURL: Observable = fromFetch( - `${this.login.authority}/api/v1/grafana_dashboard_url`, + `${this.login.authority}/api/v4/grafana_dashboard_url`, ).pipe(concatMap((resp) => from(resp.json()))); const health: Observable = fromFetch(`${this.login.authority}/health`).pipe( tap((resp: Response) => { @@ -866,7 +866,7 @@ export class ApiService { first(), map( (target) => - `${this.login.authority}/api/v2.1/targets/${encodeURIComponent( + `${this.login.authority}/api/v4/targets/${encodeURIComponent( target!.connectUrl, )}/templates/${encodeURIComponent(template.name)}/type/${encodeURIComponent(template.type)}`, ), diff --git a/src/app/Shared/Services/Login.service.tsx b/src/app/Shared/Services/Login.service.tsx index 23fd5bf4c..235c06234 100644 --- a/src/app/Shared/Services/Login.service.tsx +++ b/src/app/Shared/Services/Login.service.tsx @@ -29,7 +29,7 @@ export class LoginService { this.authority = process.env.CRYOSTAT_AUTHORITY || '.'; this.sessionState.next(SessionState.CREATING_USER_SESSION); - fromFetch(`${this.authority}/api/v2.1/auth`, { + fromFetch(`${this.authority}/api/v4/auth`, { credentials: 'include', mode: 'cors', method: 'POST', @@ -65,7 +65,7 @@ export class LoginService { } setLoggedOut(): Observable { - return fromFetch(`${this.authority}/api/v2.1/logout`, { + return fromFetch(`${this.authority}/api/v4logout`, { credentials: 'include', mode: 'cors', method: 'POST', diff --git a/src/app/utils/fakeData.ts b/src/app/utils/fakeData.ts index 9601fc4e7..9f6cec002 100644 --- a/src/app/utils/fakeData.ts +++ b/src/app/utils/fakeData.ts @@ -90,9 +90,9 @@ export const fakeTarget: Target = { export const fakeAARecording: ActiveRecording = { name: 'automated-analysis', downloadUrl: - '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/recordings/automated-analysis', + 'https://clustercryostat-sample-default.apps.ci-ln-25fg5f2-76ef8.origin-ci-int-aws.dev.rhcloud.com:443/api/v4/targets/service:jmx:rmi:%2F%2F%2Fjndi%2Frmi:%2F%2F10-128-2-27.my-namespace.pod:9097%2Fjmxrmi/recordings/automated-analysis', 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', + 'https://clustercryostat-sample-default.apps.ci-ln-25fg5f2-76ef8.origin-ci-int-aws.dev.rhcloud.com:443/api/v4/targets/service:jmx:rmi:%2F%2F%2Fjndi%2Frmi:%2F%2F10-128-2-27.my-namespace.pod:9097%2Fjmxrmi/reports/automated-analysis', metadata: { labels: [ { diff --git a/src/mirage/index.ts b/src/mirage/index.ts index e7f79fd32..21f9c292d 100644 --- a/src/mirage/index.ts +++ b/src/mirage/index.ts @@ -79,9 +79,9 @@ export const startMirage = ({ environment = 'development' } = {}) => { reportsAvailable: true, reportsConfigured: false, })); - this.get('api/v1/grafana_datasource_url', () => new Response(500)); - this.get('api/v1/grafana_dashboard_url', () => new Response(500)); - this.post('api/v2.1/auth', () => { + this.get('api/v4/grafana_datasource_url', () => new Response(500)); + this.get('api/v4/grafana_dashboard_url', () => new Response(500)); + this.post('api/v4/auth', () => { return new Response( 200, {}, @@ -98,15 +98,14 @@ export const startMirage = ({ environment = 'development' } = {}) => { ); }); this.post( - 'api/v2.1/auth/token', + 'api/v4/auth/token', () => new Response(400, {}, 'Resource downloads are not supported in this demo'), ); - this.post('api/v2/targets', (schema, request) => { + this.post('api/v4/targets', (schema, request) => { const params = request.queryParams; if (params['dryrun']) { return new Response(200); } - const attrs = request.requestBody as any; const target = schema.create(Resource.TARGET, { jvmId: `${Date.now().toString(16)}`, @@ -138,8 +137,8 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, }; }); - this.get('api/v1/targets', (schema) => schema.all(Resource.TARGET).models); - this.get('api/v3/discovery', (schema) => { + this.get('api/v4/targets', (schema) => schema.all(Resource.TARGET).models); + this.get('api/v4/discovery', (schema) => { const models = schema.all(Resource.TARGET).models; const realmTypes = models.map((t) => t.annotations.cryostat['REALM']); return { @@ -162,7 +161,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { })), }; }); - this.get('api/v1/recordings', (schema) => schema.all(Resource.ARCHIVE).models); + this.get('api/v4/recordings', (schema) => schema.all(Resource.ARCHIVE).models); this.get('api/beta/fs/recordings', (schema) => { const target = schema.first(Resource.TARGET); const archives = schema.all(Resource.ARCHIVE).models; @@ -200,7 +199,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { websocket.send(JSON.stringify(msg)); return new Response(200); }); - this.post('api/v1/targets/:targetId/recordings', (schema, request) => { + this.post('api/v4/targets/:targetId/recordings', (schema, request) => { // Note: MirageJS will fake serialize FormData (i.e. FormData object is returned when accessing request.requestBody) const attrs = request.requestBody as any; @@ -246,8 +245,8 @@ export const startMirage = ({ environment = 'development' } = {}) => { ); return recording; }); - this.get('api/v1/targets/:targetId/recordings', (schema) => schema.all(Resource.RECORDING).models); - this.delete('api/v1/targets/:targetId/recordings/:recordingName', (schema, request) => { + this.get('api/v4/targets/:targetId/recordings', (schema) => schema.all(Resource.RECORDING).models); + this.delete('api/v4/targets/:targetId/recordings/:recordingName', (schema, request) => { const recordingName = request.params.recordingName; const recording = schema.findBy(Resource.RECORDING, { name: recordingName }); @@ -271,7 +270,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { websocket.send(JSON.stringify(msg)); return new Response(200); }); - this.patch('api/v1/targets/:targetId/recordings/:recordingName', (schema, request) => { + this.patch('api/v4/targets/:targetId/recordings/:recordingName', (schema, request) => { const body = request.requestBody; const recordingName = request.params.recordingName; const target = schema.findBy(Resource.TARGET, { connectUrl: request.params.targetId }); @@ -392,8 +391,8 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, ); }); - this.get('api/v1/targets/:targetId/recordingOptions', () => []); - this.get('api/v1/targets/:targetId/events', () => [ + this.get('api/v4/targets/:targetId/recordingOptions', () => []); + this.get('api/v4/targets/:targetId/events', () => [ { category: ['GC', 'Java Virtual Machine'], name: 'GC Heap Configuration', @@ -401,7 +400,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { description: 'The configuration of the garbage collected heap', }, ]); - this.get('api/v1/targets/:targetId/templates', () => [ + this.get('api/v4/targets/:targetId/templates', () => [ { name: 'Demo Template', provider: 'Demo', @@ -409,7 +408,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { description: 'This is not a real event template, but it is here!', }, ]); - this.get('api/v2/probes', () => []); + this.get('api/v4/probes', () => []); this.post('api/beta/matchExpressions', (_, request) => { const attr = JSON.parse(request.requestBody); if (!attr.matchExpression || !attr.targets) { @@ -423,7 +422,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, }; }); - this.post('api/v2/rules', (schema, request) => { + this.post('api/v4/rules', (schema, request) => { const attrs = JSON.parse(request.requestBody); const rule = schema.create(Resource.RULE, attrs); const msg = { @@ -440,10 +439,10 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, }; }); - this.get('api/v2/rules', (schema) => ({ + this.get('api/v4/rules', (schema) => ({ data: { result: schema.all(Resource.RULE).models }, })); - this.patch('api/v2/rules/:ruleName', (schema, request) => { + this.patch('api/v4/rules/:ruleName', (schema, request) => { const ruleName = request.params.ruleName; const patch = JSON.parse(request.requestBody); const rule = schema.findBy(Resource.RULE, { name: ruleName }); @@ -462,7 +461,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { websocket.send(JSON.stringify(msg)); return new Response(200); }); - this.delete('api/v2/rules/:ruleName', (schema, request) => { + this.delete('api/v4/rules/:ruleName', (schema, request) => { const ruleName = request.params.ruleName; const rule = schema.findBy(Resource.RULE, { name: ruleName }); @@ -481,7 +480,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { websocket.send(JSON.stringify(msg)); return new Response(200); }); - this.post('api/v2.2/credentials', (schema, request) => { + this.post('api/v4/credentials', (schema, request) => { const credential = schema.create(Resource.CREDENTIAL, { matchExpression: (request.requestBody as any).get('matchExpression'), numMatchingTargets: 0, @@ -501,9 +500,9 @@ export const startMirage = ({ environment = 'development' } = {}) => { ); return new Response(201); }); - this.get('api/v2.2/credentials', (schema) => ({ data: { result: schema.all(Resource.CREDENTIAL).models } })); - this.get('api/v2.2/credentials/:id', () => ({ data: { result: { matchExpression: '', targets: [] } } })); - this.post('api/v2.2/graphql', (schema, request) => { + this.get('api/v4/credentials', (schema) => ({ data: { result: schema.all(Resource.CREDENTIAL).models } })); + this.get('api/v4/credentials/:id', () => ({ data: { result: { matchExpression: '', targets: [] } } })); + this.post('api/v4/graphql', (schema, request) => { const body = JSON.parse(request.requestBody); const query = body.query.trim(); const variables = body.variables; @@ -732,7 +731,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { } return { data }; }); - this.get('api/v3/tls/certs', () => { + this.get('api/v4/tls/certs', () => { return new Response(200, {}, ['/truststore/additional-app.crt']); }); }, diff --git a/src/test/Shared/Services/Login.service.test.tsx b/src/test/Shared/Services/Login.service.test.tsx index f7c6e565a..ce6e72572 100644 --- a/src/test/Shared/Services/Login.service.test.tsx +++ b/src/test/Shared/Services/Login.service.test.tsx @@ -109,13 +109,13 @@ describe('Login.service', () => { it('should make expected API calls', async () => { await firstValueFrom(svc.setLoggedOut()); expect(mockFromFetch).toHaveBeenCalledTimes(2); - expect(mockFromFetch).toHaveBeenNthCalledWith(1, `./api/v2.1/auth`, { + expect(mockFromFetch).toHaveBeenNthCalledWith(1, `./api/v4/auth`, { credentials: 'include', mode: 'cors', method: 'POST', body: null, }); - expect(mockFromFetch).toHaveBeenNthCalledWith(2, `./api/v2.1/logout`, { + expect(mockFromFetch).toHaveBeenNthCalledWith(2, `./api/v4/logout`, { credentials: 'include', mode: 'cors', method: 'POST', @@ -210,7 +210,7 @@ describe('Login.service', () => { it('should make expected API calls', async () => { await firstValueFrom(svc.setLoggedOut()); expect(mockFromFetch).toHaveBeenCalledTimes(3); - expect(mockFromFetch).toHaveBeenNthCalledWith(1, `./api/v2.1/auth`, { + expect(mockFromFetch).toHaveBeenNthCalledWith(1, `./api/v4/auth`, { credentials: 'include', mode: 'cors', method: 'POST', @@ -219,7 +219,7 @@ describe('Login.service', () => { Authorization: `Bearer c2hhMjU2fmhlbGxvd29ybGQ`, }), }); - expect(mockFromFetch).toHaveBeenNthCalledWith(2, `./api/v2.1/logout`, { + expect(mockFromFetch).toHaveBeenNthCalledWith(2, `./api/v4/logout`, { credentials: 'include', mode: 'cors', method: 'POST', @@ -228,7 +228,7 @@ describe('Login.service', () => { Authorization: `Bearer c2hhMjU2fmhlbGxvd29ybGQ`, }), }); - expect(mockFromFetch).toHaveBeenNthCalledWith(3, `./api/v2.1/auth`, { + expect(mockFromFetch).toHaveBeenNthCalledWith(3, `./api/v4/auth`, { credentials: 'include', mode: 'cors', method: 'POST', From c7b920332280425b09872e0f52a9bc25edae88e8 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Tue, 20 Aug 2024 15:59:08 -0400 Subject: [PATCH 02/25] yarn format:apply --- src/mirage/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/mirage/index.ts b/src/mirage/index.ts index 21f9c292d..5109f429e 100644 --- a/src/mirage/index.ts +++ b/src/mirage/index.ts @@ -97,10 +97,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, ); }); - this.post( - 'api/v4/auth/token', - () => new Response(400, {}, 'Resource downloads are not supported in this demo'), - ); + this.post('api/v4/auth/token', () => new Response(400, {}, 'Resource downloads are not supported in this demo')); this.post('api/v4/targets', (schema, request) => { const params = request.queryParams; if (params['dryrun']) { From d0b61d1b0480d03aa7aece60184f6fb525306307 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Wed, 21 Aug 2024 09:55:08 -0400 Subject: [PATCH 03/25] typo --- src/app/Shared/Services/Login.service.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/Shared/Services/Login.service.tsx b/src/app/Shared/Services/Login.service.tsx index 235c06234..133676bc4 100644 --- a/src/app/Shared/Services/Login.service.tsx +++ b/src/app/Shared/Services/Login.service.tsx @@ -65,7 +65,7 @@ export class LoginService { } setLoggedOut(): Observable { - return fromFetch(`${this.authority}/api/v4logout`, { + return fromFetch(`${this.authority}/api/v4/logout`, { credentials: 'include', mode: 'cors', method: 'POST', From 4a85473a70b87b9110f7d6d56cdf1d5dedbaa640 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Wed, 21 Aug 2024 11:19:51 -0400 Subject: [PATCH 04/25] resolve failing tests --- .../AutomatedAnalysisConfigForm.tsx | 2 +- src/app/Rules/CreateRule.tsx | 2 +- src/app/Shared/Services/Api.service.tsx | 72 +++++++++---------- src/app/Shared/Services/api.types.ts | 3 +- src/app/Topology/Entity/EntityDetails.tsx | 2 +- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx index 6e35b17e7..51a9a24c2 100644 --- a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx @@ -105,7 +105,7 @@ export const AutomatedAnalysisConfigForm: React.FC( `targets/${encodeURIComponent(target?.connectUrl || '')}/templates`, - 'v1', + 'v4', undefined, undefined, true, diff --git a/src/app/Rules/CreateRule.tsx b/src/app/Rules/CreateRule.tsx index c6d72c336..c7ed83940 100644 --- a/src/app/Rules/CreateRule.tsx +++ b/src/app/Rules/CreateRule.tsx @@ -276,7 +276,7 @@ export const CreateRuleForm: React.FC = (_props) => { context.api .doGet( `targets/${encodeURIComponent(t.connectUrl)}/templates`, - 'v1', + 'v4', undefined, true, true, diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 4c0063685..db3bb2173 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -202,7 +202,7 @@ export class ApiService { credentials?.username && form.append('username', credentials.username); credentials?.password && form.append('password', credentials.password); return this.sendRequest( - 'v2', + 'v4', `targets`, { method: 'POST', @@ -226,7 +226,7 @@ export class ApiService { } deleteTarget(target: TargetStub): Observable { - return this.sendRequest('v2', `targets/${encodeURIComponent(target.connectUrl)}`, { + return this.sendRequest('v4', `targets/${encodeURIComponent(target.connectUrl)}`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -244,7 +244,7 @@ export class ApiService { const headers = {}; headers['Content-Type'] = 'application/json'; - return this.sendLegacyRequest('v2', 'rules', 'Rule Upload Failed', { + return this.sendLegacyRequest('v4', 'rules', 'Rule Upload Failed', { method: 'POST', body: JSON.stringify(rule), headers: headers, @@ -267,7 +267,7 @@ export class ApiService { createRule(rule: Rule): Observable { const headers = new Headers(); headers.set('Content-Type', 'application/json'); - return this.sendRequest('v2', 'rules', { + return this.sendRequest('v4', 'rules', { method: 'POST', body: JSON.stringify(rule), headers, @@ -282,7 +282,7 @@ export class ApiService { const headers = new Headers(); headers.set('Content-Type', 'application/json'); return this.sendRequest( - 'v2', + 'v4', `rules/${rule.name}`, { method: 'PATCH', @@ -298,7 +298,7 @@ export class ApiService { deleteRule(name: string, clean = true): Observable { return this.sendRequest( - 'v2', + 'v4', `rules/${name}`, { method: 'DELETE', @@ -348,7 +348,7 @@ export class ApiService { return this.target.target().pipe( concatMap((target) => - this.sendRequest('v1', `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings`, { + this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings`, { method: 'POST', body: form, }).pipe( @@ -375,7 +375,7 @@ export class ApiService { createSnapshot(): Observable { return this.target.target().pipe( concatMap((target) => - this.sendRequest('v1', `targets/${encodeURIComponent(target?.connectUrl || '')}/snapshot`, { + this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/snapshot`, { method: 'POST', }).pipe( tap((resp) => { @@ -394,10 +394,10 @@ export class ApiService { ); } - createSnapshotV2(): Observable { + createSnapshotv4(): Observable { return this.target.target().pipe( concatMap((target) => - this.sendRequest('v2', `targets/${encodeURIComponent(target?.connectUrl || '')}/snapshot`, { + this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/snapshot`, { method: 'POST', }).pipe( concatMap((resp) => resp.json() as Promise), @@ -417,7 +417,7 @@ export class ApiService { return this.target.target().pipe( concatMap((target) => this.sendRequest( - 'v1', + 'v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings/${encodeURIComponent(recordingName)}`, { method: 'PATCH', @@ -435,7 +435,7 @@ export class ApiService { return this.target.target().pipe( concatMap((target) => this.sendRequest( - 'v1', + 'v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings/${encodeURIComponent(recordingName)}`, { method: 'PATCH', @@ -453,7 +453,7 @@ export class ApiService { return this.target.target().pipe( concatMap((target) => this.sendRequest( - 'v1', + 'v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings/${encodeURIComponent(recordingName)}`, { method: 'DELETE', @@ -483,7 +483,7 @@ export class ApiService { return this.target.target().pipe( concatMap((target) => this.sendRequest( - 'v1', + 'v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings/${encodeURIComponent( recordingName, )}/upload`, @@ -619,7 +619,7 @@ export class ApiService { } deleteCustomEventTemplate(templateName: string): Observable { - return this.sendRequest('v1', `templates/${encodeURIComponent(templateName)}`, { + return this.sendRequest('v4', `templates/${encodeURIComponent(templateName)}`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -637,7 +637,7 @@ export class ApiService { const body = new window.FormData(); body.append('template', file); - return this.sendLegacyRequest('v1', 'templates', 'Template Upload Failed', { + return this.sendLegacyRequest('v4', 'templates', 'Template Upload Failed', { body: body, method: 'POST', headers: {}, @@ -660,7 +660,7 @@ export class ApiService { removeProbes(): Observable { return this.target.target().pipe( concatMap((target) => - this.sendRequest('v2', `targets/${encodeURIComponent(target?.connectUrl || '')}/probes`, { + this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/probes`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -675,7 +675,7 @@ export class ApiService { return this.target.target().pipe( concatMap((target) => this.sendRequest( - 'v2', + 'v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/probes/${encodeURIComponent(templateName)}`, { method: 'POST', @@ -706,7 +706,7 @@ export class ApiService { const body = new window.FormData(); body.append('probeTemplate', file); - return this.sendLegacyRequest('v2', `probes/${file.name}`, 'Custom Probe Template Upload Failed', { + return this.sendLegacyRequest('v4', `probes/${file.name}`, 'Custom Probe Template Upload Failed', { method: 'POST', body: body, headers: {}, @@ -727,7 +727,7 @@ export class ApiService { } deleteCustomProbeTemplate(templateName: string): Observable { - return this.sendRequest('v2', `probes/${encodeURIComponent(templateName)}`, { + return this.sendRequest('v4', `probes/${encodeURIComponent(templateName)}`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -754,7 +754,7 @@ export class ApiService { doGet( path: string, - apiVersion: ApiVersion = 'v1', + apiVersion: ApiVersion = 'v4', params?: URLSearchParams, suppressNotifications?: boolean, skipStatusCheck?: boolean, @@ -767,7 +767,7 @@ export class ApiService { } getProbeTemplates(): Observable { - return this.sendRequest('v2', 'probes', { method: 'GET' }).pipe( + return this.sendRequest('v4', 'probes', { method: 'GET' }).pipe( concatMap((resp) => resp.json()), map((response: ProbeTemplateResponse) => response.data.result), first(), @@ -778,7 +778,7 @@ export class ApiService { return this.target.target().pipe( concatMap((target) => this.sendRequest( - 'v2', + 'v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/probes`, { method: 'GET', @@ -800,7 +800,7 @@ export class ApiService { skipStatusCheck = false, ): Observable { return this.sendRequest( - 'v2', + 'v4', `targets/${encodeURIComponent(target.connectUrl)}/probes`, { method: 'GET', @@ -825,7 +825,7 @@ export class ApiService { headers.set('Content-Type', 'application/json'); const req = () => this.sendRequest( - 'v2.2', + 'v4', 'graphql', { method: 'POST', @@ -877,7 +877,7 @@ export class ApiService { } downloadRule(name: string): void { - this.doGet('rules/' + name, 'v2') + this.doGet('rules/' + name, 'v4') .pipe( first(), map((resp) => resp.data.result), @@ -903,7 +903,7 @@ export class ApiService { body.append('recording', file); body.append('labels', JSON.stringify(labels)); - return this.sendLegacyRequest('v1', 'recordings', 'Recording Upload Failed', { + return this.sendLegacyRequest('v4', 'recordings', 'Recording Upload Failed', { method: 'POST', body: body, headers: {}, @@ -937,7 +937,7 @@ export class ApiService { const body = new window.FormData(); body.append('cert', file); - return this.sendLegacyRequest('v2', 'certificates', 'Certificate Upload Failed', { + return this.sendLegacyRequest('v4', 'certificates', 'Certificate Upload Failed', { method: 'POST', body, headers: {}, @@ -1066,7 +1066,7 @@ export class ApiService { body.append('username', username); body.append('password', password); - return this.sendRequest('v2.2', 'credentials', { + return this.sendRequest('v4', 'credentials', { method: 'POST', body, }).pipe( @@ -1077,7 +1077,7 @@ export class ApiService { } getCredential(id: number): Observable { - return this.sendRequest('v2.2', `credentials/${id}`, { + return this.sendRequest('v4', `credentials/${id}`, { method: 'GET', }).pipe( concatMap((resp) => resp.json()), @@ -1088,7 +1088,7 @@ export class ApiService { getCredentials(suppressNotifications = false, skipStatusCheck = false): Observable { return this.sendRequest( - 'v2.2', + 'v4', `credentials`, { method: 'GET', @@ -1104,7 +1104,7 @@ export class ApiService { } deleteCredentials(id: number): Observable { - return this.sendRequest('v2.2', `credentials/${id}`, { + return this.sendRequest('v4', `credentials/${id}`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -1114,7 +1114,7 @@ export class ApiService { getRules(suppressNotifications = false, skipStatusCheck = false): Observable { return this.sendRequest( - 'v2', + 'v4', 'rules', { method: 'GET', @@ -1354,7 +1354,7 @@ export class ApiService { getTargetActiveRecordings(target: TargetStub): Observable { return this.doGet( `targets/${encodeURIComponent(target.connectUrl)}/recordings`, - 'v1', + 'v4', undefined, true, true, @@ -1364,7 +1364,7 @@ export class ApiService { getTargetEventTemplates(target: TargetStub): Observable { return this.doGet( `targets/${encodeURIComponent(target.connectUrl)}/templates`, - 'v1', + 'v4', undefined, true, true, @@ -1374,7 +1374,7 @@ export class ApiService { getTargetEventTypes(target: TargetStub): Observable { return this.doGet( `targets/${encodeURIComponent(target.connectUrl)}/events`, - 'v1', + 'v4', undefined, true, true, diff --git a/src/app/Shared/Services/api.types.ts b/src/app/Shared/Services/api.types.ts index a36f5b015..5e276390c 100644 --- a/src/app/Shared/Services/api.types.ts +++ b/src/app/Shared/Services/api.types.ts @@ -18,7 +18,8 @@ import { AlertVariant } from '@patternfly/react-core'; import _ from 'lodash'; import { Observable } from 'rxjs'; -export type ApiVersion = 'v1' | 'v2' | 'v2.1' | 'v2.2' | 'v2.3' | 'v2.4' | 'v3' | 'beta'; +export type ApiVersion = 'v4' | 'beta'; + // ====================================== // Common Resources // ====================================== diff --git a/src/app/Topology/Entity/EntityDetails.tsx b/src/app/Topology/Entity/EntityDetails.tsx index 0ac8bc4b0..0ea97d5f9 100644 --- a/src/app/Topology/Entity/EntityDetails.tsx +++ b/src/app/Topology/Entity/EntityDetails.tsx @@ -464,7 +464,7 @@ export const TargetResources: React.FC<{ targetNode: TargetNode }> = ({ targetNo const checkIfAgentDetected = React.useCallback(() => { addSubscription( context.api - .doGet(`targets/${encodeURIComponent(target.connectUrl)}/probes`, 'v2', undefined, true, true) + .doGet(`targets/${encodeURIComponent(target.connectUrl)}/probes`, 'v4', undefined, true, true) .pipe( concatMap(() => of(true)), catchError(() => of(false)), From d9085cada6780df153141487a469ad35175bad7c Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Wed, 21 Aug 2024 11:30:22 -0400 Subject: [PATCH 05/25] resolve type-check --- src/app/SecurityPanel/ImportCertificate.tsx | 2 +- src/app/Shared/Services/Api.service.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/SecurityPanel/ImportCertificate.tsx b/src/app/SecurityPanel/ImportCertificate.tsx index c38316769..1c4354083 100644 --- a/src/app/SecurityPanel/ImportCertificate.tsx +++ b/src/app/SecurityPanel/ImportCertificate.tsx @@ -52,7 +52,7 @@ export const CertificateImport: React.FC = () => { setLoading(true); addSubscription( context.api - .doGet('tls/certs', 'v3') + .doGet('tls/certs', 'v4') .pipe(tap((_) => setLoading(false))) .subscribe(setCerts), ); diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index db3bb2173..1b451b34f 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -1130,7 +1130,7 @@ export class ApiService { } getDiscoveryTree(): Observable { - return this.sendRequest('v3', 'discovery', { + return this.sendRequest('v4', 'discovery', { method: 'GET', }).pipe( concatMap((resp) => resp.json()), From eece5b8b8ccd5f6de8b9f7a5fa94cf99e296ad02 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 3 Sep 2024 16:58:32 -0400 Subject: [PATCH 06/25] remove V2Response type --- src/app/Shared/Services/Api.service.tsx | 23 +- src/app/Shared/Services/Login.service.tsx | 6 +- src/app/Shared/Services/api.types.ts | 65 ---- .../Shared/Services/Login.service.test.tsx | 367 +++--------------- 4 files changed, 66 insertions(+), 395 deletions(-) diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 1b451b34f..9949b0768 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -39,22 +39,15 @@ import { Rule, RecordingAttributes, ActiveRecording, - RecordingResponse, ApiVersion, ProbeTemplate, - ProbeTemplateResponse, EventProbe, - EventProbesResponse, Recording, EventTemplate, - RuleResponse, ArchivedRecording, UPLOADS_SUBDIRECTORY, MatchedCredential, - CredentialResponse, StoredCredential, - CredentialsResponse, - RulesResponse, EnvironmentNode, ActiveRecordingsFilterInput, RecordingCountResponse, @@ -400,8 +393,7 @@ export class ApiService { this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/snapshot`, { method: 'POST', }).pipe( - concatMap((resp) => resp.json() as Promise), - map((response) => response.data.result), + concatMap((resp) => resp.json() as Promise), catchError((_) => of(undefined)), first(), ), @@ -769,7 +761,6 @@ export class ApiService { getProbeTemplates(): Observable { return this.sendRequest('v4', 'probes', { method: 'GET' }).pipe( concatMap((resp) => resp.json()), - map((response: ProbeTemplateResponse) => response.data.result), first(), ); } @@ -787,7 +778,6 @@ export class ApiService { suppressNotifications, ).pipe( concatMap((resp) => resp.json()), - map((response: EventProbesResponse) => response.data.result), first(), ), ), @@ -810,7 +800,6 @@ export class ApiService { skipStatusCheck, ).pipe( concatMap((resp) => resp.json()), - map((response: EventProbesResponse) => response.data.result), first(), ); } @@ -877,11 +866,8 @@ export class ApiService { } downloadRule(name: string): void { - this.doGet('rules/' + name, 'v4') - .pipe( - first(), - map((resp) => resp.data.result), - ) + this.doGet('rules/' + name, 'v4') + .pipe(first()) .subscribe((rule) => { const filename = `${rule.name}.json`; const file = new File([JSON.stringify(rule)], filename); @@ -1081,7 +1067,6 @@ export class ApiService { method: 'GET', }).pipe( concatMap((resp) => resp.json()), - map((response: CredentialResponse) => response.data.result), first(), ); } @@ -1098,7 +1083,6 @@ export class ApiService { skipStatusCheck, ).pipe( concatMap((resp) => resp.json()), - map((response: CredentialsResponse) => response.data.result), first(), ); } @@ -1124,7 +1108,6 @@ export class ApiService { skipStatusCheck, ).pipe( concatMap((resp) => resp.json()), - map((response: RulesResponse) => response.data.result), first(), ); } diff --git a/src/app/Shared/Services/Login.service.tsx b/src/app/Shared/Services/Login.service.tsx index 133676bc4..e18051439 100644 --- a/src/app/Shared/Services/Login.service.tsx +++ b/src/app/Shared/Services/Login.service.tsx @@ -39,14 +39,14 @@ export class LoginService { concatMap((response) => { let gapAuth = response?.headers?.get('Gap-Auth'); if (gapAuth) { - return new Promise((r) => r({ data: { result: { username: gapAuth } } } as any)); + return new Promise((r) => r({ username: gapAuth } as any)); } return response.json(); }), - catchError(() => of({ data: { result: { username: '' } } } as any)), + catchError(() => of({ username: '' } as any)), ) .subscribe((v) => { - this.username.next(v?.data?.result?.username ?? ''); + this.username.next(v?.username ?? ''); }); } diff --git a/src/app/Shared/Services/api.types.ts b/src/app/Shared/Services/api.types.ts index 5e276390c..31edd2cb2 100644 --- a/src/app/Shared/Services/api.types.ts +++ b/src/app/Shared/Services/api.types.ts @@ -58,22 +58,6 @@ export function isTargetMetadata(metadata: Metadata | TargetMetadata): metadata return (metadata as TargetMetadata).annotations !== undefined; } -export interface ApiV2Response { - meta: { - status: string; - type: string; - }; - data: unknown; -} - -export interface AssetJwtResponse extends ApiV2Response { - data: { - result: { - resourceUrl: string; - }; - }; -} - export type SimpleResponse = Pick; export interface XMLHttpResponse { @@ -146,13 +130,6 @@ export interface HealthGetResponse { // ====================================== // Auth Resources // ====================================== -export interface AuthV2Response extends ApiV2Response { - data: { - result: { - username: string; - }; - }; -} // ====================================== // MBean metric resources @@ -289,12 +266,6 @@ export interface ActiveRecordingsFilterInput { */ export const UPLOADS_SUBDIRECTORY = 'uploads'; -export interface RecordingResponse extends ApiV2Response { - data: { - result: ActiveRecording; - }; -} - export interface RecordingCountResponse { data: { targetNodes: { @@ -323,18 +294,6 @@ export interface MatchedCredential { targets: Target[]; } -export interface CredentialResponse extends ApiV2Response { - data: { - result: MatchedCredential; - }; -} - -export interface CredentialsResponse extends ApiV2Response { - data: { - result: StoredCredential[]; - }; -} - // ====================================== // Agent-related resources // ====================================== @@ -359,18 +318,6 @@ export interface EventProbe { fields: string; } -export interface ProbeTemplateResponse extends ApiV2Response { - data: { - result: ProbeTemplate[]; - }; -} - -export interface EventProbesResponse extends ApiV2Response { - data: { - result: EventProbe[]; - }; -} - // ====================================== // Rule resources // ====================================== @@ -387,18 +334,6 @@ export interface Rule { maxSizeBytes: number; } -export interface RulesResponse extends ApiV2Response { - data: { - result: Rule[]; - }; -} - -export interface RuleResponse extends ApiV2Response { - data: { - result: Rule; - }; -} - // ====================================== // Template resources // ====================================== diff --git a/src/test/Shared/Services/Login.service.test.tsx b/src/test/Shared/Services/Login.service.test.tsx index ce6e72572..f8879757d 100644 --- a/src/test/Shared/Services/Login.service.test.tsx +++ b/src/test/Shared/Services/Login.service.test.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { ApiV2Response } from '@app/Shared/Services/api.types'; import { LoginService } from '@app/Shared/Services/Login.service'; import { SessionState } from '@app/Shared/Services/service.types'; import { SettingsService } from '@app/Shared/Services/Settings.service'; @@ -69,326 +68,80 @@ describe('Login.service', () => { jest.restoreAllMocks(); }); - describe('with Basic AuthMethod', () => { - beforeEach(async () => { - const initAuthResp = createResponse(401, false, new Headers({ 'X-WWW-Authenticate': 'Basic' }), { - meta: { - type: 'text/plain', - status: 'Unauthorized', - }, - data: { - reason: 'HTTP Authorization Failure', - }, - }); - const authResp = createResponse(200, true, new Headers({ 'X-WWW-Authenticate': 'Basic' }), { - meta: { - type: 'application/json', - status: 'OK', - }, - data: { - result: { - username: 'user', - }, - }, - }); - const logoutResp = createResponse(200, true); - mockFromFetch - .mockReturnValueOnce(of(initAuthResp)) - .mockReturnValueOnce(of(authResp)) - .mockReturnValueOnce(of(logoutResp)); - window.location.href = 'https://example.com/'; - location.href = window.location.href; - svc = new LoginService(settingsSvc); - }); - - xit('should emit true', async () => { - const result = await firstValueFrom(svc.setLoggedOut()); - expect(result).toBeTruthy(); - }); - - it('should make expected API calls', async () => { - await firstValueFrom(svc.setLoggedOut()); - expect(mockFromFetch).toHaveBeenCalledTimes(2); - expect(mockFromFetch).toHaveBeenNthCalledWith(1, `./api/v4/auth`, { - credentials: 'include', - mode: 'cors', - method: 'POST', - body: null, - }); - expect(mockFromFetch).toHaveBeenNthCalledWith(2, `./api/v4/logout`, { - credentials: 'include', - mode: 'cors', - method: 'POST', - body: null, - }); - }); - - it('should emit logged-out', async () => { - await firstValueFrom(svc.setLoggedOut()); - await firstValueFrom(svc.loggedOut().pipe(timeout({ first: 1000 }))); + beforeEach(async () => { + const initAuthResp = createResponse(401, false, new Headers({ 'X-WWW-Authenticate': 'Basic' }), { + meta: { + type: 'text/plain', + status: 'Unauthorized', + }, + data: { + reason: 'HTTP Authorization Failure', + }, }); - - it('should reset session state', async () => { - const beforeState = await firstValueFrom(svc.getSessionState()); - expect(beforeState).toEqual(SessionState.CREATING_USER_SESSION); - await firstValueFrom(svc.setLoggedOut()); - const afterState = await firstValueFrom(svc.getSessionState()); - expect(afterState).toEqual(SessionState.NO_USER_SESSION); - }); - - it('should redirect to login page', async () => { - await firstValueFrom(svc.setLoggedOut()); - expect(window.location.href).toEqual('/'); + const authResp = createResponse(200, true, new Headers({ 'X-WWW-Authenticate': 'Basic' }), { + meta: { + type: 'application/json', + status: 'OK', + }, + data: { + result: { + username: 'user', + }, + }, }); + const logoutResp = createResponse(200, true); + mockFromFetch + .mockReturnValueOnce(of(initAuthResp)) + .mockReturnValueOnce(of(authResp)) + .mockReturnValueOnce(of(logoutResp)); + window.location.href = 'https://example.com/'; + location.href = window.location.href; + svc = new LoginService(settingsSvc); }); - xdescribe('with Bearer AuthMethod', () => { - let authResp: Response; - let logoutResp: Response; - let authRedirectResp: Response; - let submitSpy: jest.SpyInstance; - - beforeEach(async () => { - authResp = createResponse(200, true, new Headers({ 'X-WWW-Authenticate': 'Bearer' }), { - meta: { - type: 'application/json', - status: 'OK', - }, - data: { - result: { - username: 'kube:admin', - }, - }, - }); - logoutResp = createResponse( - 302, - true, - new Headers({ - 'X-Location': 'https://oauth-server.example.com/logout', - 'access-control-expose-headers': 'Location', - }), - ); - authRedirectResp = createResponse( - 302, - true, - new Headers({ - 'X-Location': - 'https://oauth-server.example.com/oauth/authorize?client_id=system%3Aserviceaccount%3Amy-namespace%3Amy-cryostat&response_type=token&response_mode=fragment&scope=user%3Acheck-access+role%3Acryostat-operator-oauth-client%3Amy-namespace', - 'access-control-expose-headers': 'Location', - }), - { - meta: { - type: 'application/json', - status: 'Found', - }, - data: { - result: undefined, - }, - }, - ); - // Submit is unimplemented in JSDOM - submitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation(); + it('should emit true', async () => { + const result = await firstValueFrom(svc.setLoggedOut()); + expect(result).toBeTruthy(); + }); - mockFromFetch.mockReturnValueOnce(of(authResp)); - const token = 'sha256~helloworld'; - window.location.href = 'https://example.com/#token_type=Bearer&access_token=' + token; - location.hash = 'token_type=Bearer&access_token=' + token; - svc = new LoginService(settingsSvc); - expect(mockFromFetch).toBeCalledTimes(1); + it('should make expected API calls', async () => { + await firstValueFrom(svc.setLoggedOut()); + expect(mockFromFetch).toHaveBeenCalledTimes(2); + expect(mockFromFetch).toHaveBeenNthCalledWith(1, `./api/v4/auth`, { + credentials: 'include', + mode: 'cors', + method: 'POST', + body: null, }); - - describe('with no errors', () => { - beforeEach(async () => { - mockFromFetch.mockReturnValueOnce(of(logoutResp)).mockReturnValueOnce(of(authRedirectResp)); - }); - - it('should emit true', async () => { - const result = await firstValueFrom(svc.setLoggedOut()); - expect(result).toBeTruthy(); - }); - - it('should make expected API calls', async () => { - await firstValueFrom(svc.setLoggedOut()); - expect(mockFromFetch).toHaveBeenCalledTimes(3); - expect(mockFromFetch).toHaveBeenNthCalledWith(1, `./api/v4/auth`, { - credentials: 'include', - mode: 'cors', - method: 'POST', - body: null, - headers: new Headers({ - Authorization: `Bearer c2hhMjU2fmhlbGxvd29ybGQ`, - }), - }); - expect(mockFromFetch).toHaveBeenNthCalledWith(2, `./api/v4/logout`, { - credentials: 'include', - mode: 'cors', - method: 'POST', - body: null, - headers: new Headers({ - Authorization: `Bearer c2hhMjU2fmhlbGxvd29ybGQ`, - }), - }); - expect(mockFromFetch).toHaveBeenNthCalledWith(3, `./api/v4/auth`, { - credentials: 'include', - mode: 'cors', - method: 'POST', - body: null, - }); - }); - - it('should submit a form to the OAuth server', async () => { - await firstValueFrom(svc.setLoggedOut()); - const rawForm = document.getElementById('logoutForm'); - expect(rawForm).toBeInTheDocument(); - expect(rawForm).toBeInstanceOf(HTMLFormElement); - const form = rawForm as HTMLFormElement; - expect(form.action).toEqual('https://oauth-server.example.com/logout'); - expect(form.method.toUpperCase()).toEqual('POST'); - - expect(form.childElementCount).toBe(1); - const rawInput = form.firstChild; - expect(rawInput).toBeInstanceOf(HTMLInputElement); - const input = rawInput as HTMLInputElement; - expect(input.value).toEqual( - '/oauth/authorize?client_id=system%3Aserviceaccount%3Amy-namespace%3Amy-cryostat&response_type=token&response_mode=fragment&scope=user%3Acheck-access+role%3Acryostat-operator-oauth-client%3Amy-namespace', - ); - expect(input.name).toEqual('then'); - expect(input.type).toEqual('hidden'); - - expect(document.body).toContainElement(form); - expect(submitSpy).toHaveBeenCalled(); - }); - - it('should emit logged-out', async () => { - await firstValueFrom(svc.setLoggedOut()); - await firstValueFrom(svc.loggedOut().pipe(timeout({ first: 1000 }))); - }); - - it('should reset session state', async () => { - const beforeState = await firstValueFrom(svc.getSessionState()); - expect(beforeState).toEqual(SessionState.CREATING_USER_SESSION); - await firstValueFrom(svc.setLoggedOut()); - const afterState = await firstValueFrom(svc.getSessionState()); - expect(afterState).toEqual(SessionState.NO_USER_SESSION); - }); + expect(mockFromFetch).toHaveBeenNthCalledWith(2, `./api/v4/logout`, { + credentials: 'include', + mode: 'cors', + method: 'POST', + body: null, }); + }); - describe('with errors', () => { - let logSpy: jest.SpyInstance; - beforeEach(() => { - logSpy = jest.spyOn(window.console, 'error').mockImplementation(); - }); - - describe('backend logout returns non-302 response', () => { - beforeEach(() => { - const badLogoutResp = createResponse( - 200, - true, - new Headers({ - 'X-Location': 'https://oauth-server.example.com/logout', - 'access-control-expose-headers': 'Location', - }), - ); - mockFromFetch.mockReturnValueOnce(of(badLogoutResp)).mockReturnValueOnce(of(authRedirectResp)); - }); - - it('should fail to log out', async () => { - const result = await firstValueFrom(svc.setLoggedOut()); - expect(result).toBeFalsy(); - expect(logSpy).toHaveBeenCalledWith( - expect.stringContaining('"message":"Could not find OAuth logout endpoint"'), - ); - }); - }); - - describe('backend logout returns 302 response without X-Location header', () => { - beforeEach(() => { - const badLogoutResp = createResponse( - 302, - true, - new Headers({ - 'access-control-expose-headers': 'Location', - }), - ); - mockFromFetch.mockReturnValueOnce(of(badLogoutResp)).mockReturnValueOnce(of(authRedirectResp)); - }); - - it('should fail to log out', async () => { - const result = await firstValueFrom(svc.setLoggedOut()); - expect(result).toBeFalsy(); - expect(logSpy).toHaveBeenCalledWith( - expect.stringContaining('"message":"Could not find OAuth logout endpoint"'), - ); - }); - }); - - describe('backend auth returns non-302 response', () => { - beforeEach(() => { - const badAuthRedirectResp = createResponse( - 200, - true, - new Headers({ - 'X-Location': - 'https://oauth-server.example.com/oauth/authorize?client_id=system%3Aserviceaccount%3Amy-namespace%3Amy-cryostat&response_type=token&response_mode=fragment&scope=user%3Acheck-access+role%3Acryostat-operator-oauth-client%3Amy-namespace', - 'access-control-expose-headers': 'Location', - }), - { - meta: { - type: 'application/json', - status: 'OK', - }, - data: { - result: undefined, - }, - }, - ); - mockFromFetch.mockReturnValueOnce(of(logoutResp)).mockReturnValueOnce(of(badAuthRedirectResp)); - }); - - it('should fail to log out', async () => { - const result = await firstValueFrom(svc.setLoggedOut()); - expect(result).toBeFalsy(); - expect(logSpy).toHaveBeenCalledWith( - expect.stringContaining('"message":"Could not find OAuth login endpoint"'), - ); - }); - }); + it('should emit logged-out', async () => { + await firstValueFrom(svc.setLoggedOut()); + await firstValueFrom(svc.loggedOut().pipe(timeout({ first: 1000 }))); + }); - describe('backend auth returns 302 response without X-Location header', () => { - beforeEach(() => { - const badAuthRedirectResp = createResponse( - 302, - true, - new Headers({ - 'access-control-expose-headers': 'Location', - }), - { - meta: { - type: 'application/json', - status: 'Found', - }, - data: { - result: undefined, - }, - }, - ); - mockFromFetch.mockReturnValueOnce(of(logoutResp)).mockReturnValueOnce(of(badAuthRedirectResp)); - }); + it('should reset session state', async () => { + const beforeState = await firstValueFrom(svc.getSessionState()); + expect(beforeState).toEqual(SessionState.CREATING_USER_SESSION); + await firstValueFrom(svc.setLoggedOut()); + const afterState = await firstValueFrom(svc.getSessionState()); + expect(afterState).toEqual(SessionState.NO_USER_SESSION); + }); - it('should fail to log out', async () => { - const result = await firstValueFrom(svc.setLoggedOut()); - expect(result).toBeFalsy(); - expect(logSpy).toHaveBeenCalledWith( - expect.stringContaining('"message":"Could not find OAuth login endpoint"'), - ); - }); - }); - }); + it('should redirect to login page', async () => { + await firstValueFrom(svc.setLoggedOut()); + expect(window.location.href).toEqual('/'); }); }); }); -function createResponse(status: number, ok: boolean, headers?: Headers, jsonBody?: ApiV2Response): Response { +function createResponse(status: number, ok: boolean, headers?: Headers, jsonBody?: any): Response { return { status: status, ok: ok, From 6d00dd789f88947d3da81a3c4fda6b38b663be24 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 3 Sep 2024 17:12:36 -0400 Subject: [PATCH 07/25] target creation cleanup --- src/app/Shared/Services/Api.service.tsx | 23 ++++++----------------- src/app/Topology/Actions/CreateTarget.tsx | 12 ++++++------ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 9949b0768..f1cdb3356 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -186,7 +186,7 @@ export class ApiService { credentials?: { username?: string; password?: string }, storeCredentials = false, dryrun = false, - ): Observable<{ status: number; body: object }> { + ): Observable { const form = new window.FormData(); form.append('connectUrl', target.connectUrl); if (target.alias && target.alias.trim()) { @@ -206,14 +206,9 @@ export class ApiService { true, ).pipe( first(), - concatMap((resp) => resp.json().then((body) => ({ status: resp.status, body: body as object }))), - catchError((err: Error) => { - if (isHttpError(err)) { - return from( - err.httpResponse.json().then((body) => ({ status: err.httpResponse.status, body: body as object })), - ); - } - return of({ status: 0, body: { data: { reason: err.message } } }); // Status 0 -> request is not completed + map((resp) => resp.ok), + catchError((_) => { + return of(false); }), ); } @@ -1144,7 +1139,6 @@ export class ApiService { ).pipe( first(), concatMap((resp: Response) => resp.json()), - map((body): Target[] => body.data.result.targets || []), ); } @@ -1250,7 +1244,7 @@ export class ApiService { first(), concatMap((resp) => resp.json()), map((body) => { - const result: string | undefined = body?.data?.result; + const result: string | undefined = body; switch (result?.toUpperCase()) { case 'FAILURE': return { error: new Error('Invalid username or password.'), severeLevel: ValidatedOptions.error }; @@ -1562,12 +1556,7 @@ export class ApiService { } else { Promise.resolve(error.xmlHttpResponse.body as string).then((detail) => { if (!suppressNotifications) { - try { - const body = JSON.parse(detail).data.reason; - this.notifications.danger(title, body); - } catch { - this.notifications.danger(title, detail); - } + this.notifications.danger(title, detail); } }); } diff --git a/src/app/Topology/Actions/CreateTarget.tsx b/src/app/Topology/Actions/CreateTarget.tsx index 39170a144..8edf391e4 100644 --- a/src/app/Topology/Actions/CreateTarget.tsx +++ b/src/app/Topology/Actions/CreateTarget.tsx @@ -179,13 +179,13 @@ export const CreateTarget: React.FC = ({ prefilled }) => { credentials, true, ) - .subscribe(({ status, body }) => { + .subscribe((success) => { setLoading(false); - const option = isHttpOk(status) ? ValidatedOptions.success : ValidatedOptions.error; + const option = success ? ValidatedOptions.success : ValidatedOptions.error; if (option === ValidatedOptions.success) { exitForm(); } else { - let errorMessage = (body as any)?.data?.reason || 'Connection test failure'; + let errorMessage = 'Connection test failure'; setValidation({ option: option, errorMessage, @@ -210,12 +210,12 @@ export const CreateTarget: React.FC = ({ prefilled }) => { false, true, ) - .subscribe(({ status, body }) => { + .subscribe((success) => { setTesting(false); - const option = isHttpOk(status) ? ValidatedOptions.success : ValidatedOptions.error; + const option = success ? ValidatedOptions.success : ValidatedOptions.error; setValidation({ option: option, - errorMessage: option !== ValidatedOptions.success ? body['data']['reason'] : '', + errorMessage: option !== ValidatedOptions.success ? '' : 'Connection test failure', }); }), ); From 3c90d7a65dfceb1cce39506d918f53453a49b6bd Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 4 Sep 2024 10:18:00 -0400 Subject: [PATCH 08/25] most things moved over to v4. automated analysis still assumes a fixed recording name --- .../CreateRecording/CustomRecordingForm.tsx | 6 +- .../CreateRecording/SnapshotRecordingForm.tsx | 4 +- .../AutomatedAnalysisCard.tsx | 1 + .../AutomatedAnalysisConfigForm.tsx | 8 +- .../Charts/jfr/JFRMetricsChartController.tsx | 1 + src/app/Events/EventTemplates.tsx | 4 +- src/app/Events/EventTypes.tsx | 4 +- src/app/RecordingMetadata/BulkEditLabels.tsx | 4 +- src/app/Recordings/ActiveRecordingsTable.tsx | 12 +- src/app/Rules/CreateRule.tsx | 14 +- src/app/SecurityPanel/ImportCertificate.tsx | 2 +- src/app/Shared/Services/Api.service.tsx | 177 ++++++------------ src/app/Shared/Services/api.types.ts | 1 + src/app/Topology/Entity/EntityDetails.tsx | 2 +- src/test/Events/EventTemplates.test.tsx | 2 +- src/test/Events/EventTypes.test.tsx | 2 +- .../RecordingMetadata/BulkEditLabels.test.tsx | 2 +- .../Recordings/ActiveRecordingsTable.test.tsx | 2 +- 18 files changed, 84 insertions(+), 164 deletions(-) diff --git a/src/app/CreateRecording/CustomRecordingForm.tsx b/src/app/CreateRecording/CustomRecordingForm.tsx index 8d52831b7..ebeca872f 100644 --- a/src/app/CreateRecording/CustomRecordingForm.tsx +++ b/src/app/CreateRecording/CustomRecordingForm.tsx @@ -273,10 +273,8 @@ export const CustomRecordingForm: React.FC = () => { } addSubscription( forkJoin({ - templates: context.api.doGet(`targets/${encodeURIComponent(target.connectUrl)}/templates`), - recordingOptions: context.api.doGet( - `targets/${encodeURIComponent(target.connectUrl)}/recordingOptions`, - ), + templates: context.api.doGet(`targets/${target.id}/event_templates`), + recordingOptions: context.api.doGet(`targets/${target.id}/recordingOptions`), }).subscribe({ next: ({ templates, recordingOptions }) => { setErrorMessage(''); diff --git a/src/app/CreateRecording/SnapshotRecordingForm.tsx b/src/app/CreateRecording/SnapshotRecordingForm.tsx index 18f21952c..24568999b 100644 --- a/src/app/CreateRecording/SnapshotRecordingForm.tsx +++ b/src/app/CreateRecording/SnapshotRecordingForm.tsx @@ -40,9 +40,9 @@ export const SnapshotRecordingForm: React.FC = (_) = context.api .createSnapshot() .pipe(first()) - .subscribe((success) => { + .subscribe((result) => { setLoading(false); - if (success) { + if (result) { exitForm(); } }), diff --git a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx index 861e7c3bf..f353d6756 100644 --- a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx @@ -528,6 +528,7 @@ export const AutomatedAnalysisCard: DashboardCardFC if (usingCachedReport) { generateReport(); } else { + // FIXME this should use a GraphQL query addSubscription( context.api.deleteRecording('automated-analysis').subscribe({ next: () => { diff --git a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx index 51a9a24c2..454072f91 100644 --- a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx @@ -103,13 +103,7 @@ export const AutomatedAnalysisConfigForm: React.FC !target, of([]), context.api - .doGet( - `targets/${encodeURIComponent(target?.connectUrl || '')}/templates`, - 'v4', - undefined, - undefined, - true, - ) + .doGet(`targets/${target!.id}/event_templates`, undefined, undefined, true) .pipe(first()), ).subscribe({ next: (templates: EventTemplate[]) => { diff --git a/src/app/Dashboard/Charts/jfr/JFRMetricsChartController.tsx b/src/app/Dashboard/Charts/jfr/JFRMetricsChartController.tsx index 44987bdac..713ddeb34 100644 --- a/src/app/Dashboard/Charts/jfr/JFRMetricsChartController.tsx +++ b/src/app/Dashboard/Charts/jfr/JFRMetricsChartController.tsx @@ -116,6 +116,7 @@ export class JFRMetricsChartController { this._state$.next(v ? ControllerState.READY : ControllerState.NO_DATA); if (v) { this._api + // FIXME this should retrieve the ID given the recording name by using a GraphQL query .uploadActiveRecordingToGrafana(RECORDING_NAME) .pipe(first()) .subscribe((_) => { diff --git a/src/app/Events/EventTemplates.tsx b/src/app/Events/EventTemplates.tsx index 7563e724a..88cb453fb 100644 --- a/src/app/Events/EventTemplates.tsx +++ b/src/app/Events/EventTemplates.tsx @@ -167,9 +167,7 @@ export const EventTemplates: React.FC = () => { .pipe( filter((target) => !!target), first(), - concatMap((target: Target) => - context.api.doGet(`targets/${encodeURIComponent(target.connectUrl)}/templates`), - ), + concatMap((target: Target) => context.api.getTargetEventTemplates(target)), ) .subscribe({ next: handleTemplates, diff --git a/src/app/Events/EventTypes.tsx b/src/app/Events/EventTypes.tsx index bebb94c60..8f2b17bce 100644 --- a/src/app/Events/EventTypes.tsx +++ b/src/app/Events/EventTypes.tsx @@ -123,9 +123,7 @@ export const EventTypes: React.FC = () => { .pipe( filter((target) => !!target), first(), - concatMap((target: Target) => - context.api.doGet(`targets/${encodeURIComponent(target.connectUrl)}/events`), - ), + concatMap((target: Target) => context.api.getTargetEventTypes(target)), ) .subscribe({ next: handleTypes, diff --git a/src/app/RecordingMetadata/BulkEditLabels.tsx b/src/app/RecordingMetadata/BulkEditLabels.tsx index 1d6944777..60aba36cf 100644 --- a/src/app/RecordingMetadata/BulkEditLabels.tsx +++ b/src/app/RecordingMetadata/BulkEditLabels.tsx @@ -160,9 +160,7 @@ export const BulkEditLabels: React.FC = ({ } else if (isTargetRecording) { observable = context.target.target().pipe( filter((target) => !!target), - concatMap((target: Target) => - context.api.doGet(`targets/${encodeURIComponent(target.connectUrl)}/recordings`), - ), + concatMap((target: Target) => context.api.getTargetActiveRecordings(target)), first(), ); } else { diff --git a/src/app/Recordings/ActiveRecordingsTable.tsx b/src/app/Recordings/ActiveRecordingsTable.tsx index 3ff3955be..ce68b8941 100644 --- a/src/app/Recordings/ActiveRecordingsTable.tsx +++ b/src/app/Recordings/ActiveRecordingsTable.tsx @@ -228,9 +228,7 @@ export const ActiveRecordingsTable: React.FC = (prop .target() .pipe( filter((target) => !!target), - concatMap((target: Target) => - context.api.doGet(`targets/${encodeURIComponent(target.connectUrl)}/recordings`), - ), + concatMap((target: Target) => context.api.getTargetActiveRecordings(target)), first(), ) .subscribe({ @@ -391,7 +389,7 @@ export const ActiveRecordingsTable: React.FC = (prop filteredRecordings.forEach((r: ActiveRecording) => { if (checkedIndices.includes(r.id)) { handleRowCheck(false, r.id); - tasks.push(context.api.archiveRecording(r.name).pipe(first())); + tasks.push(context.api.archiveRecording(r.remoteId).pipe(first())); } }); addSubscription( @@ -417,7 +415,7 @@ export const ActiveRecordingsTable: React.FC = (prop if (checkedIndices.includes(r.id)) { handleRowCheck(false, r.id); if (r.state === RecordingState.RUNNING || r.state === RecordingState.STARTING) { - tasks.push(context.api.stopRecording(r.name).pipe(first())); + tasks.push(context.api.stopRecording(r.remoteId).pipe(first())); } } }); @@ -443,7 +441,7 @@ export const ActiveRecordingsTable: React.FC = (prop filteredRecordings.forEach((r: ActiveRecording) => { if (checkedIndices.includes(r.id)) { context.reports.delete(r); - tasks.push(context.api.deleteRecording(r.name).pipe(first())); + tasks.push(context.api.deleteRecording(r.remoteId).pipe(first())); } }); addSubscription( @@ -1011,7 +1009,7 @@ export const ActiveRecordingRow: React.FC = ({ context.api.uploadActiveRecordingToGrafana(recording.name)} + uploadFn={() => context.api.uploadActiveRecordingToGrafana(recording.remoteId)} /> ); diff --git a/src/app/Rules/CreateRule.tsx b/src/app/Rules/CreateRule.tsx index c7ed83940..850427db1 100644 --- a/src/app/Rules/CreateRule.tsx +++ b/src/app/Rules/CreateRule.tsx @@ -273,17 +273,9 @@ export const CreateRuleForm: React.FC = (_props) => { () => targets.length > 0, forkJoin( targets.map((t) => - context.api - .doGet( - `targets/${encodeURIComponent(t.connectUrl)}/templates`, - 'v4', - undefined, - true, - true, - ) - .pipe( - catchError((_) => of([])), // Fail silently - ), + context.api.getTargetEventTemplates(t).pipe( + catchError((_) => of([])), // Fail silently + ), ), ).pipe( map((allTemplates) => { diff --git a/src/app/SecurityPanel/ImportCertificate.tsx b/src/app/SecurityPanel/ImportCertificate.tsx index 1c4354083..6b4e51ee7 100644 --- a/src/app/SecurityPanel/ImportCertificate.tsx +++ b/src/app/SecurityPanel/ImportCertificate.tsx @@ -52,7 +52,7 @@ export const CertificateImport: React.FC = () => { setLoading(true); addSubscription( context.api - .doGet('tls/certs', 'v4') + .doGet('tls/certs') .pipe(tap((_) => setLoading(false))) .subscribe(setCerts), ); diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index f1cdb3356..c6c96fc6b 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -214,7 +214,7 @@ export class ApiService { } deleteTarget(target: TargetStub): Observable { - return this.sendRequest('v4', `targets/${encodeURIComponent(target.connectUrl)}`, { + return this.sendRequest('v4', `targets/${target.id}`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -335,8 +335,9 @@ export class ApiService { } return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => - this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings`, { + this.sendRequest('v4', `targets/${target!.id}/recordings`, { method: 'POST', body: form, }).pipe( @@ -360,35 +361,14 @@ export class ApiService { ); } - createSnapshot(): Observable { + createSnapshot(): Observable { return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => - this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/snapshot`, { + this.sendRequest('v4', `targets/${target!.id}/snapshot`, { method: 'POST', }).pipe( - tap((resp) => { - if (resp.status == 202) { - this.notifications.warning( - 'Snapshot Failed to Create', - 'The Recording is not readable for reasons, such as, unavailability of active and non-snapshot source Recordings from where the event data is read.', - ); - } - }), - map((resp) => resp.status == 200), - catchError((_) => of(false)), - first(), - ), - ), - ); - } - - createSnapshotv4(): Observable { - return this.target.target().pipe( - concatMap((target) => - this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/snapshot`, { - method: 'POST', - }).pipe( - concatMap((resp) => resp.json() as Promise), + concatMap((resp) => (resp.status === 202 ? of(undefined) : (resp.json() as Promise))), catchError((_) => of(undefined)), first(), ), @@ -400,17 +380,14 @@ export class ApiService { return this.archiveEnabled.asObservable(); } - archiveRecording(recordingName: string): Observable { + archiveRecording(remoteId: number): Observable { return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => - this.sendRequest( - 'v4', - `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings/${encodeURIComponent(recordingName)}`, - { - method: 'PATCH', - body: 'SAVE', - }, - ).pipe( + this.sendRequest('v4', `targets/${target!.id}/recordings/${remoteId}`, { + method: 'PATCH', + body: 'SAVE', + }).pipe( map((resp) => resp.ok), first(), ), @@ -418,17 +395,14 @@ export class ApiService { ); } - stopRecording(recordingName: string): Observable { + stopRecording(remoteId: number): Observable { return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => - this.sendRequest( - 'v4', - `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings/${encodeURIComponent(recordingName)}`, - { - method: 'PATCH', - body: 'STOP', - }, - ).pipe( + this.sendRequest('v4', `targets/${target!.id}/recordings/${remoteId}`, { + method: 'PATCH', + body: 'STOP', + }).pipe( map((resp) => resp.ok), first(), ), @@ -436,16 +410,13 @@ export class ApiService { ); } - deleteRecording(recordingName: string): Observable { + deleteRecording(remoteId: number): Observable { return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => - this.sendRequest( - 'v4', - `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings/${encodeURIComponent(recordingName)}`, - { - method: 'DELETE', - }, - ).pipe( + this.sendRequest('v4', `targets/${target!.id}/recordings/${remoteId}`, { + method: 'DELETE', + }).pipe( map((resp) => resp.ok), first(), ), @@ -466,18 +437,13 @@ export class ApiService { ); } - uploadActiveRecordingToGrafana(recordingName: string): Observable { + uploadActiveRecordingToGrafana(remoteId: number): Observable { return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => - this.sendRequest( - 'v4', - `targets/${encodeURIComponent(target?.connectUrl || '')}/recordings/${encodeURIComponent( - recordingName, - )}/upload`, - { - method: 'POST', - }, - ).pipe( + this.sendRequest('v4', `targets/${target!.id}/recordings/${remoteId}/upload`, { + method: 'POST', + }).pipe( map((resp) => resp.ok), first(), ), @@ -486,18 +452,14 @@ export class ApiService { } uploadArchivedRecordingToGrafana( - sourceTarget: Observable, + sourceTarget: Observable, recordingName: string, ): Observable { return sourceTarget.pipe( concatMap((target) => - this.sendRequest( - 'beta', - `recordings/${encodeURIComponent(target?.connectUrl || '')}/${encodeURIComponent(recordingName)}/upload`, - { - method: 'POST', - }, - ).pipe( + this.sendRequest('v4', `grafana/${window.btoa((target!.jvmId ?? 'uploads') + '/' + recordingName)}`, { + method: 'POST', + }).pipe( map((resp) => resp.ok), first(), ), @@ -507,20 +469,16 @@ export class ApiService { // from file system path functions uploadArchivedRecordingToGrafanaFromPath(jvmId: string, recordingName: string): Observable { - return this.sendRequest( - 'beta', - `fs/recordings/${encodeURIComponent(jvmId)}/${encodeURIComponent(recordingName)}/upload`, - { - method: 'POST', - }, - ).pipe( + return this.sendRequest('v4', `grafana/${window.btoa((jvmId ?? 'uploads') + '/' + recordingName)}`, { + method: 'POST', + }).pipe( map((resp) => resp.ok), first(), ); } deleteArchivedRecordingFromPath(jvmId: string, recordingName: string): Observable { - return this.sendRequest('beta', `fs/recordings/${encodeURIComponent(jvmId)}/${encodeURIComponent(recordingName)}`, { + return this.sendRequest('v4', `grafana/${window.btoa((jvmId ?? 'uploads') + '/' + recordingName)}`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -606,7 +564,7 @@ export class ApiService { } deleteCustomEventTemplate(templateName: string): Observable { - return this.sendRequest('v4', `templates/${encodeURIComponent(templateName)}`, { + return this.sendRequest('v4', `event_templates/${encodeURIComponent(templateName)}`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -646,8 +604,9 @@ export class ApiService { removeProbes(): Observable { return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => - this.sendRequest('v4', `targets/${encodeURIComponent(target?.connectUrl || '')}/probes`, { + this.sendRequest('v4', `targets/${target!.id}/probes`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), @@ -660,14 +619,11 @@ export class ApiService { insertProbes(templateName: string): Observable { return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => - this.sendRequest( - 'v4', - `targets/${encodeURIComponent(target?.connectUrl || '')}/probes/${encodeURIComponent(templateName)}`, - { - method: 'POST', - }, - ).pipe( + this.sendRequest('v4', `targets/${target!.id}/probes/${encodeURIComponent(templateName)}`, { + method: 'POST', + }).pipe( tap((resp) => { if (resp.status == 400) { this.notifications.warning( @@ -762,10 +718,11 @@ export class ApiService { getActiveProbes(suppressNotifications = false): Observable { return this.target.target().pipe( + filter((t) => !!t), concatMap((target) => this.sendRequest( 'v4', - `targets/${encodeURIComponent(target?.connectUrl || '')}/probes`, + `targets/${target!.id}/probes`, { method: 'GET', }, @@ -786,7 +743,7 @@ export class ApiService { ): Observable { return this.sendRequest( 'v4', - `targets/${encodeURIComponent(target.connectUrl)}/probes`, + `targets/${target.id}/probes`, { method: 'GET', }, @@ -847,12 +804,13 @@ export class ApiService { this.target .target() .pipe( + filter((t) => !!t), first(), map( (target) => - `${this.login.authority}/api/v4/targets/${encodeURIComponent( - target!.connectUrl, - )}/templates/${encodeURIComponent(template.name)}/type/${encodeURIComponent(template.type)}`, + `${this.login.authority}/api/v4/targets/${target!.id}/event_templates/${encodeURIComponent( + template.type, + )}/${encodeURIComponent(template.name)}`, ), ) .subscribe((resourceUrl) => { @@ -861,7 +819,7 @@ export class ApiService { } downloadRule(name: string): void { - this.doGet('rules/' + name, 'v4') + this.doGet(`rules/${name}`) .pipe(first()) .subscribe((rule) => { const filename = `${rule.name}.json`; @@ -1126,7 +1084,7 @@ export class ApiService { headers.set('Content-Type', 'application/json'); return this.sendRequest( - 'beta', + 'v4', 'matchExpressions', { method: 'POST', @@ -1139,6 +1097,7 @@ export class ApiService { ).pipe( first(), concatMap((resp: Response) => resp.json()), + map((r) => r.targets), ); } @@ -1234,8 +1193,8 @@ export class ApiService { body.append('password', credentials.password); return this.sendRequest( - 'beta', - `credentials/${encodeURIComponent(target.connectUrl)}`, + 'v4', + `credentials/test/${target.id}`, { method: 'POST', body }, undefined, true, @@ -1329,33 +1288,15 @@ export class ApiService { } getTargetActiveRecordings(target: TargetStub): Observable { - return this.doGet( - `targets/${encodeURIComponent(target.connectUrl)}/recordings`, - 'v4', - undefined, - true, - true, - ); + return this.doGet(`targets/${target.id}/recordings`, 'v4', undefined, true, true); } getTargetEventTemplates(target: TargetStub): Observable { - return this.doGet( - `targets/${encodeURIComponent(target.connectUrl)}/templates`, - 'v4', - undefined, - true, - true, - ); + return this.doGet(`targets/${target.id}/event_templates`, 'v4', undefined, true, true); } getTargetEventTypes(target: TargetStub): Observable { - return this.doGet( - `targets/${encodeURIComponent(target.connectUrl)}/events`, - 'v4', - undefined, - true, - true, - ); + return this.doGet(`targets/${target.id}/events`, 'v4', undefined, true, true); } downloadLayoutTemplate(template: LayoutTemplate): void { diff --git a/src/app/Shared/Services/api.types.ts b/src/app/Shared/Services/api.types.ts index 31edd2cb2..bb20c9c09 100644 --- a/src/app/Shared/Services/api.types.ts +++ b/src/app/Shared/Services/api.types.ts @@ -246,6 +246,7 @@ export interface ActiveRecording extends Recording { toDisk: boolean; maxSize: number; maxAge: number; + remoteId: number; } export interface ActiveRecordingsFilterInput { diff --git a/src/app/Topology/Entity/EntityDetails.tsx b/src/app/Topology/Entity/EntityDetails.tsx index 0ea97d5f9..c1a63c580 100644 --- a/src/app/Topology/Entity/EntityDetails.tsx +++ b/src/app/Topology/Entity/EntityDetails.tsx @@ -464,7 +464,7 @@ export const TargetResources: React.FC<{ targetNode: TargetNode }> = ({ targetNo const checkIfAgentDetected = React.useCallback(() => { addSubscription( context.api - .doGet(`targets/${encodeURIComponent(target.connectUrl)}/probes`, 'v4', undefined, true, true) + .getActiveProbesForTarget(target) .pipe( concatMap(() => of(true)), catchError(() => of(false)), diff --git a/src/test/Events/EventTemplates.test.tsx b/src/test/Events/EventTemplates.test.tsx index cd1567664..bd3dae83d 100644 --- a/src/test/Events/EventTemplates.test.tsx +++ b/src/test/Events/EventTemplates.test.tsx @@ -73,7 +73,7 @@ jest.spyOn(defaultServices.api, 'addCustomEventTemplate').mockReturnValue(of(tru jest.spyOn(defaultServices.api, 'deleteCustomEventTemplate').mockReturnValue(of(true)); jest.spyOn(defaultServices.api, 'downloadTemplate').mockReturnValue(void 0); -jest.spyOn(defaultServices.api, 'doGet').mockReturnValue(of([mockCustomEventTemplate])); +jest.spyOn(defaultServices.api, 'getTargetEventTemplates').mockReturnValue(of([mockCustomEventTemplate])); jest.spyOn(defaultServices.target, 'target').mockReturnValue(of(mockTarget)); jest.spyOn(defaultServices.target, 'authFailure').mockReturnValue(of()); diff --git a/src/test/Events/EventTypes.test.tsx b/src/test/Events/EventTypes.test.tsx index 5a4b5466b..cdf51b9aa 100644 --- a/src/test/Events/EventTypes.test.tsx +++ b/src/test/Events/EventTypes.test.tsx @@ -40,7 +40,7 @@ const mockEventType: EventType = { options: [{ some_key: { name: 'some_name', description: 'a_desc', defaultValue: 'some_value' } }], }; -jest.spyOn(defaultServices.api, 'doGet').mockReturnValue(of([mockEventType])); +jest.spyOn(defaultServices.api, 'getTargetEventTypes').mockReturnValue(of([mockEventType])); jest.spyOn(defaultServices.target, 'target').mockReturnValue(of(mockTarget)); jest.spyOn(defaultServices.target, 'authFailure').mockReturnValue(of()); diff --git a/src/test/RecordingMetadata/BulkEditLabels.test.tsx b/src/test/RecordingMetadata/BulkEditLabels.test.tsx index b09332146..6ac0009de 100644 --- a/src/test/RecordingMetadata/BulkEditLabels.test.tsx +++ b/src/test/RecordingMetadata/BulkEditLabels.test.tsx @@ -119,7 +119,7 @@ const mockArchivedRecordingsResponse = { jest.spyOn(defaultServices.target, 'target').mockReturnValue(of(mockTarget)); jest.spyOn(defaultServices.api, 'graphql').mockReturnValue(of(mockArchivedRecordingsResponse)); -jest.spyOn(defaultServices.api, 'doGet').mockReturnValue(of(mockActiveRecordingResponse)); +jest.spyOn(defaultServices.api, 'getTargetActiveRecordings').mockReturnValue(of(mockActiveRecordingResponse)); jest .spyOn(defaultServices.notificationChannel, 'messages') .mockReturnValueOnce(of()) // renders correctly diff --git a/src/test/Recordings/ActiveRecordingsTable.test.tsx b/src/test/Recordings/ActiveRecordingsTable.test.tsx index 9f14df6e9..1b75e767e 100644 --- a/src/test/Recordings/ActiveRecordingsTable.test.tsx +++ b/src/test/Recordings/ActiveRecordingsTable.test.tsx @@ -98,7 +98,7 @@ jest.mock('@app/Recordings/RecordingFilters', () => { jest.spyOn(defaultServices.api, 'archiveRecording').mockReturnValue(of(true)); jest.spyOn(defaultServices.api, 'deleteRecording').mockReturnValue(of(true)); -jest.spyOn(defaultServices.api, 'doGet').mockReturnValue(of([mockRecording])); +jest.spyOn(defaultServices.api, 'getTargetActiveRecordings').mockReturnValue(of([mockRecording])); jest.spyOn(defaultServices.api, 'downloadRecording').mockReturnValue(void 0); jest.spyOn(defaultServices.api, 'grafanaDashboardUrl').mockReturnValue(of('/grafanaUrl')); jest.spyOn(defaultServices.api, 'grafanaDatasourceUrl').mockReturnValue(of('/datasource')); From 9c2ad9d2f909f99a4e2da8bfab0453a11f684aca Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 21 Dec 2023 17:06:41 -0500 Subject: [PATCH 09/25] replace v1 targets query with v3 --- src/app/Shared/Services/Api.service.tsx | 4 ++++ src/app/Shared/Services/Targets.service.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index c6c96fc6b..69cd57cf8 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -181,6 +181,10 @@ export class ApiService { }); } + getTargets(): Observable { + return this.doGet('targets', 'v3'); + } + createTarget( target: TargetStub, credentials?: { username?: string; password?: string }, diff --git a/src/app/Shared/Services/Targets.service.tsx b/src/app/Shared/Services/Targets.service.tsx index 516d2f99f..8215821d3 100644 --- a/src/app/Shared/Services/Targets.service.tsx +++ b/src/app/Shared/Services/Targets.service.tsx @@ -60,7 +60,7 @@ export class TargetsService { } queryForTargets(): Observable { - return this.api.doGet(`targets`).pipe( + return this.api.getTargets().pipe( first(), tap((targets) => this._targets$.next(targets)), map(() => undefined), From 14e246d776ca9d247489d752cf9ea3a0054bda1f Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 21 Dec 2023 17:15:29 -0500 Subject: [PATCH 10/25] replace v1 target active recordings query with v3 --- src/app/Shared/Services/Api.service.tsx | 8 ++++++-- src/app/Topology/Entity/utils.tsx | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 69cd57cf8..c95fde3a2 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -1291,8 +1291,12 @@ export class ApiService { ).pipe(map((v) => (v.data?.targetNodes[0]?.target?.archivedRecordings?.data as ArchivedRecording[]) ?? [])); } - getTargetActiveRecordings(target: TargetStub): Observable { - return this.doGet(`targets/${target.id}/recordings`, 'v4', undefined, true, true); + getTargetActiveRecordings( + target: TargetStub, + suppressNotifications = false, + skipStatusCheck = false, + ): Observable { + return this.doGet(`targets/${target.id}/recordings`, 'v4', undefined, suppressNotifications, skipStatusCheck); } getTargetEventTemplates(target: TargetStub): Observable { diff --git a/src/app/Topology/Entity/utils.tsx b/src/app/Topology/Entity/utils.tsx index 9e25102a1..129a0fd1b 100644 --- a/src/app/Topology/Entity/utils.tsx +++ b/src/app/Topology/Entity/utils.tsx @@ -80,7 +80,7 @@ export const getTargetOwnedResources = ( ): Observable => { switch (resourceType) { case 'activeRecordings': - return apiService.getTargetActiveRecordings(target); + return apiService.getTargetActiveRecordings(target, true, true); case 'archivedRecordings': return apiService.getTargetArchivedRecordings(target); case 'eventTemplates': From ddafa78847423226f99673ee268c12ae581f5b11 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 21 Dec 2023 17:31:56 -0500 Subject: [PATCH 11/25] replace v1 target event types and templates queries with v3 --- .../CreateRecording/CustomRecordingForm.tsx | 6 +++-- .../AutomatedAnalysisConfigForm.tsx | 8 +------ src/app/Rules/CreateRule.tsx | 2 +- src/app/Shared/Services/Api.service.tsx | 24 +++++++++++++++---- src/app/Topology/Entity/utils.tsx | 4 ++-- src/test/Rules/CreateRule.test.tsx | 4 ++-- 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/app/CreateRecording/CustomRecordingForm.tsx b/src/app/CreateRecording/CustomRecordingForm.tsx index ebeca872f..cbe92b236 100644 --- a/src/app/CreateRecording/CustomRecordingForm.tsx +++ b/src/app/CreateRecording/CustomRecordingForm.tsx @@ -273,8 +273,10 @@ export const CustomRecordingForm: React.FC = () => { } addSubscription( forkJoin({ - templates: context.api.doGet(`targets/${target.id}/event_templates`), - recordingOptions: context.api.doGet(`targets/${target.id}/recordingOptions`), + templates: context.api.getTargetEventTemplates(target), + recordingOptions: context.api.doGet( + `targets/${target.id}/recordingOptions`, + ), }).subscribe({ next: ({ templates, recordingOptions }) => { setErrorMessage(''); diff --git a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx index 454072f91..f176e567c 100644 --- a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.tsx @@ -99,13 +99,7 @@ export const AutomatedAnalysisConfigForm: React.FC { setIsLoading(true); addSubscription( - iif( - () => !target, - of([]), - context.api - .doGet(`targets/${target!.id}/event_templates`, undefined, undefined, true) - .pipe(first()), - ).subscribe({ + iif(() => !target, of([]), context.api.getTargetEventTemplates(target!, false, true).pipe(first())).subscribe({ next: (templates: EventTemplate[]) => { setErrorMessage(''); setTemplates(templates); diff --git a/src/app/Rules/CreateRule.tsx b/src/app/Rules/CreateRule.tsx index 850427db1..c0a1f22c4 100644 --- a/src/app/Rules/CreateRule.tsx +++ b/src/app/Rules/CreateRule.tsx @@ -273,7 +273,7 @@ export const CreateRuleForm: React.FC = (_props) => { () => targets.length > 0, forkJoin( targets.map((t) => - context.api.getTargetEventTemplates(t).pipe( + context.api.getTargetEventTemplates(t, true, true).pipe( catchError((_) => of([])), // Fail silently ), ), diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index c95fde3a2..bc481d350 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -1299,12 +1299,28 @@ export class ApiService { return this.doGet(`targets/${target.id}/recordings`, 'v4', undefined, suppressNotifications, skipStatusCheck); } - getTargetEventTemplates(target: TargetStub): Observable { - return this.doGet(`targets/${target.id}/event_templates`, 'v4', undefined, true, true); + getTargetEventTemplates( + target: TargetStub, + suppressNotifications = false, + skipStatusCheck = false, + ): Observable { + return this.doGet( + `targets/${target.id}/event_templates`, + 'v4', + undefined, + suppressNotifications, + skipStatusCheck, + ); } - getTargetEventTypes(target: TargetStub): Observable { - return this.doGet(`targets/${target.id}/events`, 'v4', undefined, true, true); + getTargetEventTypes(target: TargetStub, suppressNotifications = false, skipStatusCheck = false): Observable { + return this.doGet( + `targets/${target.id}/events`, + 'v4', + undefined, + suppressNotifications, + skipStatusCheck, + ); } downloadLayoutTemplate(template: LayoutTemplate): void { diff --git a/src/app/Topology/Entity/utils.tsx b/src/app/Topology/Entity/utils.tsx index 129a0fd1b..52c7eef5a 100644 --- a/src/app/Topology/Entity/utils.tsx +++ b/src/app/Topology/Entity/utils.tsx @@ -84,9 +84,9 @@ export const getTargetOwnedResources = ( case 'archivedRecordings': return apiService.getTargetArchivedRecordings(target); case 'eventTemplates': - return apiService.getTargetEventTemplates(target); + return apiService.getTargetEventTemplates(target, true, true); case 'eventTypes': - return apiService.getTargetEventTypes(target); + return apiService.getTargetEventTypes(target, true, true); case 'agentProbes': return apiService.getActiveProbesForTarget(target, true, true); case 'automatedRules': diff --git a/src/test/Rules/CreateRule.test.tsx b/src/test/Rules/CreateRule.test.tsx index 0ef98a4e7..b9e4de28a 100644 --- a/src/test/Rules/CreateRule.test.tsx +++ b/src/test/Rules/CreateRule.test.tsx @@ -64,7 +64,7 @@ jest.mock('react-router-dom', () => ({ useNavigate: () => mockNavigate, })); -jest.spyOn(defaultServices.api, 'doGet').mockReturnValue(of([mockEventTemplate])); +jest.spyOn(defaultServices.api, 'getTargetEventTemplates').mockReturnValue(of([mockEventTemplate])); jest.spyOn(defaultServices.targets, 'targets').mockReturnValue(of([mockTarget])); jest.spyOn(defaultServices.api, 'matchTargetsWithExpr').mockImplementation((matchExpression, _targets) => { @@ -262,7 +262,7 @@ describe('', () => { await user.type(nameInput, mockRule.name); await user.type(descriptionInput, mockRule.description); await user.type(matchExpressionInput, escapeKeyboardInput(mockRule.matchExpression)); - await waitFor(() => expect(defaultServices.api.doGet).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(defaultServices.api.getTargetEventTemplates).toHaveBeenCalledTimes(1)); await user.selectOptions(templateSelect, ['Profiling']); await user.type(maxSizeInput, `${mockRule.maxSizeBytes}`); From e6797a5417e0995e11b6c1ef683183f2b3a981ff Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 4 Sep 2024 10:39:02 -0400 Subject: [PATCH 12/25] test fixup --- .../CreateRecording/CustomRecordingForm.test.tsx | 12 ++---------- .../AutomatedAnalysisConfigForm.test.tsx | 2 +- src/test/Recordings/ActiveRecordingsTable.test.tsx | 11 ++++++----- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/test/CreateRecording/CustomRecordingForm.test.tsx b/src/test/CreateRecording/CustomRecordingForm.test.tsx index 23e0a38bf..e14493aaa 100644 --- a/src/test/CreateRecording/CustomRecordingForm.test.tsx +++ b/src/test/CreateRecording/CustomRecordingForm.test.tsx @@ -62,16 +62,8 @@ const mockResponse: Response = { } as Response; jest.spyOn(defaultServices.target, 'target').mockReturnValue(of(mockTarget)); -jest - .spyOn(defaultServices.api, 'doGet') - .mockReturnValueOnce(of([mockCustomEventTemplate])) // renders correctly - .mockReturnValueOnce(of(mockRecordingOptions)) - - .mockReturnValueOnce(of([mockCustomEventTemplate])) // should create recording when form is filled and create is clicked - .mockReturnValueOnce(of(mockRecordingOptions)) - - .mockReturnValueOnce(of([mockCustomEventTemplate])) // should show correct helper texts in metadata label editor - .mockReturnValueOnce(of(mockRecordingOptions)); +jest.spyOn(defaultServices.api, 'getTargetEventTemplates').mockReturnValue(of([mockCustomEventTemplate])); +jest.spyOn(defaultServices.api, 'doGet').mockReturnValue(of(mockRecordingOptions)); jest.spyOn(defaultServices.target, 'authFailure').mockReturnValue(of()); diff --git a/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.test.tsx b/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.test.tsx index d6c304c51..029de68a4 100644 --- a/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.test.tsx +++ b/src/test/Dashboard/AutomatedAnalysis/AutomatedAnalysisConfigForm.test.tsx @@ -66,7 +66,7 @@ jest.mock('@app/utils/LocalStorage', () => { }; }); -jest.spyOn(defaultServices.api, 'doGet').mockReturnValue(of([mockTemplate1, mockTemplate2])); +jest.spyOn(defaultServices.api, 'getTargetEventTemplates').mockReturnValue(of([mockTemplate1, mockTemplate2])); jest .spyOn(defaultServices.settings, 'automatedAnalysisRecordingConfig') diff --git a/src/test/Recordings/ActiveRecordingsTable.test.tsx b/src/test/Recordings/ActiveRecordingsTable.test.tsx index 1b75e767e..35932e3ca 100644 --- a/src/test/Recordings/ActiveRecordingsTable.test.tsx +++ b/src/test/Recordings/ActiveRecordingsTable.test.tsx @@ -66,6 +66,7 @@ const mockRecording: ActiveRecording = { toDisk: false, maxSize: 0, maxAge: 0, + remoteId: 998877, }; const mockAnotherRecording = { ...mockRecording, name: 'anotherRecording', id: 1 }; const mockCreateNotification = { @@ -386,7 +387,7 @@ describe('', () => { const archiveRequestSpy = jest.spyOn(defaultServices.api, 'archiveRecording'); expect(archiveRequestSpy).toHaveBeenCalledTimes(1); - expect(archiveRequestSpy).toBeCalledWith('someRecording'); + expect(archiveRequestSpy).toBeCalledWith(mockRecording.remoteId); }); it('stops the selected Recording when Stop is clicked', async () => { @@ -410,7 +411,7 @@ describe('', () => { const stopRequestSpy = jest.spyOn(defaultServices.api, 'stopRecording'); expect(stopRequestSpy).toHaveBeenCalledTimes(1); - expect(stopRequestSpy).toBeCalledWith('someRecording'); + expect(stopRequestSpy).toBeCalledWith(mockRecording.remoteId); }); it('opens the labels drawer when Edit Labels is clicked', async () => { @@ -464,7 +465,7 @@ describe('', () => { }); expect(deleteRequestSpy).toBeCalledTimes(1); - expect(deleteRequestSpy).toBeCalledWith('someRecording'); + expect(deleteRequestSpy).toBeCalledWith(mockRecording.remoteId); expect(dialogWarningSpy).toBeCalledTimes(1); expect(dialogWarningSpy).toBeCalledWith(DeleteOrDisableWarningType.DeleteActiveRecordings, false); }); @@ -491,7 +492,7 @@ describe('', () => { expect(screen.queryByLabelText(DeleteActiveRecordings.ariaLabel)).not.toBeInTheDocument(); expect(deleteRequestSpy).toHaveBeenCalledTimes(1); - expect(deleteRequestSpy).toBeCalledWith('someRecording'); + expect(deleteRequestSpy).toBeCalledWith(mockRecording.remoteId); }); it('downloads a Recording when Download Recording is clicked', async () => { @@ -539,7 +540,7 @@ describe('', () => { const grafanaUploadSpy = jest.spyOn(defaultServices.api, 'uploadActiveRecordingToGrafana'); expect(grafanaUploadSpy).toHaveBeenCalledTimes(1); - expect(grafanaUploadSpy).toBeCalledWith('someRecording'); + expect(grafanaUploadSpy).toBeCalledWith(mockRecording.remoteId); }); it('should show error view if failing to retrieve Recordings', async () => { From bb94268d02d2e6b34d2f8ab5aff1f0d2cd1ee3ec Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 4 Sep 2024 10:40:42 -0400 Subject: [PATCH 13/25] remove unused import --- src/app/Topology/Actions/CreateTarget.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/Topology/Actions/CreateTarget.tsx b/src/app/Topology/Actions/CreateTarget.tsx index 8edf391e4..8139436a0 100644 --- a/src/app/Topology/Actions/CreateTarget.tsx +++ b/src/app/Topology/Actions/CreateTarget.tsx @@ -19,7 +19,6 @@ import { BreadcrumbPage } from '@app/BreadcrumbPage/BreadcrumbPage'; import { LinearDotSpinner } from '@app/Shared/Components/LinearDotSpinner'; import { LoadingProps } from '@app/Shared/Components/types'; import { Target } from '@app/Shared/Services/api.types'; -import { isHttpOk } from '@app/Shared/Services/api.utils'; import { ServiceContext } from '@app/Shared/Services/Services'; import '@app/Topology/styles/base.css'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; From 59653e7f46a9e2e18bee8a3280895d6520f6db50 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 4 Sep 2024 10:47:11 -0400 Subject: [PATCH 14/25] typechecl --- .../SnapshotRecordingForm.test.tsx | 20 ++++++++++++++++++- .../RecordingMetadata/BulkEditLabels.test.tsx | 1 + .../Filters/DurationFilter.test.tsx | 1 + .../Recordings/Filters/LabelFilter.test.tsx | 1 + .../Recordings/Filters/NameFilter.test.tsx | 1 + .../Filters/RecordingStateFilter.test.tsx | 1 + src/test/Recordings/RecordingFilters.test.tsx | 1 + 7 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/test/CreateRecording/SnapshotRecordingForm.test.tsx b/src/test/CreateRecording/SnapshotRecordingForm.test.tsx index 35e097f21..99725f97f 100644 --- a/src/test/CreateRecording/SnapshotRecordingForm.test.tsx +++ b/src/test/CreateRecording/SnapshotRecordingForm.test.tsx @@ -16,6 +16,7 @@ import { SnapshotRecordingForm } from '@app/CreateRecording/SnapshotRecordingForm'; import { authFailMessage } from '@app/ErrorView/types'; +import { ActiveRecording, RecordingState } 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'; @@ -31,6 +32,23 @@ const mockTarget = { labels: [], annotations: { cryostat: [], platform: [] }, }; +const mockRecording: ActiveRecording = { + id: 100, + state: RecordingState.RUNNING, + duration: 1010, + startTime: 9999, + continuous: false, + toDisk: false, + maxSize: 55, + maxAge: 66, + remoteId: 77, + name: 'snapshot-10', + downloadUrl: 'http://localhost:8080/api/v4/targets/1/recordings/77', + reportUrl: 'http://localhost:8080/api/v4/targets/1/reports/77', + metadata: { + labels: [], + }, +}; jest.spyOn(defaultServices.target, 'authFailure').mockReturnValue(of()); jest.spyOn(defaultServices.target, 'target').mockReturnValue(of(mockTarget)); @@ -67,7 +85,7 @@ describe('', () => { }); it('should create Recording when create is clicked', async () => { - const onCreateSpy = jest.spyOn(defaultServices.api, 'createSnapshot').mockReturnValue(of(true)); + const onCreateSpy = jest.spyOn(defaultServices.api, 'createSnapshot').mockReturnValue(of(mockRecording)); const { user } = render({ routerConfigs: { routes: [ diff --git a/src/test/RecordingMetadata/BulkEditLabels.test.tsx b/src/test/RecordingMetadata/BulkEditLabels.test.tsx index 6ac0009de..1a888a290 100644 --- a/src/test/RecordingMetadata/BulkEditLabels.test.tsx +++ b/src/test/RecordingMetadata/BulkEditLabels.test.tsx @@ -73,6 +73,7 @@ const mockActiveRecording: ActiveRecording = { toDisk: false, maxSize: 0, maxAge: 0, + remoteId: 9876, }; const mockActiveLabelsNotification = { diff --git a/src/test/Recordings/Filters/DurationFilter.test.tsx b/src/test/Recordings/Filters/DurationFilter.test.tsx index b76ed160d..4f888f761 100644 --- a/src/test/Recordings/Filters/DurationFilter.test.tsx +++ b/src/test/Recordings/Filters/DurationFilter.test.tsx @@ -39,6 +39,7 @@ const mockRecording: ActiveRecording = { toDisk: false, maxSize: 0, maxAge: 0, + remoteId: 8765, }; const durationRangeWithoutUpperLimit = { from: { value: mockRecording.duration / 1000, unit: DurationUnit.SECOND } }; diff --git a/src/test/Recordings/Filters/LabelFilter.test.tsx b/src/test/Recordings/Filters/LabelFilter.test.tsx index b934ae580..b086d17ee 100644 --- a/src/test/Recordings/Filters/LabelFilter.test.tsx +++ b/src/test/Recordings/Filters/LabelFilter.test.tsx @@ -46,6 +46,7 @@ const mockRecording: ActiveRecording = { toDisk: false, maxSize: 0, maxAge: 0, + remoteId: 6543, }; const mockAnotherRecording = { ...mockRecording, diff --git a/src/test/Recordings/Filters/NameFilter.test.tsx b/src/test/Recordings/Filters/NameFilter.test.tsx index daa850a3a..4ef80be79 100644 --- a/src/test/Recordings/Filters/NameFilter.test.tsx +++ b/src/test/Recordings/Filters/NameFilter.test.tsx @@ -38,6 +38,7 @@ const mockRecording: ActiveRecording = { toDisk: false, maxSize: 0, maxAge: 0, + remoteId: 5432, }; const mockAnotherRecording = { ...mockRecording, name: 'anotherRecording' }; const mockRecordingList = [mockRecording, mockAnotherRecording]; diff --git a/src/test/Recordings/Filters/RecordingStateFilter.test.tsx b/src/test/Recordings/Filters/RecordingStateFilter.test.tsx index 9b8359ee4..e33b1709c 100644 --- a/src/test/Recordings/Filters/RecordingStateFilter.test.tsx +++ b/src/test/Recordings/Filters/RecordingStateFilter.test.tsx @@ -38,6 +38,7 @@ const mockRecording: ActiveRecording = { toDisk: false, maxSize: 0, maxAge: 0, + remoteId: 4321, }; const mockAnotherRecording = { ...mockRecording, diff --git a/src/test/Recordings/RecordingFilters.test.tsx b/src/test/Recordings/RecordingFilters.test.tsx index 7c5047f4d..e2056adb2 100644 --- a/src/test/Recordings/RecordingFilters.test.tsx +++ b/src/test/Recordings/RecordingFilters.test.tsx @@ -66,6 +66,7 @@ const mockActiveRecording: ActiveRecording = { toDisk: false, maxSize: 0, maxAge: 0, + remoteId: 3210, }; const mockActiveRecordingList = [ mockActiveRecording, From a47bebdaefc143e589224ab9c33b11098acc7c6b Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 4 Sep 2024 11:17:46 -0400 Subject: [PATCH 15/25] fixup mirage --- src/app/utils/fakeData.ts | 5 +- src/mirage/index.ts | 102 +++++++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/app/utils/fakeData.ts b/src/app/utils/fakeData.ts index 9f6cec002..f7e294bdb 100644 --- a/src/app/utils/fakeData.ts +++ b/src/app/utils/fakeData.ts @@ -117,6 +117,7 @@ export const fakeAARecording: ActiveRecording = { toDisk: false, maxSize: 1048576, maxAge: 0, + remoteId: 567567, }; export const fakeEvaluations: AnalysisResult[] = [ @@ -306,7 +307,7 @@ class FakeApiService extends ApiService { return of(true); } - uploadActiveRecordingToGrafana(_recordingName: string): Observable { + uploadActiveRecordingToGrafana(_remoteId: number): Observable { return of(true); } @@ -379,7 +380,7 @@ class FakeApiService extends ApiService { }); } - deleteRecording(_recordingName: string): Observable { + deleteRecording(_remoteId: number): Observable { return of(true); } } diff --git a/src/mirage/index.ts b/src/mirage/index.ts index 5109f429e..0510dd7e4 100644 --- a/src/mirage/index.ts +++ b/src/mirage/index.ts @@ -86,14 +86,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { 200, {}, { - meta: { - status: 'OK', - }, - data: { - result: { - username: environment, - }, - }, + username: environment, }, ); }); @@ -128,11 +121,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { message: { event: { serviceRef: target, kind: 'FOUND' } }, }), ); - return { - data: { - result: target, - }, - }; + return target; }); this.get('api/v4/targets', (schema) => schema.all(Resource.TARGET).models); this.get('api/v4/discovery', (schema) => { @@ -200,12 +189,11 @@ export const startMirage = ({ environment = 'development' } = {}) => { // Note: MirageJS will fake serialize FormData (i.e. FormData object is returned when accessing request.requestBody) const attrs = request.requestBody as any; + const remoteId = Math.floor(Math.random() * 1000000); const recording = schema.create(Resource.RECORDING, { // id will generated by Mirage (i.e. increment integers) downloadUrl: '', - reportUrl: `api/beta/reports/${encodeURIComponent(request.params.targetId)}/${encodeURIComponent( - attrs.get('recordingName'), - )}`, + reportUrl: `api/v4/targets/${request.params.targetId}/reports/${remoteId}`, name: attrs.get('recordingName'), state: 'RUNNING', startTime: +Date.now(), @@ -227,6 +215,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, ], }, + remoteId, }); websocket.send( JSON.stringify({ @@ -243,9 +232,8 @@ export const startMirage = ({ environment = 'development' } = {}) => { return recording; }); this.get('api/v4/targets/:targetId/recordings', (schema) => schema.all(Resource.RECORDING).models); - this.delete('api/v4/targets/:targetId/recordings/:recordingName', (schema, request) => { - const recordingName = request.params.recordingName; - const recording = schema.findBy(Resource.RECORDING, { name: recordingName }); + this.delete('api/v4/targets/:targetId/recordings/:remoteId', (schema, request) => { + const recording = schema.findBy(Resource.RECORDING, { remoteId: request.params.remoteId }); if (!recording) { return new Response(404); @@ -267,11 +255,10 @@ export const startMirage = ({ environment = 'development' } = {}) => { websocket.send(JSON.stringify(msg)); return new Response(200); }); - this.patch('api/v4/targets/:targetId/recordings/:recordingName', (schema, request) => { + this.patch('api/v4/targets/:targetId/recordings/:remoteId', (schema, request) => { const body = request.requestBody; - const recordingName = request.params.recordingName; - const target = schema.findBy(Resource.TARGET, { connectUrl: request.params.targetId }); - const recording = schema.findBy(Resource.RECORDING, { name: recordingName }); + const target = schema.findBy(Resource.TARGET, { id: request.params.targetId }); + const recording = schema.findBy(Resource.RECORDING, { remoteId: request.params.remoteId }); if (!recording || !target) { return new Response(404); @@ -321,7 +308,49 @@ export const startMirage = ({ environment = 'development' } = {}) => { } return new Response(200); }); - this.get('api/beta/reports/:targetId/:recordingName', () => { + this.post('api/v4/targets/:targetId/snapshot', (schema, request) => { + const remoteId = Math.floor(Math.random() * 1000000); + const recording = schema.create(Resource.RECORDING, { + // id will generated by Mirage (i.e. increment integers) + downloadUrl: '', + reportUrl: `api/v4/targets/${request.params.targetId}/reports/${remoteId}`, + name: `snapshot-${remoteId}`, + state: 'STOPPED', + startTime: +Date.now(), + duration: Math.floor(Math.random() * 1000), + continuous: false, + toDisk: true, + maxSize: 0, + maxAge: 0, + metadata: { + labels: [ + { + key: 'template.type', + value: 'TARGET', + }, + { + key: 'template.name', + value: 'Demo_Template', + }, + ], + }, + remoteId, + }); + websocket.send( + JSON.stringify({ + meta: { + category: 'ActiveRecordingCreated', + type: { type: 'application', subType: 'json' }, + }, + message: { + target: request.params.targetId, + recording, + }, + }), + ); + return recording; + }); + this.get('api/v4/targets/:targetId/reports/:remoteId', () => { return new Response( 200, {}, @@ -397,7 +426,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { description: 'The configuration of the garbage collected heap', }, ]); - this.get('api/v4/targets/:targetId/templates', () => [ + this.get('api/v4/targets/:targetId/event_templates', () => [ { name: 'Demo Template', provider: 'Demo', @@ -406,17 +435,14 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, ]); this.get('api/v4/probes', () => []); - this.post('api/beta/matchExpressions', (_, request) => { + this.get('api/v4/targets/:targetId/probes', () => []); + this.post('api/v4/matchExpressions', (_, request) => { const attr = JSON.parse(request.requestBody); if (!attr.matchExpression || !attr.targets) { return new Response(400); } return { - data: { - result: { - targets: attr.targets, - }, - }, + targets: attr.targets, }; }); this.post('api/v4/rules', (schema, request) => { @@ -430,15 +456,9 @@ export const startMirage = ({ environment = 'development' } = {}) => { message: rule, }; websocket.send(JSON.stringify(msg)); - return { - data: { - result: rule, - }, - }; + return rule; }); - this.get('api/v4/rules', (schema) => ({ - data: { result: schema.all(Resource.RULE).models }, - })); + this.get('api/v4/rules', (schema) => schema.all(Resource.RULE).models); this.patch('api/v4/rules/:ruleName', (schema, request) => { const ruleName = request.params.ruleName; const patch = JSON.parse(request.requestBody); @@ -497,8 +517,8 @@ export const startMirage = ({ environment = 'development' } = {}) => { ); return new Response(201); }); - this.get('api/v4/credentials', (schema) => ({ data: { result: schema.all(Resource.CREDENTIAL).models } })); - this.get('api/v4/credentials/:id', () => ({ data: { result: { matchExpression: '', targets: [] } } })); + this.get('api/v4/credentials', (schema) => schema.all(Resource.CREDENTIAL).models); + this.get('api/v4/credentials/:id', () => ({ matchExpression: '', targets: [] })); this.post('api/v4/graphql', (schema, request) => { const body = JSON.parse(request.requestBody); const query = body.query.trim(); From 64b7da44cc5841049291411448b1a8eae3510699 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 4 Sep 2024 15:03:57 -0400 Subject: [PATCH 16/25] merge fixup --- src/app/Shared/Services/Api.service.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index bc481d350..b4d6e2de9 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -482,7 +482,7 @@ export class ApiService { } deleteArchivedRecordingFromPath(jvmId: string, recordingName: string): Observable { - return this.sendRequest('v4', `grafana/${window.btoa((jvmId ?? 'uploads') + '/' + recordingName)}`, { + return this.sendRequest('beta', `fs/recordings/${encodeURIComponent(jvmId)}/${encodeURIComponent(recordingName)}`, { method: 'DELETE', }).pipe( map((resp) => resp.ok), From 2bf22e2baf9a9ac9e7a961a2fff74d42f903ae90 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Sep 2024 10:41:48 -0400 Subject: [PATCH 17/25] suppress notifications --- src/app/Topology/Entity/EntityDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/Topology/Entity/EntityDetails.tsx b/src/app/Topology/Entity/EntityDetails.tsx index c1a63c580..69f1cbc92 100644 --- a/src/app/Topology/Entity/EntityDetails.tsx +++ b/src/app/Topology/Entity/EntityDetails.tsx @@ -464,7 +464,7 @@ export const TargetResources: React.FC<{ targetNode: TargetNode }> = ({ targetNo const checkIfAgentDetected = React.useCallback(() => { addSubscription( context.api - .getActiveProbesForTarget(target) + .getActiveProbesForTarget(target, true, true) .pipe( concatMap(() => of(true)), catchError(() => of(false)), From 7b2d1187c27d8ecc1aebf01372f2f4dc0387b814 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Sep 2024 11:58:20 -0400 Subject: [PATCH 18/25] fixup queries to retrieve recording remote ID by label, then use that ID in followup --- .../AutomatedAnalysisCard.tsx | 27 ++++++++------ .../Charts/jfr/JFRMetricsChartController.tsx | 16 ++++++--- src/app/Shared/Services/Api.service.tsx | 35 ++++++++++++++++++- src/app/utils/fakeData.ts | 2 +- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx index f353d6756..7a298eb07 100644 --- a/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx +++ b/src/app/Dashboard/AutomatedAnalysis/AutomatedAnalysisCard.tsx @@ -98,7 +98,7 @@ import _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; -import { filter, first, map, tap } from 'rxjs'; +import { concatMap, filter, first, map, tap } from 'rxjs'; import { DashboardCard } from '../DashboardCard'; import { DashboardCardDescriptor, DashboardCardFC, DashboardCardSizes, DashboardCardTypeProps } from '../types'; import { AutomatedAnalysisCardList } from './AutomatedAnalysisCardList'; @@ -528,22 +528,29 @@ export const AutomatedAnalysisCard: DashboardCardFC if (usingCachedReport) { generateReport(); } else { - // FIXME this should use a GraphQL query addSubscription( - context.api.deleteRecording('automated-analysis').subscribe({ - next: () => { - generateReport(); - }, - error: (error) => { - handleStateErrors(error.message); - }, - }), + context.target + .target() + .pipe( + filter((t) => !!t), + concatMap((t) => context.api.targetRecordingRemoteIdByOrigin(t!, automatedAnalysisRecordingName)), + concatMap((id) => context.api.deleteRecording(id!)), + ) + .subscribe({ + next: () => { + generateReport(); + }, + error: (error) => { + handleStateErrors(error.message); + }, + }), ); } }, [ addSubscription, context.api, context.reports, + context.target, targetConnectURL, usingCachedReport, usingArchivedReport, diff --git a/src/app/Dashboard/Charts/jfr/JFRMetricsChartController.tsx b/src/app/Dashboard/Charts/jfr/JFRMetricsChartController.tsx index 713ddeb34..668fdd90d 100644 --- a/src/app/Dashboard/Charts/jfr/JFRMetricsChartController.tsx +++ b/src/app/Dashboard/Charts/jfr/JFRMetricsChartController.tsx @@ -23,6 +23,7 @@ import { BehaviorSubject, concatMap, distinctUntilChanged, + filter, finalize, first, map, @@ -115,10 +116,15 @@ export class JFRMetricsChartController { .subscribe((v) => { this._state$.next(v ? ControllerState.READY : ControllerState.NO_DATA); if (v) { - this._api - // FIXME this should retrieve the ID given the recording name by using a GraphQL query - .uploadActiveRecordingToGrafana(RECORDING_NAME) - .pipe(first()) + this._target + .target() + .pipe( + filter((t) => !!t), + first(), + concatMap((t) => this._api.targetRecordingRemoteIdByOrigin(t!, RECORDING_NAME)), + filter((remoteId) => remoteId != null), + concatMap((id) => this._api.uploadActiveRecordingToGrafana(id!).pipe(first())), + ) .subscribe((_) => { this._state$.next(ControllerState.READY); }); @@ -130,7 +136,7 @@ export class JFRMetricsChartController { if (!target) { return of(false); } - return this._api.targetHasRecording(target, { + return this._api.targetHasJFRMetricsRecording(target, { state: RecordingState.RUNNING, labels: [`origin=${RECORDING_NAME}`], }); diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index b4d6e2de9..5b9526e6f 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -1149,7 +1149,40 @@ export class ApiService { ); } - targetHasRecording(target: TargetStub, filter: ActiveRecordingsFilterInput = {}): Observable { + targetRecordingRemoteIdByOrigin(target: TargetStub, origin: string): Observable { + return this.graphql( + ` + query ActiveRecordingIdForRecordingByOriginLabel($id: BigInteger!) { + targetNodes(filter: { targetIds: [$id] }) { + target { + activeRecordings(filter: { + labels: ["origin=${origin}"] + }) { + data { + remoteId + } + } + } + } + } + `, + { id: target.id }, + ).pipe( + map((resp) => { + const nodes = resp.data?.targetNodes ?? []; + if (nodes.length === 0) { + return undefined; + } + const data = nodes[0]?.target?.activeRecordings?.data ?? []; + if (data.length === 0) { + return undefined; + } + return data[0]?.remoteId; + }), + ); + } + + targetHasJFRMetricsRecording(target: TargetStub, filter: ActiveRecordingsFilterInput = {}): Observable { return this.graphql( ` query ActiveRecordingsForJFRMetrics($id: BigInteger!, $recordingFilter: ActiveRecordingsFilterInput) { diff --git a/src/app/utils/fakeData.ts b/src/app/utils/fakeData.ts index f7e294bdb..b451ae4fe 100644 --- a/src/app/utils/fakeData.ts +++ b/src/app/utils/fakeData.ts @@ -303,7 +303,7 @@ class FakeApiService extends ApiService { } // JFR Metrics card - targetHasRecording(_target: Target, _filter?: ActiveRecordingsFilterInput): Observable { + targetHasJFRMetricsRecording(_target: Target, _filter?: ActiveRecordingsFilterInput): Observable { return of(true); } From ff070d18fd81fd5ba0b7ef9bee5c2c8abfc49164 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Sep 2024 14:45:30 -0400 Subject: [PATCH 19/25] use jvmId rather than connectUrl where possible --- src/app/RecordingMetadata/BulkEditLabels.tsx | 4 +-- src/app/Recordings/ActiveRecordingsTable.tsx | 8 +++--- .../Recordings/ArchivedRecordingsTable.tsx | 2 +- src/app/Shared/Services/Services.tsx | 2 +- src/app/Shared/Services/Targets.service.tsx | 2 -- src/app/Topology/Actions/utils.tsx | 8 +++--- src/app/Topology/Entity/utils.tsx | 10 +------ src/mirage/index.ts | 26 +++++++++++++------ 8 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/app/RecordingMetadata/BulkEditLabels.tsx b/src/app/RecordingMetadata/BulkEditLabels.tsx index 60aba36cf..0e294bb74 100644 --- a/src/app/RecordingMetadata/BulkEditLabels.tsx +++ b/src/app/RecordingMetadata/BulkEditLabels.tsx @@ -259,9 +259,9 @@ export const BulkEditLabels: React.FC = ({ const event = parts[1]; const isMatch = - currentTarget?.connectUrl === event.message.target || + currentTarget?.jvmId === event.message.jvmId || currentTarget?.jvmId === event.message.recording.jvmId || - currentTarget?.connectUrl === 'uploads'; + currentTarget?.jvmId === 'uploads'; setRecordings((oldRecordings) => { return oldRecordings.map((recording) => { diff --git a/src/app/Recordings/ActiveRecordingsTable.tsx b/src/app/Recordings/ActiveRecordingsTable.tsx index ce68b8941..9f2e64d76 100644 --- a/src/app/Recordings/ActiveRecordingsTable.tsx +++ b/src/app/Recordings/ActiveRecordingsTable.tsx @@ -257,7 +257,7 @@ export const ActiveRecordingsTable: React.FC = (prop context.notificationChannel.messages(NotificationCategory.SnapshotCreated), ), ]).subscribe(([currentTarget, event]) => { - if (currentTarget?.connectUrl != event.message.target && currentTarget?.jvmId != event.message.jvmId) { + if (currentTarget?.jvmId != event.message.jvmId) { return; } setRecordings((old) => old.concat([event.message.recording])); @@ -274,7 +274,7 @@ export const ActiveRecordingsTable: React.FC = (prop context.notificationChannel.messages(NotificationCategory.SnapshotDeleted), ), ]).subscribe(([currentTarget, event]) => { - if (currentTarget?.connectUrl != event.message.target && currentTarget?.jvmId != event.message.jvmId) { + if (currentTarget?.jvmId != event.message.jvmId) { return; } @@ -290,7 +290,7 @@ export const ActiveRecordingsTable: React.FC = (prop context.target.target(), context.notificationChannel.messages(NotificationCategory.ActiveRecordingStopped), ]).subscribe(([currentTarget, event]) => { - if (currentTarget?.connectUrl != event.message.target && currentTarget?.jvmId != event.message.jvmId) { + if (currentTarget?.jvmId != event.message.jvmId) { return; } setRecordings((old) => { @@ -321,7 +321,7 @@ export const ActiveRecordingsTable: React.FC = (prop context.target.target(), context.notificationChannel.messages(NotificationCategory.RecordingMetadataUpdated), ]).subscribe(([currentTarget, event]) => { - if (currentTarget?.connectUrl != event.message.target && currentTarget?.jvmId != event.message.jvmId) { + if (currentTarget?.jvmId != event.message.jvmId) { return; } setRecordings((old) => { diff --git a/src/app/Recordings/ArchivedRecordingsTable.tsx b/src/app/Recordings/ArchivedRecordingsTable.tsx index 5fee22e5a..8e6c9b5c5 100644 --- a/src/app/Recordings/ArchivedRecordingsTable.tsx +++ b/src/app/Recordings/ArchivedRecordingsTable.tsx @@ -326,7 +326,7 @@ export const ArchivedRecordingsTable: React.FC = ( context.notificationChannel.messages(NotificationCategory.ActiveRecordingSaved), ), ]).subscribe(([currentTarget, event]) => { - if (currentTarget?.connectUrl != event.message.target && currentTarget?.jvmId != event.message.jvmId) { + if (currentTarget?.jvmId != event.message.jvmId) { return; } setRecordings((old) => diff --git a/src/app/Shared/Services/Services.tsx b/src/app/Shared/Services/Services.tsx index 1dd7f78d2..f8058f720 100644 --- a/src/app/Shared/Services/Services.tsx +++ b/src/app/Shared/Services/Services.tsx @@ -39,7 +39,7 @@ const login = new LoginService(settings); const api = new ApiService(target, NotificationsInstance, login); const notificationChannel = new NotificationChannel(NotificationsInstance, login); const reports = new ReportService(login, NotificationsInstance); -const targets = new TargetsService(api, NotificationsInstance, login, notificationChannel); +const targets = new TargetsService(api, NotificationsInstance, notificationChannel); const defaultServices: Services = { target, diff --git a/src/app/Shared/Services/Targets.service.tsx b/src/app/Shared/Services/Targets.service.tsx index 8215821d3..e61966be3 100644 --- a/src/app/Shared/Services/Targets.service.tsx +++ b/src/app/Shared/Services/Targets.service.tsx @@ -19,7 +19,6 @@ import { Observable, BehaviorSubject, of } from 'rxjs'; import { catchError, first, map, tap } from 'rxjs/operators'; import { ApiService } from './Api.service'; import { Target, NotificationCategory, TargetDiscoveryEvent } from './api.types'; -import { LoginService } from './Login.service'; import { NotificationChannel } from './NotificationChannel.service'; import { NotificationService } from './Notifications.service'; @@ -29,7 +28,6 @@ export class TargetsService { constructor( private readonly api: ApiService, private readonly notifications: NotificationService, - login: LoginService, notificationChannel: NotificationChannel, ) { // just trigger a startup query diff --git a/src/app/Topology/Actions/utils.tsx b/src/app/Topology/Actions/utils.tsx index b1672b88e..f31dad221 100644 --- a/src/app/Topology/Actions/utils.tsx +++ b/src/app/Topology/Actions/utils.tsx @@ -26,7 +26,7 @@ import { getAllLeaves, isTargetNode } from '@app/Shared/Services/api.utils'; import { NotificationService } from '@app/Shared/Services/Notifications.service'; import { ContextMenuSeparator } from '@patternfly/react-topology'; import { merge, filter, map, debounceTime } from 'rxjs'; -import { getConnectUrlFromEvent } from '../Entity/utils'; +import { getJvmIdFromEvent } from '../Entity/utils'; import { GraphElement, ListElement } from '../Shared/types'; import { ContextMenuItem } from './NodeActions'; import type { ActionUtils, NodeAction, NodeActionKey, GroupActionResponse, MenuItemVariant } from './types'; @@ -40,11 +40,11 @@ export const isQuickRecording = (recording: ActiveRecording) => { }; export const isQuickRecordingExist = (group: EnvironmentNode, { services }: ActionUtils) => { - const svcUrls = new Set(getAllLeaves(group).map((tn) => tn.target.connectUrl)); + const jvmIds = new Set(getAllLeaves(group).map((tn) => tn.target.jvmId)); const filterFn = (e: NotificationMessage) => { - const targetId = getConnectUrlFromEvent(e); + const jvmId = getJvmIdFromEvent(e); const recording = e.message.recording; - return targetId !== undefined && svcUrls.has(targetId) && isQuickRecording(recording); + return jvmId !== undefined && jvmIds.has(jvmId) && isQuickRecording(recording); }; return merge( diff --git a/src/app/Topology/Entity/utils.tsx b/src/app/Topology/Entity/utils.tsx index 52c7eef5a..d147de6d4 100644 --- a/src/app/Topology/Entity/utils.tsx +++ b/src/app/Topology/Entity/utils.tsx @@ -272,9 +272,6 @@ export const getExpandedResourceDetails = ( } }; -export const getConnectUrlFromEvent = (event: NotificationMessage): string | undefined => { - return event.message.target || event.message.targetId; -}; export const getJvmIdFromEvent = (event: NotificationMessage): string | undefined => { return event.message.jvmId; }; @@ -347,14 +344,9 @@ export const useResources = ( ), ) .subscribe(([targetNode, event]) => { - const extractedUrl = getConnectUrlFromEvent(event); const extractedJvmId = getJvmIdFromEvent(event); const isOwned = isOwnedResource(resourceType); - if ( - !isOwned || - (extractedUrl && extractedUrl === targetNode.target.connectUrl) || - (extractedJvmId && extractedJvmId === targetNode.target.jvmId) - ) { + if (!isOwned || (extractedJvmId && extractedJvmId === targetNode.target.jvmId)) { setLoading(true); setResources((old) => { // Avoid accessing state directly, which diff --git a/src/mirage/index.ts b/src/mirage/index.ts index 0510dd7e4..38e7c7d38 100644 --- a/src/mirage/index.ts +++ b/src/mirage/index.ts @@ -154,7 +154,6 @@ export const startMirage = ({ environment = 'development' } = {}) => { return target ? [ { - connectUrl: target.attrs.connectUrl, jvmId: target.attrs.jvmId, recordings: archives, }, @@ -233,9 +232,10 @@ export const startMirage = ({ environment = 'development' } = {}) => { }); this.get('api/v4/targets/:targetId/recordings', (schema) => schema.all(Resource.RECORDING).models); this.delete('api/v4/targets/:targetId/recordings/:remoteId', (schema, request) => { + const target = schema.findBy(Resource.TARGET, { id: request.params.targetId }); const recording = schema.findBy(Resource.RECORDING, { remoteId: request.params.remoteId }); - if (!recording) { + if (!target || !recording) { return new Response(404); } recording.destroy(); @@ -249,7 +249,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { recording: { ...recording.attrs, }, - target: request.params.targetId, + jvmId: target.jvmId, }, }; websocket.send(JSON.stringify(msg)); @@ -276,7 +276,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { recording: { ...recording.attrs, }, - target: request.params.targetId, + jvmId: target.jvmId, }, }; websocket.send(JSON.stringify(msg)); @@ -299,7 +299,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, message: { recording: archived, - target: request.params.targetId, + jvmId: target.jvmId, }, }; websocket.send(JSON.stringify(msg)); @@ -310,6 +310,10 @@ export const startMirage = ({ environment = 'development' } = {}) => { }); this.post('api/v4/targets/:targetId/snapshot', (schema, request) => { const remoteId = Math.floor(Math.random() * 1000000); + const target = schema.findBy(Resource.TARGET, { id: request.params.targetId }); + if (!target) { + return new Response(404); + } const recording = schema.create(Resource.RECORDING, { // id will generated by Mirage (i.e. increment integers) downloadUrl: '', @@ -343,8 +347,8 @@ export const startMirage = ({ environment = 'development' } = {}) => { type: { type: 'application', subType: 'json' }, }, message: { - target: request.params.targetId, recording, + jvmId: target.jvmId, }, }), ); @@ -524,6 +528,12 @@ export const startMirage = ({ environment = 'development' } = {}) => { const query = body.query.trim(); const variables = body.variables; const begin = query.substring(0, query.indexOf('{')); + let target: any; + if (variables.connectUrl) { + target = schema.findBy(Resource.TARGET, { connectUrl: variables.connectUrl }); + } else if (variables.jvmId) { + target = schema.findBy(Resource.TARGET, { jvmId: variables.jvmId }); + } let name = 'unknown'; for (const n of begin.split(' ')) { if (n == '{') { @@ -634,7 +644,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, message: { recordingName: variables.recordingName, - target: variables.connectUrl, + target: target?.jvmId ?? 'unknown', metadata: { labels: labelsArray, }, @@ -678,7 +688,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { }, message: { recordingName: variables.recordingName, - target: variables.connectUrl, + target: target?.jvmId ?? 'unknown', metadata: { labels: labelsArray, }, From 1654241c0489db5779ec2bfcb9ce604a60a2599a Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Sep 2024 16:29:04 -0400 Subject: [PATCH 20/25] include target details in notification bodies --- src/app/Shared/Services/api.utils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/Shared/Services/api.utils.ts b/src/app/Shared/Services/api.utils.ts index 24448f82d..8c030d150 100644 --- a/src/app/Shared/Services/api.utils.ts +++ b/src/app/Shared/Services/api.utils.ts @@ -261,7 +261,7 @@ export const messageKeys = new Map([ { variant: AlertVariant.success, title: 'Recording stopped', - body: (evt) => `${evt.message.recording.name} was stopped`, + body: (evt) => `${evt.message.recording.name} in target ${evt.message.target} was stopped`, } as NotificationMessageMapper, ], [ @@ -269,7 +269,7 @@ export const messageKeys = new Map([ { variant: AlertVariant.success, title: 'Recording saved', - body: (evt) => `${evt.message.recording.name} was archived`, + body: (evt) => `${evt.message.recording.name} in target ${evt.message.target} was archived`, } as NotificationMessageMapper, ], [ @@ -277,7 +277,7 @@ export const messageKeys = new Map([ { variant: AlertVariant.success, title: 'Recording deleted', - body: (evt) => `${evt.message.recording.name} was deleted`, + body: (evt) => `${evt.message.recording.name} in target ${evt.message.target} was deleted`, } as NotificationMessageMapper, ], [ @@ -293,7 +293,7 @@ export const messageKeys = new Map([ { variant: AlertVariant.success, title: 'Snapshot deleted', - body: (evt) => `${evt.message.recording.name} was deleted`, + body: (evt) => `${evt.message.recording.name} in target ${evt.message.target} was deleted`, } as NotificationMessageMapper, ], [ @@ -389,7 +389,7 @@ export const messageKeys = new Map([ { variant: AlertVariant.success, title: 'Recording metadata updated', - body: (evt) => `${evt.message.recording.name} metadata was updated`, + body: (evt) => `${evt.message.recording.name} in target ${evt.message.target} metadata was updated`, } as NotificationMessageMapper, ], [ From c66298f6a4e3e91d03dc73827fa66c197db99eb7 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 16 Sep 2024 10:48:47 -0400 Subject: [PATCH 21/25] remove credential numMatchingTargets field, use targets.length --- .../Credentials/StoredCredentials.tsx | 30 +++++++++---------- src/app/Shared/Services/Api.service.tsx | 3 +- src/app/Shared/Services/api.types.ts | 7 +---- src/app/Topology/Entity/types.ts | 4 +-- src/app/Topology/Entity/utils.tsx | 8 ++--- src/app/utils/fakeData.ts | 4 +-- src/mirage/index.ts | 4 +-- 7 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/app/SecurityPanel/Credentials/StoredCredentials.tsx b/src/app/SecurityPanel/Credentials/StoredCredentials.tsx index f3deb9870..d4b30febd 100644 --- a/src/app/SecurityPanel/Credentials/StoredCredentials.tsx +++ b/src/app/SecurityPanel/Credentials/StoredCredentials.tsx @@ -18,7 +18,7 @@ import { DeleteOrDisableWarningType } from '@app/Modal/types'; import { JmxAuthDescription } from '@app/Shared/Components/JmxAuthDescription'; import { LoadingView } from '@app/Shared/Components/LoadingView'; import { MatchExpressionDisplay } from '@app/Shared/Components/MatchExpression/MatchExpressionDisplay'; -import { StoredCredential, NotificationCategory } from '@app/Shared/Services/api.types'; +import { MatchedCredential, NotificationCategory } from '@app/Shared/Services/api.types'; import { ServiceContext } from '@app/Shared/Services/Services'; import { useSort } from '@app/utils/hooks/useSort'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; @@ -68,7 +68,7 @@ import { SecurityCard } from '../types'; import { CreateCredentialModal } from './CreateCredentialModal'; import { MatchedTargetsTable } from './MatchedTargetsTable'; -export const includesCredential = (credentials: StoredCredential[], credential: StoredCredential): boolean => { +export const includesCredential = (credentials: MatchedCredential[], credential: MatchedCredential): boolean => { return credentials.some((cred) => cred.id === credential.id); }; @@ -84,16 +84,16 @@ const enum Actions { } interface State { - credentials: StoredCredential[]; - expandedCredentials: StoredCredential[]; - checkedCredentials: StoredCredential[]; + credentials: MatchedCredential[]; + expandedCredentials: MatchedCredential[]; + checkedCredentials: MatchedCredential[]; isHeaderChecked: boolean; } const reducer = (state: State, action) => { switch (action.type) { case Actions.HANDLE_REFRESH: { - const credentials: StoredCredential[] = action.payload.credentials; + const credentials: MatchedCredential[] = action.payload.credentials; const updatedCheckedCredentials = state.checkedCredentials.filter((cred) => includesCredential(credentials, cred), ); @@ -118,7 +118,7 @@ const reducer = (state: State, action) => { }; } case Actions.HANDLE_CREDENTIALS_DELETED_NOTIFICATION: { - const deletedCredential: StoredCredential = action.payload.credential; + const deletedCredential: MatchedCredential = action.payload.credential; const updatedCheckedCredentials = state.checkedCredentials.filter((o) => o.id !== deletedCredential.id); return { @@ -155,8 +155,8 @@ const reducer = (state: State, action) => { case Actions.HANDLE_ATLEAST_ONE_MATCH_ROW_CHECK: case Actions.HANDLE_NO_MATCH_ROW_CHECK: { const noMatch = action.payload.noMatch; - const checkedCredentials = state.credentials.filter(({ numMatchingTargets }) => - noMatch ? numMatchingTargets === 0 : numMatchingTargets > 0, + const checkedCredentials = state.credentials.filter(({ targets }) => + noMatch ? targets.length === 0 : targets.length > 0, ); return { ...state, @@ -165,7 +165,7 @@ const reducer = (state: State, action) => { }; } case Actions.HANDLE_TOGGLE_EXPANDED: { - const credential: StoredCredential = action.payload.credential; + const credential: MatchedCredential = action.payload.credential; const matched = state.expandedCredentials.some((o) => o.id === credential.id); const updated = state.expandedCredentials.filter((o) => o.id !== credential.id); if (!matched) { @@ -202,9 +202,9 @@ export const StoredCredentials = () => { const context = React.useContext(ServiceContext); const addSubscription = useSubscriptions(); const [state, dispatch] = React.useReducer(reducer, { - credentials: [] as StoredCredential[], - expandedCredentials: [] as StoredCredential[], - checkedCredentials: [] as StoredCredential[], + credentials: [] as MatchedCredential[], + expandedCredentials: [] as MatchedCredential[], + checkedCredentials: [] as MatchedCredential[], isHeaderChecked: false, } as State); const [sortBy, getSortParams] = useSort(); @@ -216,7 +216,7 @@ export const StoredCredentials = () => { const refreshStoredCredentialsAndCounts = React.useCallback(() => { setIsLoading(true); addSubscription( - context.api.getCredentials().subscribe((credentials: StoredCredential[]) => { + context.api.getCredentials().subscribe((credentials: MatchedCredential[]) => { dispatch({ type: Actions.HANDLE_REFRESH, payload: { credentials: credentials } }); setIsLoading(false); }), @@ -425,7 +425,7 @@ export const StoredCredentials = () => { - {credential.numMatchingTargets} + {credential.targets.length} diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 5b9526e6f..3dd05946c 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -47,7 +47,6 @@ import { ArchivedRecording, UPLOADS_SUBDIRECTORY, MatchedCredential, - StoredCredential, EnvironmentNode, ActiveRecordingsFilterInput, RecordingCountResponse, @@ -1028,7 +1027,7 @@ export class ApiService { ); } - getCredentials(suppressNotifications = false, skipStatusCheck = false): Observable { + getCredentials(suppressNotifications = false, skipStatusCheck = false): Observable { return this.sendRequest( 'v4', `credentials`, diff --git a/src/app/Shared/Services/api.types.ts b/src/app/Shared/Services/api.types.ts index bb20c9c09..98a25614d 100644 --- a/src/app/Shared/Services/api.types.ts +++ b/src/app/Shared/Services/api.types.ts @@ -284,13 +284,8 @@ export interface RecordingCountResponse { // ====================================== // Credential resources // ====================================== -export interface StoredCredential { - id: number; - matchExpression: string; - numMatchingTargets: number; -} - export interface MatchedCredential { + id: number; matchExpression: string; targets: Target[]; } diff --git a/src/app/Topology/Entity/types.ts b/src/app/Topology/Entity/types.ts index 631c06436..3a8c66a16 100644 --- a/src/app/Topology/Entity/types.ts +++ b/src/app/Topology/Entity/types.ts @@ -21,7 +21,7 @@ import type { NotificationMessage, Recording, Rule, - StoredCredential, + MatchedCredential, } from '@app/Shared/Services/api.types'; import { Observable } from 'rxjs'; @@ -38,7 +38,7 @@ export type PatchFn = ( removed?: boolean, ) => Observable; -export type ResourceTypes = Recording | EventTemplate | EventType | EventProbe | Rule | StoredCredential; +export type ResourceTypes = Recording | EventTemplate | EventType | EventProbe | Rule | MatchedCredential; // Note: Values will be word split to used as display names export const TargetOwnedResourceTypeAsArray = [ diff --git a/src/app/Topology/Entity/utils.tsx b/src/app/Topology/Entity/utils.tsx index d147de6d4..ab319260a 100644 --- a/src/app/Topology/Entity/utils.tsx +++ b/src/app/Topology/Entity/utils.tsx @@ -18,7 +18,7 @@ import { ApiService } from '@app/Shared/Services/Api.service'; import { TargetNode, Rule, - StoredCredential, + MatchedCredential, NotificationCategory, NotificationMessage, Recording, @@ -108,7 +108,7 @@ export const getTargetOwnedResources = ( apiService.isTargetMatched(crd.matchExpression, target).pipe(map((ok) => (ok ? [crd] : []))), ); return forkJoin(tasks).pipe( - defaultIfEmpty([[] as StoredCredential[]]), + defaultIfEmpty([[] as MatchedCredential[]]), map((credentials) => credentials.reduce((prev, curr) => prev.concat(curr))), ); }), @@ -217,8 +217,8 @@ export const getResourceListPatchFn = ( ); }; case 'credentials': - return (arr: StoredCredential[], eventData: NotificationMessage, removed?: boolean) => { - const credential: StoredCredential = eventData.message; + return (arr: MatchedCredential[], eventData: NotificationMessage, removed?: boolean) => { + const credential: MatchedCredential = eventData.message; return apiService.isTargetMatched(credential.matchExpression, target).pipe( map((ok) => { diff --git a/src/app/utils/fakeData.ts b/src/app/utils/fakeData.ts index b451ae4fe..cfc6a0985 100644 --- a/src/app/utils/fakeData.ts +++ b/src/app/utils/fakeData.ts @@ -28,7 +28,7 @@ import { EventTemplate, EventProbe, Rule, - StoredCredential, + MatchedCredential, RecordingAttributes, NullableTarget, EventType, @@ -345,7 +345,7 @@ class FakeApiService extends ApiService { return of([]); } - getCredentials(_suppressNotifications?: boolean, _skipStatusCheck?: boolean): Observable { + getCredentials(_suppressNotifications?: boolean, _skipStatusCheck?: boolean): Observable { return of([]); } diff --git a/src/mirage/index.ts b/src/mirage/index.ts index 38e7c7d38..8a8676341 100644 --- a/src/mirage/index.ts +++ b/src/mirage/index.ts @@ -504,7 +504,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { this.post('api/v4/credentials', (schema, request) => { const credential = schema.create(Resource.CREDENTIAL, { matchExpression: (request.requestBody as any).get('matchExpression'), - numMatchingTargets: 0, + targets: [], }); websocket.send( JSON.stringify({ @@ -515,7 +515,7 @@ export const startMirage = ({ environment = 'development' } = {}) => { message: { id: credential.id, matchExpression: credential.matchExpression, - numMatchingTargets: credential.numMatchingTargets, + targets: [], }, }), ); From 70cb1189069ac7de29e4eb0bd407516fb1aa7349 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 16 Sep 2024 11:51:42 -0400 Subject: [PATCH 22/25] yarn format:apply --- src/app/CreateRecording/CustomRecordingForm.tsx | 4 +--- src/app/Shared/Services/Api.service.tsx | 6 +++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/CreateRecording/CustomRecordingForm.tsx b/src/app/CreateRecording/CustomRecordingForm.tsx index cbe92b236..60eb68674 100644 --- a/src/app/CreateRecording/CustomRecordingForm.tsx +++ b/src/app/CreateRecording/CustomRecordingForm.tsx @@ -274,9 +274,7 @@ export const CustomRecordingForm: React.FC = () => { addSubscription( forkJoin({ templates: context.api.getTargetEventTemplates(target), - recordingOptions: context.api.doGet( - `targets/${target.id}/recordingOptions`, - ), + recordingOptions: context.api.doGet(`targets/${target.id}/recordingOptions`), }).subscribe({ next: ({ templates, recordingOptions }) => { setErrorMessage(''); diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 3dd05946c..1f3e7c829 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -1345,7 +1345,11 @@ export class ApiService { ); } - getTargetEventTypes(target: TargetStub, suppressNotifications = false, skipStatusCheck = false): Observable { + getTargetEventTypes( + target: TargetStub, + suppressNotifications = false, + skipStatusCheck = false, + ): Observable { return this.doGet( `targets/${target.id}/events`, 'v4', From 6900f207a7a1b35de3be0d992430e4f9f68099a2 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 16 Sep 2024 11:52:11 -0400 Subject: [PATCH 23/25] rebase fixup --- src/app/Shared/Services/Api.service.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/Shared/Services/Api.service.tsx b/src/app/Shared/Services/Api.service.tsx index 1f3e7c829..2ec5037ae 100644 --- a/src/app/Shared/Services/Api.service.tsx +++ b/src/app/Shared/Services/Api.service.tsx @@ -181,7 +181,7 @@ export class ApiService { } getTargets(): Observable { - return this.doGet('targets', 'v3'); + return this.doGet('targets', 'v4'); } createTarget( From e6d2b496c908ec642aac03b794ea30d962a81224 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:23:22 -0400 Subject: [PATCH 24/25] build(deps-dev): bump webpack from 5.94.0 to 5.95.0 (#1427) Bumps [webpack](https://github.com/webpack/webpack) from 5.94.0 to 5.95.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.94.0...v5.95.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2d42303f4..8811ce733 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "typescript": "^5.6.2", "url-loader": "^4.1.1", "wait-on": "^8.0.1", - "webpack": "^5.94.0", + "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.1.0", diff --git a/yarn.lock b/yarn.lock index a940ad4ef..2075bac8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4427,7 +4427,7 @@ __metadata: typescript: ^5.6.2 url-loader: ^4.1.1 wait-on: ^8.0.1 - webpack: ^5.94.0 + webpack: ^5.95.0 webpack-bundle-analyzer: ^4.10.2 webpack-cli: ^5.1.4 webpack-dev-server: ^5.1.0 @@ -14646,9 +14646,9 @@ __metadata: languageName: node linkType: hard -"webpack@npm:^5.94.0": - version: 5.94.0 - resolution: "webpack@npm:5.94.0" +"webpack@npm:^5.95.0": + version: 5.95.0 + resolution: "webpack@npm:5.95.0" dependencies: "@types/estree": ^1.0.5 "@webassemblyjs/ast": ^1.12.1 @@ -14678,7 +14678,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 6a3d667be304a69cd6dcb8d676bc29f47642c0d389af514cfcd646eaaa809961bc6989fc4b2621a717dfc461130f29c6e20006d62a32e012dafaa9517813a4e6 + checksum: 0c3dfe288de4d62f8f3dc25478a618894883cab739121330763b7847e43304630ea2815ae2351a5f8ff6ab7c9642caf530d503d89bda261fe2cd220e524dd5d1 languageName: node linkType: hard From 9d620aa79251984ea32d26c0aa82308a9dcdd3d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:11:13 -0400 Subject: [PATCH 25/25] build(deps-dev): bump eslint-plugin-react from 7.36.1 to 7.37.0 (#1428) Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.36.1 to 7.37.0. - [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases) - [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md) - [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.36.1...v7.37.0) --- updated-dependencies: - dependency-name: eslint-plugin-react dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8811ce733..726fe11fd 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "css-minimizer-webpack-plugin": "^7.0.0", "eslint": "^8.57.0", "eslint-plugin-import": "^2.30.0", - "eslint-plugin-react": "^7.36.1", + "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-unused-imports": "^4.1.4", "eslint-webpack-plugin": "^4.2.0", diff --git a/yarn.lock b/yarn.lock index 2075bac8d..0564ae909 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4381,7 +4381,7 @@ __metadata: dayjs: ^1.11.13 eslint: ^8.57.0 eslint-plugin-import: ^2.30.0 - eslint-plugin-react: ^7.36.1 + eslint-plugin-react: ^7.37.0 eslint-plugin-react-hooks: ^4.6.2 eslint-plugin-unused-imports: ^4.1.4 eslint-webpack-plugin: ^4.2.0 @@ -6197,9 +6197,9 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react@npm:^7.36.1": - version: 7.36.1 - resolution: "eslint-plugin-react@npm:7.36.1" +"eslint-plugin-react@npm:^7.37.0": + version: 7.37.0 + resolution: "eslint-plugin-react@npm:7.37.0" dependencies: array-includes: ^3.1.8 array.prototype.findlast: ^1.2.5 @@ -6221,7 +6221,7 @@ __metadata: string.prototype.repeat: ^1.0.0 peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - checksum: bf3be414f3d639200a7d91feeaa6beec3397feed93ab22eaecef44dda37ecbd01812ed1720c72a9861fb276d3543cea69a834a66f64de3d878796fef4f4bf129 + checksum: ece92e28b34ced0fd6bddebd41234ee53187b236fd507abef4f61cc868e27edd94fb7e290f44ff546037a6862c3302e848185a5e6511e2bcdf1883a1bfaa4ffc languageName: node linkType: hard