Skip to content

Add link detection to tracing drawer

Daniele Rossetti requested to merge drosse/tracing-drawer-link-detection into master

What does this MR do and why?

Tracing Details - Detect links in attributes drawer and make them clickable

Closes gitlab-org/opstrace/opstrace#2571 (closed)

MR acceptance checklist

Screenshots or screen recordings

Before After
image image

How to set up and validate locally

  • Enable observability_tracing feature flag

Apply patch to load mocks ( pbpaste | git apply --allow-empty )

diff --git a/app/assets/javascripts/observability/client.js b/app/assets/javascripts/observability/client.js
index 3a793c9dc140..df301594f2e7 100644
--- a/app/assets/javascripts/observability/client.js
+++ b/app/assets/javascripts/observability/client.js
@@ -1,21 +1,38 @@
+/* eslint-disable @gitlab/require-i18n-strings */
 import * as Sentry from '~/sentry/sentry_browser_wrapper';
 import axios from '~/lib/utils/axios_utils';
 import { logError } from '~/lib/logger';
 import { DEFAULT_SORTING_OPTION, SORTING_OPTIONS } from './constants';

+const MOCK_METRICS = {
+  metrics: [],
+};
+
+const MOCK_TRACES = {
+  project_id: 51792562,
+  traces: [],
+  total_traces: 500,
+  next_page_token:
+    'eyJsYXN0X3NlZW5fdGltZXN0YW1wIjoiMjAyMy0xMS0xMCAxNjoyNzo0Ny4xMDQ0NzcwOTIiLCJzZWVuX3RyYWNlX2lkcyI6WyI2ZTI0NjFjZS04MWVmLTRkYWItN2ZmYS1hMTRlMDRiODhmNWUiXX0=',
+};
+
 function reportErrorAndThrow(e) {
   logError(e);
   Sentry.captureException(e);
   throw e;
 }
