github.com/grafana/pyroscope@v1.18.0/public/app/services/apps.ts (about)

     1  import {
     2    App,
     3    AppSchema,
     4    appsModel,
     5    PyroscopeAppLabel,
     6    ServiceNameLabel,
     7  } from '@pyroscope/models/app';
     8  import { Result } from '@pyroscope/util/fp';
     9  import { z, ZodError } from 'zod';
    10  import type { RequestError } from '@pyroscope/services/base';
    11  import { parseResponse, request } from '@pyroscope/services/base';
    12  
    13  // SeriesResponse refers to the response from the server, without any manipulation
    14  const SeriesResponseSchema = z.preprocess(
    15    (arg) => {
    16      const noop = { labelsSet: [] };
    17      if (!arg || typeof arg !== 'object') {
    18        return noop;
    19      }
    20  
    21      // The backend may return an empty object ({})
    22      if (!('labelsSet' in arg)) {
    23        return noop;
    24      }
    25  
    26      return arg;
    27    },
    28    z.object({
    29      labelsSet: z.array(
    30        z.object({
    31          labels: z.array(z.object({ name: z.string(), value: z.string() })),
    32        })
    33      ),
    34    })
    35  );
    36  type SeriesResponse = z.infer<typeof SeriesResponseSchema>;
    37  
    38  // Transform SeriesResponseSchema in a list of applications
    39  // It:
    40  // * flattens all labels from the same labelSet into an object (App)
    41  // * remove duplicates
    42  const ListOfAppsSchema = SeriesResponseSchema.transform(flattenAndMergeLabels)
    43    .transform(removeWithoutRequiredLabels)
    44    .pipe(z.array(AppSchema))
    45    .transform(removeDuplicateApps);
    46  
    47  function removeWithoutRequiredLabels(
    48    s: ReturnType<typeof flattenAndMergeLabels>
    49  ) {
    50    return s.filter((a) => {
    51      return PyroscopeAppLabel in a || ServiceNameLabel in a;
    52    });
    53  }
    54  
    55  function flattenAndMergeLabels(s: SeriesResponse) {
    56    return s.labelsSet.map((v) => {
    57      return v.labels.reduce((acc, curr) => {
    58        acc[curr.name] = curr.value;
    59        return acc;
    60      }, {} as Record<string, string>);
    61    });
    62  }
    63  
    64  function removeDuplicateApps(app: App[]) {
    65    const idFn = (b: (typeof app)[number]) => `${b.__profile_type__}-${b.name}`;
    66  
    67    const visited = new Set<string>();
    68  
    69    return app.filter((b) => {
    70      // TODO: it may be possible that the same "app" belongs to different languages
    71      // with this code we only use the first one
    72      if (visited.has(idFn(b))) {
    73        return false;
    74      }
    75  
    76      visited.add(idFn(b));
    77      return true;
    78    });
    79  }
    80  
    81  export async function fetchApps(
    82    fromMs?: number,
    83    untilMs?: number
    84  ): Promise<Result<App[], RequestError | ZodError>> {
    85    let response = await request('/querier.v1.QuerierService/Series', {
    86      method: 'POST',
    87      body: JSON.stringify({
    88        matchers: [],
    89        labelNames: [
    90          PyroscopeAppLabel,
    91          ServiceNameLabel,
    92          '__profile_type__',
    93          '__type__',
    94          '__name__',
    95        ],
    96        start: fromMs || 0,
    97        end: untilMs || 0,
    98      }),
    99      headers: {
   100        'content-type': 'application/json',
   101      },
   102    });
   103  
   104    if (response.isOk) {
   105      return parseResponse(response, ListOfAppsSchema);
   106    }
   107  
   108    // try without labelNames in case of an error since this has been added in a later version
   109    response = await request('/querier.v1.QuerierService/Series', {
   110      method: 'POST',
   111      body: JSON.stringify({
   112        matchers: [],
   113        start: fromMs,
   114        end: untilMs,
   115      }),
   116      headers: {
   117        'content-type': 'application/json',
   118      },
   119    });
   120    if (response.isOk) {
   121      return parseResponse(response, ListOfAppsSchema);
   122    }
   123  
   124    return Result.err<App[], RequestError>(response.error);
   125  }
   126  
   127  export interface FetchAppsError {
   128    message?: string;
   129  }
   130  
   131  export async function fetchAppsOG(): Promise<
   132    Result<App[], RequestError | ZodError>
   133  > {
   134    const response = await request('/api/apps');
   135  
   136    if (response.isOk) {
   137      return parseResponse(response, appsModel);
   138    }
   139  
   140    return Result.err<App[], RequestError>(response.error);
   141  }
   142  
   143  export async function deleteApp(data: {
   144    name: string;
   145  }): Promise<Result<boolean, RequestError | ZodError>> {
   146    const { name } = data;
   147    const response = await request(`/api/apps`, {
   148      method: 'DELETE',
   149      body: JSON.stringify({ name }),
   150    });
   151  
   152    if (response.isOk) {
   153      return Result.ok(true);
   154    }
   155  
   156    return Result.err<boolean, RequestError>(response.error);
   157  }