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 }