+
+function mockReturnDataWithDelay(data) {
+  return new Promise((resolve) => {
+    setTimeout(() => resolve(data), 500);
+  });
+}
+
 // Provisioning API spec: https://gitlab.com/gitlab-org/opstrace/opstrace/-/blob/main/provisioning-api/pkg/provisioningapi/routes.go#L59
 async function enableObservability(provisioningUrl) {
   try {
-    // Note: axios.put(url, undefined, {withCredentials: true}) does not send cookies properly, so need to use the API below for the correct behaviour
-    return await axios(provisioningUrl, {
-      method: 'put',
-      withCredentials: true,
-    });
+    console.log('[DEBUG] Enabling Observability');
+    return mockReturnDataWithDelay();
   } catch (e) {
     return reportErrorAndThrow(e);
   }
@@ -24,11 +41,12 @@ async function enableObservability(provisioningUrl) {
 // Provisioning API spec: https://gitlab.com/gitlab-org/opstrace/opstrace/-/blob/main/provisioning-api/pkg/provisioningapi/routes.go#L37
 async function isObservabilityEnabled(provisioningUrl) {
   try {
-    const { data } = await axios.get(provisioningUrl, { withCredentials: true });
+    console.log('[DEBUG] Checking Observability Enabled');
+    const data = { status: 'ready' };
     if (data && data.status) {
       // we currently ignore the 'status' payload and just check if the request was successful
       // We might improve this as part of https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2315
-      return true;
+      return mockReturnDataWithDelay(true);
     }
   } catch (e) {
     if (e.response.status === 404) {
@@ -40,19 +58,141 @@ async function isObservabilityEnabled(provisioningUrl) {
 }

 async function fetchTrace(tracingUrl, traceId) {
-  try {
-    if (!traceId) {
-      throw new Error('traceId is required.');
-    }
-
-    const { data } = await axios.get(`${tracingUrl}/${traceId}`, {
-      withCredentials: true,
-    });
-
-    return data;
-  } catch (e) {
-    return reportErrorAndThrow(e);
-  }
+  console.log(`[DEBUG] Fetch trace ${traceId} from ${tracingUrl}`);
+  return mockReturnDataWithDelay({
+    timestamp: '2023-11-30T10:18:50.197Z',
+    timestamp_nano: 1701339530197000000,
+    trace_id: '85d44543-4c9c-2ced-5e75-18845b4157c9',
+    service_name: 'GitLab pipelines',
+    operation: 'https://gitlab.com/gitlab-org/gitlab/-/pipelines/1090600528',
+    status_code: 'STATUS_CODE_UNSET',
+    duration_nano: 4143985000000,
+    total_spans: 231,
+    spans: [
+      {
+        timestamp: '2023-11-30T10:18:50.197Z',
+        timestamp_nano: 1701339530197000000,
+        span_id: 'EE5CBB255408D598',
+        trace_id: '85d44543-4c9c-2ced-5e75-18845b4157c9',
+        service_name: 'GitLab pipelines',
+        operation: 'https://gitlab.com/gitlab-org/gitlab/-/pipelines/1090600528',
+        duration_nano: 4143985000000,
+        parent_span_id: '',
+        status_code: 'STATUS_CODE_UNSET',
+        status_message: '',
+        trace_state: '',
+        span_kind: 'SPAN_KIND_INTERNAL',
+        scope_name: '',
+        scope_version: '',
+        span_attributes: {
+          created_at: '2023-11-30T10:18:36.367Z',
+          duration: '4147',
+          finished_at: '2023-11-30T11:27:54.182Z',
+          queued_duration: '13',
+          ref: 'refs/merge-requests/138112/merge',
+          started_at: '2023-11-30T10:18:50.197Z',
+          status: 'success',
+          updated_at: '2023-11-30T11:27:54.197Z',
+          user: 'gitlab-bot',
+          web_url: 'https://gitlab.com/gitlab-org/gitlab/-/pipelines/1090600528',
+        },
+        resource_attributes: {
+          'process.command': '/Users/gitlab/.asdf/installs/ruby/3.2.2/bin/rake',
+          'process.pid': '33954',
+          'process.runtime.description':
+            'ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]',
+          'process.runtime.name': 'ruby',
+          'process.runtime.version': '3.2.2',
+          'service.name': 'GitLab pipelines',
+          'telemetry.sdk.language': 'ruby',
+          'telemetry.sdk.name': 'opentelemetry',
+          'telemetry.sdk.version': '1.3.1',
+        },
+      },
+      {
+        timestamp: '2023-11-30T10:18:45.411Z',
+        timestamp_nano: 1701339525411000000,
+        span_id: 'F924C183EA08B48F',
+        trace_id: '85d44543-4c9c-2ced-5e75-18845b4157c9',
+        service_name: 'GitLab pipelines',
+        operation: 'rails-production-server-boot-puma-example',
+        duration_nano: 250128000000,
+        parent_span_id: 'EE5CBB255408D598',
+        status_code: 'STATUS_CODE_UNSET',
+        status_message: '',
+        trace_state: '',
+        span_kind: 'SPAN_KIND_INTERNAL',
+        scope_name: '',
+        scope_version: '',
+        span_attributes: {
+          allow_failure: 'false',
+          duration: '250.128455',
+          finished_at: '2023-11-30T10:22:55.539Z',
+          id: '5651404193',
+          job_name: 'rails-production-server-boot-puma-example',
+          queued_duration: '0.310679',
+          stage: 'preflight',
+          started_at: '2023-11-30T10:18:45.411Z',
+          status: 'success',
+          user: 'gitlab-bot',
+          web_url: 'https://gitlab.com/gitlab-org/gitlab/-/jobs/5651404193',
+        },
+        resource_attributes: {
+          'process.command': '/Users/gitlab/.asdf/installs/ruby/3.2.2/bin/rake',
+          'process.pid': '33954',
+          'process.runtime.description':
+            'ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]',
+          'process.runtime.name': 'ruby',
+          'process.runtime.version': '3.2.2',
+          'service.name': 'GitLab pipelines',
+          'telemetry.sdk.language': 'ruby',
+          'telemetry.sdk.name': 'opentelemetry',
+          'telemetry.sdk.version': '1.3.1',
+        },
+      },
+      {
+        timestamp: '2023-11-30T10:18:46.145Z',
+        timestamp_nano: 1701339526145000000,
+        span_id: 'EBBE05DFF04D1F40',
+        trace_id: '85d44543-4c9c-2ced-5e75-18845b4157c9',
+        service_name: 'GitLab pipelines',
+        operation: 'rails-production-server-boot-puma-cng',
+        duration_nano: 227918000000,
+        parent_span_id: 'EE5CBB255408D598',
+        status_code: 'STATUS_CODE_UNSET',
+        status_message: '',
+        trace_state: '',
+        span_kind: 'SPAN_KIND_INTERNAL',
+        scope_name: '',
+        scope_version: '',
+        span_attributes: {
+          allow_failure: 'false',
+          duration: '227.918133',
+          finished_at: '2023-11-30T10:22:34.063Z',
+          id: '5651404196',
+          job_name: 'rails-production-server-boot-puma-cng',
+          queued_duration: '0.748236',
+          stage: 'preflight',
+          started_at: '2023-11-30T10:18:46.145Z',
+          status: 'success',
+          user: 'gitlab-bot',
+          web_url: 'https://gitlab.com/gitlab-org/gitlab/-/jobs/5651404196',
+        },
+        resource_attributes: {
+          'process.command': '/Users/gitlab/.asdf/installs/ruby/3.2.2/bin/rake',
+          'process.pid': '33954',
+          'process.runtime.description':
+            'ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]',
+          'process.runtime.name': 'ruby',
+          'process.runtime.version': '3.2.2',
+          'service.name': 'GitLab pipelines',
+          'telemetry.sdk.language': 'ruby',
+          'telemetry.sdk.name': 'opentelemetry',
+          'telemetry.sdk.version': '1.3.1',
+        },
+      },
+    ],
+  });
 }

 /**
@@ -198,15 +338,15 @@ async function fetchTraces(tracingUrl, { filters = {}, pageToken, pageSize, sort
     : DEFAULT_SORTING_OPTION;
   params.append('sort', sortOrder);

+  console.log(`[DEBUG] Fetching traces with params: ${params.toString()}`);
+
   try {
-    const { data } = await axios.get(tracingUrl, {
-      withCredentials: true,
-      params,
-    });
+    const data = MOCK_TRACES;
+
     if (!Array.isArray(data.traces)) {
       throw new Error('traces are missing/invalid in the response'); // eslint-disable-line @gitlab/require-i18n-strings
     }
-    return data;
+    return mockReturnDataWithDelay(data);
   } catch (e) {
     return reportErrorAndThrow(e);
   }
@@ -214,15 +354,17 @@ async function fetchTraces(tracingUrl, { filters = {}, pageToken, pageSize, sort

 async function fetchServices(servicesUrl) {
   try {
-    const { data } = await axios.get(servicesUrl, {
-      withCredentials: true,
-    });
+    console.log(`[DEBUG] Fetching services from ${servicesUrl}`);
+    const uniqueServices = new Set(
+      MOCK_TRACES.traces.map((t) => t.spans.map((s) => s.service_name)).flat(),
+    );
+    const data = { services: Array.from(uniqueServices).map((s) => ({ name: s })) };

     if (!Array.isArray(data.services)) {
       throw new Error('failed to fetch services. invalid response'); // eslint-disable-line @gitlab/require-i18n-strings
     }

-    return data.services;
+    return mockReturnDataWithDelay(data.services);
   } catch (e) {
     return reportErrorAndThrow(e);
   }
@@ -237,15 +379,21 @@ async function fetchOperations(operationsUrl, serviceName) {
       throw new Error('fetchOperations() - operationsUrl must contain $SERVICE_NAME$');
     }
     const url = operationsUrl.replace('$SERVICE_NAME$', serviceName);
-    const { data } = await axios.get(url, {
-      withCredentials: true,
-    });
+
+    console.log('[DEBUG] fetching operations suggestions from', url); // eslint-disable-line @gitlab/require-i18n-strings
+    const uniqOps = new Set(
+      MOCK_TRACES.traces
+        .map((t) => t.spans.filter((s) => s.service_name === serviceName))
+        .flat()
+        .map((s) => s.operation),
+    );
+    const data = { operations: Array.from(uniqOps).map((s) => ({ name: s })) };

     if (!Array.isArray(data.operations)) {
       throw new Error('failed to fetch operations. invalid response'); // eslint-disable-line @gitlab/require-i18n-strings
     }

-    return data.operations;
+    return mockReturnDataWithDelay(data.operations);
   } catch (e) {
     return reportErrorAndThrow(e);
   }
@@ -268,14 +416,12 @@ async function fetchMetrics(metricsUrl, { filters = {}, limit } = {}) {
         }
       }
     }
-    const { data } = await axios.get(metricsUrl, {
-      withCredentials: true,
-      params,
-    });
+    console.log(`[DEBUG] Fetching metrics with params: ${params.toString()}`);
+    const data = MOCK_METRICS;
     if (!Array.isArray(data.metrics)) {
       throw new Error('metrics are missing/invalid in the response'); // eslint-disable-line @gitlab/require-i18n-strings
     }
-    return data;
+    return mockReturnDataWithDelay(data);
   } catch (e) {
     return reportErrorAndThrow(e);
   }
diff --git a/app/assets/javascripts/observability/components/observability_container.vue b/app/assets/javascripts/observability/components/observability_container.vue
index b89c2624f81c..f6cbf7ee771f 100644
--- a/app/assets/javascripts/observability/components/observability_container.vue
+++ b/app/assets/javascripts/observability/components/observability_container.vue
@@ -27,12 +27,12 @@ export default {

     // TODO: Improve local GDK dev experience with tracing https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/2308
     // Uncomment the lines below to to test this locally
-    // setTimeout(() => {
-    //   this.messageHandler({
-    //     data: { type: 'AUTH_COMPLETION', status: 'success' },
-    //     origin: new URL(this.oauthUrl).origin,
-    //   });
-    // }, 2000);
+    setTimeout(() => {
+      this.messageHandler({
+        data: { type: 'AUTH_COMPLETION', status: 'success' },
+        origin: new URL(this.apiConfig.oauthUrl).origin,
+      });
+    }, 2000);
   },
   destroyed() {
     window.removeEventListener('message', this.messageHandler);
Edited by Daniele Rossetti

Merge request reports