From 248281daabcf923372cabc0f883520fb7f194542 Mon Sep 17 00:00:00 2001 From: Greedy-Geek Date: Thu, 4 Jan 2024 11:10:29 +0530 Subject: [PATCH 1/6] fix: copy curl command support in span detail component --- .../components/span-detail/span-data.ts | 1 + .../span-detail/span-detail.component.scss | 10 ++++-- .../span-detail/span-detail.component.ts | 33 ++++++++++++++----- .../span-detail/span-detail.module.ts | 2 ++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/projects/observability/src/shared/components/span-detail/span-data.ts b/projects/observability/src/shared/components/span-detail/span-data.ts index 418c2599a..099863439 100644 --- a/projects/observability/src/shared/components/span-detail/span-data.ts +++ b/projects/observability/src/shared/components/span-detail/span-data.ts @@ -18,4 +18,5 @@ export interface SpanData { exitCallsBreakup?: Dictionary; startTime?: number; logEvents?: LogEvent[]; + requestMethod?: string; } diff --git a/projects/observability/src/shared/components/span-detail/span-detail.component.scss b/projects/observability/src/shared/components/span-detail/span-detail.component.scss index a8a69b924..a2f8ff379 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.component.scss +++ b/projects/observability/src/shared/components/span-detail/span-detail.component.scss @@ -11,8 +11,14 @@ display: flex; flex-direction: column; - .toggle-group { - margin-top: 18px; + .toggle-group-and-actions { + display: flex; + justify-content: space-between; + align-items: center; + + .toggle-group { + margin-top: 18px; + } } .title-header { diff --git a/projects/observability/src/shared/components/span-detail/span-detail.component.ts b/projects/observability/src/shared/components/span-detail/span-detail.component.ts index 56f792111..28e6f0668 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.component.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.component.ts @@ -1,12 +1,13 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { IconType } from '@hypertrace/assets-library'; import { TypedSimpleChanges } from '@hypertrace/common'; -import { ToggleItem } from '@hypertrace/components'; +import { ButtonSize, ToggleItem } from '@hypertrace/components'; import { isEmpty } from 'lodash-es'; import { Observable, ReplaySubject } from 'rxjs'; import { SpanData } from './span-data'; import { SpanDetailLayoutStyle } from './span-detail-layout-style'; import { SpanDetailTab } from './span-detail-tab'; +import { ObservabilityIconType } from '../../icons/observability-icon-type'; @Component({ selector: 'ht-span-detail', @@ -27,13 +28,25 @@ import { SpanDetailTab } from './span-detail-tab';
- - + +
+ + + + +
@@ -106,6 +119,10 @@ export class SpanDetailComponent implements OnChanges { @Input() public showAttributesTab: boolean = true; + + @Input() + public curlCommand?: string; + @Output() public readonly closed: EventEmitter = new EventEmitter(); public showRequestTab?: boolean; diff --git a/projects/observability/src/shared/components/span-detail/span-detail.module.ts b/projects/observability/src/shared/components/span-detail/span-detail.module.ts index 9269cf402..2b79378a6 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.module.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.module.ts @@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { ButtonModule, + CopyToClipboardModule, IconModule, JsonViewerModule, LabelModule, @@ -41,6 +42,7 @@ import { SpanTagsDetailModule } from './tags/span-tags-detail.module'; LogEventsTableModule, ToggleGroupModule, MessageDisplayModule, + CopyToClipboardModule, ], declarations: [SpanDetailComponent], exports: [SpanDetailComponent], From 7ba8c01927887d4d8eacdd2fc8a2b41d91aa572a Mon Sep 17 00:00:00 2001 From: Greedy-Geek Date: Thu, 4 Jan 2024 12:36:48 +0530 Subject: [PATCH 2/6] fix: support to generate curl command --- projects/observability/src/public-api.ts | 3 + .../span-detail/span-detail.component.ts | 19 ++- .../span-detail/span-detail.module.ts | 2 + .../curl-command-generator-util.test.ts | 120 ++++++++++++++++++ .../curl-command-generator-util.ts | 92 ++++++++++++++ 5 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.test.ts create mode 100644 projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.ts diff --git a/projects/observability/src/public-api.ts b/projects/observability/src/public-api.ts index c03881ee7..9e72abaa7 100644 --- a/projects/observability/src/public-api.ts +++ b/projects/observability/src/public-api.ts @@ -413,3 +413,6 @@ export * from './shared/utils/time-range'; // CSV Downloader Service export * from './shared/services/global-csv-download/global-csv-download.service'; + +// Curl Command Generator +export * from './shared/utils/curl-command-generator/curl-command-generator-util'; diff --git a/projects/observability/src/shared/components/span-detail/span-detail.component.ts b/projects/observability/src/shared/components/span-detail/span-detail.component.ts index 28e6f0668..73c5e5b7d 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.component.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.component.ts @@ -2,12 +2,13 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Out import { IconType } from '@hypertrace/assets-library'; import { TypedSimpleChanges } from '@hypertrace/common'; import { ButtonSize, ToggleItem } from '@hypertrace/components'; +import { CurlCommandGeneratorUtil } from '@hypertrace/observability'; import { isEmpty } from 'lodash-es'; import { Observable, ReplaySubject } from 'rxjs'; +import { ObservabilityIconType } from '../../icons/observability-icon-type'; import { SpanData } from './span-data'; import { SpanDetailLayoutStyle } from './span-detail-layout-style'; import { SpanDetailTab } from './span-detail-tab'; -import { ObservabilityIconType } from '../../icons/observability-icon-type'; @Component({ selector: 'ht-span-detail', @@ -39,10 +40,9 @@ import { ObservabilityIconType } from '../../icons/observability-icon-type'; @@ -120,9 +120,6 @@ export class SpanDetailComponent implements OnChanges { @Input() public showAttributesTab: boolean = true; - @Input() - public curlCommand?: string; - @Output() public readonly closed: EventEmitter = new EventEmitter(); public showRequestTab?: boolean; @@ -170,6 +167,16 @@ export class SpanDetailComponent implements OnChanges { this.activeTabSubject.next(tab); } + protected getCurlCommand = (span: SpanData): string => + CurlCommandGeneratorUtil.generateCurlCommand( + span.requestHeaders, + span.requestCookies, + span.requestBody, + span.requestUrl, + span.protocolName ?? '', + span.requestMethod ?? '', + ); + /** * Tabs are added in order: * 1. Request diff --git a/projects/observability/src/shared/components/span-detail/span-detail.module.ts b/projects/observability/src/shared/components/span-detail/span-detail.module.ts index 2b79378a6..648ef995a 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.module.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.module.ts @@ -21,6 +21,7 @@ import { SpanRequestDetailModule } from './request/span-request-detail.module'; import { SpanResponseDetailModule } from './response/span-response-detail.module'; import { SpanDetailComponent } from './span-detail.component'; import { SpanTagsDetailModule } from './tags/span-tags-detail.module'; +import { MemoizeModule } from '@hypertrace/common'; @NgModule({ imports: [ @@ -43,6 +44,7 @@ import { SpanTagsDetailModule } from './tags/span-tags-detail.module'; ToggleGroupModule, MessageDisplayModule, CopyToClipboardModule, + MemoizeModule, ], declarations: [SpanDetailComponent], exports: [SpanDetailComponent], diff --git a/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.test.ts b/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.test.ts new file mode 100644 index 000000000..339335241 --- /dev/null +++ b/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.test.ts @@ -0,0 +1,120 @@ +import { CurlCommandGeneratorUtil } from './curl-command-generator-util'; + +describe('generateCurlCommand', () => { + test('should generate a curl command for HTTP GET requests', () => { + const requestUrl = 'https://example.com'; + const protocol = 'HTTP'; + const methodType = 'GET'; + const requestHeaders = {}; + const requestCookies = {}; + const requestBody = ''; + + const curlCommand = CurlCommandGeneratorUtil.generateCurlCommand( + requestHeaders, + requestCookies, + requestBody, + requestUrl, + protocol, + methodType, + ); + + expect(curlCommand).toEqual(`curl -X GET https://example.com`); + }); + + test('should generate a curl command for HTTP POST requests with headers and body', () => { + const requestUrl = 'https://example.com'; + const protocol = 'HTTP'; + const methodType = 'POST'; + const requestHeaders = { + 'Content-Type': 'application/json', + Accept: 'application/json', + }; + const requestCookies = {}; + const requestBody = '{"name": "John", "age": 30}'; + + const curlCommand = CurlCommandGeneratorUtil.generateCurlCommand( + requestHeaders, + requestCookies, + requestBody, + requestUrl, + protocol, + methodType, + ); + + expect(curlCommand).toEqual( + `curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -d '{"name": "John", "age": 30}' https://example.com`, + ); + }); + + test('should generate a curl command for HTTP POST requests with cookies', () => { + const requestUrl = 'https://example.com'; + const protocol = 'HTTP'; + const methodType = 'POST'; + const requestHeaders = {}; + const requestCookies = { + sessionid: '123456789', + user: 'John Doe', + }; + const requestBody = '{"name": "John", "age": 30}'; + const curlCommand = CurlCommandGeneratorUtil.generateCurlCommand( + requestHeaders, + requestCookies, + requestBody, + requestUrl, + protocol, + methodType, + ); + + expect(curlCommand).toEqual( + `curl -X POST -b 'sessionid=123456789;user=John Doe' -d '{"name": "John", "age": 30}' https://example.com`, + ); + }); + + test('should generate a curl command for HTTP POST requests with headers, cookies, and body', () => { + const requestUrl = 'https://example.com'; + const protocol = 'HTTP'; + const methodType = 'POST'; + const requestHeaders = { + 'Content-Type': 'application/json', + Accept: 'application/json', + }; + const requestCookies = { + sessionid: '123456789', + user: 'John Doe', + }; + const requestBody = '{"name": "John", "age": 30}'; + + const curlCommand = CurlCommandGeneratorUtil.generateCurlCommand( + requestHeaders, + requestCookies, + requestBody, + requestUrl, + protocol, + methodType, + ); + + expect(curlCommand).toEqual( + `curl -X POST -H 'Content-Type: application/json' -H 'Accept: application/json' -b 'sessionid=123456789;user=John Doe' -d '{"name": "John", "age": 30}' https://example.com`, + ); + }); + + test('should return an error message for unsupported protocols', () => { + const requestUrl = 'https://example.com'; + const protocol = 'ftp'; + const methodType = 'POST'; + const requestHeaders = {}; + const requestCookies = {}; + const requestBody = ''; + + const curlCommand = CurlCommandGeneratorUtil.generateCurlCommand( + requestHeaders, + requestCookies, + requestBody, + requestUrl, + protocol, + methodType, + ); + + expect(curlCommand).toEqual('curl command is not supported'); + }); +}); diff --git a/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.ts b/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.ts new file mode 100644 index 000000000..58b55dae9 --- /dev/null +++ b/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.ts @@ -0,0 +1,92 @@ +import { Dictionary } from '@hypertrace/common'; + +export abstract class CurlCommandGeneratorUtil { + private static readonly CURL_COMMAND_NAME: string = 'curl'; + private static readonly HEADER_OPTION: string = '-H'; + private static readonly REQUEST_OPTION: string = '-X'; + private static readonly BODY_OPTION: string = '-d'; + private static readonly COOKIES_OPTION: string = '-b'; + private static readonly SINGLE_SPACE: string = ' '; + private static readonly SINGLE_QUOTES_DELIMITER_CHAR: string = "\\'"; + private static readonly SINGLE_QUOTE_CHAR: string = "'"; + private static readonly SEMI_COLON_CHAR: string = ';'; + private static readonly COLON_CHAR: string = ':'; + private static readonly EQUALS_CHAR: string = '='; + private static readonly GET_METHOD: string = 'GET'; + private static readonly DELETE_METHOD: string = 'DELETE'; + private static readonly NOT_SUPPORTED_MESSAGE: string = 'curl command is not supported'; + + public static generateCurlCommand( + requestHeaders: Dictionary, + requestCookies: Dictionary, + requestBody: string, + requestUrl: string, + protocol: string, + methodType: string, + ): string { + let curlCommand: string = ''; + + if (protocol === Protocol.PROTOCOL_HTTP || protocol === Protocol.PROTOCOL_HTTPS) { + curlCommand += `${this.CURL_COMMAND_NAME}${this.SINGLE_SPACE}${this.REQUEST_OPTION}${this.SINGLE_SPACE}${methodType}${this.SINGLE_SPACE}`; + + if (Object.entries(requestHeaders).length > 0) { + curlCommand += `${this.getHeadersAsString(requestHeaders)}`; + } + + if (Object.entries(requestCookies).length > 0) { + curlCommand += `${this.getCookiesAsString(requestCookies)}`; + } + + // { POST, PUT, PATCH } methodType will have a body, and { GET, DELETE } will not. + if (!(methodType.includes(this.GET_METHOD) || methodType.includes(this.DELETE_METHOD))) { + curlCommand += `${this.BODY_OPTION}${this.SINGLE_SPACE}${this.SINGLE_QUOTE_CHAR}${this.getEnrichedBody( + requestBody, + )}${this.SINGLE_QUOTE_CHAR}${this.SINGLE_SPACE}`; + } + + curlCommand += requestUrl; + } else { + curlCommand += this.NOT_SUPPORTED_MESSAGE; + } + + console.log(curlCommand); + + return curlCommand; + } + + private static getHeadersAsString(requestHeaders: Dictionary): string { + return Object.entries(requestHeaders) + .map( + ([key, value]) => + `${this.HEADER_OPTION}${this.SINGLE_SPACE}${this.SINGLE_QUOTE_CHAR}${key}${this.COLON_CHAR}${this.SINGLE_SPACE}${value}${this.SINGLE_QUOTE_CHAR}${this.SINGLE_SPACE}`, + ) + .join(''); + } + + private static getCookiesAsString(requestCookies: Dictionary): string { + let cookiesString: string = ''; + + cookiesString += `${this.COOKIES_OPTION}${this.SINGLE_SPACE}${this.SINGLE_QUOTE_CHAR}`; + + Object.entries(requestCookies).forEach(([key, value]) => { + cookiesString += `${key}${this.EQUALS_CHAR}${value}${this.SEMI_COLON_CHAR}`; + }); + + if (cookiesString[cookiesString.length - 1] === this.SEMI_COLON_CHAR) { + cookiesString = cookiesString.substr(0, cookiesString.length - 1); + } + + cookiesString += `${this.SINGLE_QUOTE_CHAR}${this.SINGLE_SPACE}`; + + return cookiesString; + } + + private static getEnrichedBody(body: string): string { + return body.replaceAll(this.SINGLE_QUOTE_CHAR, this.SINGLE_QUOTES_DELIMITER_CHAR); + } +} + +const enum Protocol { + PROTOCOL_HTTP = 'HTTP', + PROTOCOL_HTTPS = 'HTTPS', +} From aa854fc7e832f5858267138cfdd26333fc1420ea Mon Sep 17 00:00:00 2001 From: Greedy-Geek Date: Thu, 4 Jan 2024 12:40:24 +0530 Subject: [PATCH 3/6] fix: remove console log --- .../utils/curl-command-generator/curl-command-generator-util.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.ts b/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.ts index 58b55dae9..6d68bfc8b 100644 --- a/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.ts +++ b/projects/observability/src/shared/utils/curl-command-generator/curl-command-generator-util.ts @@ -49,8 +49,6 @@ export abstract class CurlCommandGeneratorUtil { curlCommand += this.NOT_SUPPORTED_MESSAGE; } - console.log(curlCommand); - return curlCommand; } From 44208ffce060420ee4de9456c3733f63cca8acc9 Mon Sep 17 00:00:00 2001 From: Greedy-Geek Date: Thu, 4 Jan 2024 12:55:39 +0530 Subject: [PATCH 4/6] fix: imports --- .../shared/components/span-detail/span-detail.component.ts | 5 +++-- .../src/shared/components/span-detail/span-detail.module.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/projects/observability/src/shared/components/span-detail/span-detail.component.ts b/projects/observability/src/shared/components/span-detail/span-detail.component.ts index 73c5e5b7d..e5cd2ec9f 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.component.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.component.ts @@ -1,14 +1,15 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { IconType } from '@hypertrace/assets-library'; import { TypedSimpleChanges } from '@hypertrace/common'; -import { ButtonSize, ToggleItem } from '@hypertrace/components'; -import { CurlCommandGeneratorUtil } from '@hypertrace/observability'; import { isEmpty } from 'lodash-es'; import { Observable, ReplaySubject } from 'rxjs'; import { ObservabilityIconType } from '../../icons/observability-icon-type'; import { SpanData } from './span-data'; import { SpanDetailLayoutStyle } from './span-detail-layout-style'; import { SpanDetailTab } from './span-detail-tab'; +import { ButtonSize } from '../../../../../components/src/button/button'; +import { ToggleItem } from '../../../../../components/src/toggle-group/toggle-item'; +import { CurlCommandGeneratorUtil } from '../../utils/curl-command-generator/curl-command-generator-util'; @Component({ selector: 'ht-span-detail', diff --git a/projects/observability/src/shared/components/span-detail/span-detail.module.ts b/projects/observability/src/shared/components/span-detail/span-detail.module.ts index 648ef995a..71ddc3530 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.module.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.module.ts @@ -21,7 +21,7 @@ import { SpanRequestDetailModule } from './request/span-request-detail.module'; import { SpanResponseDetailModule } from './response/span-response-detail.module'; import { SpanDetailComponent } from './span-detail.component'; import { SpanTagsDetailModule } from './tags/span-tags-detail.module'; -import { MemoizeModule } from '@hypertrace/common'; +import { MemoizeModule } from '../../../../../common/src/utilities/memoize/memoize.module'; @NgModule({ imports: [ From 79cebff43be0fc7359f6146a6b5a938641a961b1 Mon Sep 17 00:00:00 2001 From: Greedy-Geek Date: Thu, 4 Jan 2024 14:18:33 +0530 Subject: [PATCH 5/6] fix: style update, showCurlCommand config --- .../components/span-detail/span-detail.component.scss | 7 ++----- .../shared/components/span-detail/span-detail.component.ts | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/projects/observability/src/shared/components/span-detail/span-detail.component.scss b/projects/observability/src/shared/components/span-detail/span-detail.component.scss index a2f8ff379..7d79e462d 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.component.scss +++ b/projects/observability/src/shared/components/span-detail/span-detail.component.scss @@ -13,12 +13,9 @@ .toggle-group-and-actions { display: flex; - justify-content: space-between; align-items: center; - - .toggle-group { - margin-top: 18px; - } + gap: 8px; + margin-top: 18px; } .title-header { diff --git a/projects/observability/src/shared/components/span-detail/span-detail.component.ts b/projects/observability/src/shared/components/span-detail/span-detail.component.ts index e5cd2ec9f..93a176dc2 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.component.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.component.ts @@ -41,6 +41,7 @@ import { CurlCommandGeneratorUtil } from '../../utils/curl-command-generator/cur = new EventEmitter(); public showRequestTab?: boolean; From c96c779d17c320b676faa5bcd3a0c280ba58c707 Mon Sep 17 00:00:00 2001 From: Greedy-Geek Date: Thu, 4 Jan 2024 14:20:20 +0530 Subject: [PATCH 6/6] fix: imports --- .../src/shared/components/span-detail/span-detail.component.ts | 3 +-- .../src/shared/components/span-detail/span-detail.module.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/projects/observability/src/shared/components/span-detail/span-detail.component.ts b/projects/observability/src/shared/components/span-detail/span-detail.component.ts index 93a176dc2..5ba1fc63a 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.component.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.component.ts @@ -7,9 +7,8 @@ import { ObservabilityIconType } from '../../icons/observability-icon-type'; import { SpanData } from './span-data'; import { SpanDetailLayoutStyle } from './span-detail-layout-style'; import { SpanDetailTab } from './span-detail-tab'; -import { ButtonSize } from '../../../../../components/src/button/button'; -import { ToggleItem } from '../../../../../components/src/toggle-group/toggle-item'; import { CurlCommandGeneratorUtil } from '../../utils/curl-command-generator/curl-command-generator-util'; +import { ButtonSize, ToggleItem } from '@hypertrace/components'; @Component({ selector: 'ht-span-detail', diff --git a/projects/observability/src/shared/components/span-detail/span-detail.module.ts b/projects/observability/src/shared/components/span-detail/span-detail.module.ts index 71ddc3530..648ef995a 100644 --- a/projects/observability/src/shared/components/span-detail/span-detail.module.ts +++ b/projects/observability/src/shared/components/span-detail/span-detail.module.ts @@ -21,7 +21,7 @@ import { SpanRequestDetailModule } from './request/span-request-detail.module'; import { SpanResponseDetailModule } from './response/span-response-detail.module'; import { SpanDetailComponent } from './span-detail.component'; import { SpanTagsDetailModule } from './tags/span-tags-detail.module'; -import { MemoizeModule } from '../../../../../common/src/utilities/memoize/memoize.module'; +import { MemoizeModule } from '@hypertrace/common'; @NgModule({ imports: [