Mirror of https://github.com/roostorg/osprey
github.com/roostorg/osprey
1import { saveAs } from 'file-saver';
2import { matchPath } from 'react-router';
3
4import { history } from '../stores/QueryStore';
5import { LabelMutation, LabelStatusAPIMapping } from '../types/LabelTypes';
6import {
7 TopNComparisonResult,
8 ScanResult,
9 ScanQueryRequest,
10 TimeseriesResult,
11 StoredExecutionResult,
12 QueryRequest,
13 ScanQueryOrder,
14 BaseQueryRequest,
15 FeatureLocations,
16} from '../types/QueryTypes';
17import HTTPUtils, { HTTPResponse } from '../utils/HTTPUtils';
18
19import { BULK_LABEL_DEFAULT_LIMIT, Routes } from '../Constants';
20
21export function getBaseRequest({
22 start,
23 end,
24 queryFilter,
25 entityFeatureFilters = new Set(),
26}: BaseQueryRequest): QueryRequest {
27 const entityViewMatch = matchPath<{ entityId: string; entityType: string }>(history.location.pathname, {
28 path: Routes.ENTITY,
29 });
30
31 /* eslint-disable */
32 const queryRequest: QueryRequest = {
33 start,
34 end,
35 query_filter: queryFilter,
36 entity: null,
37 };
38
39 if (entityViewMatch != null) {
40 queryRequest.entity = {
41 id: decodeURIComponent(entityViewMatch.params.entityId),
42 type: decodeURIComponent(entityViewMatch.params.entityType),
43 feature_filters: entityFeatureFilters.size > 0 ? [...entityFeatureFilters] : null,
44 };
45 }
46 /* eslint-enable */
47
48 return queryRequest;
49}
50
51export async function getTopNQueryResults(
52 query: BaseQueryRequest,
53 dimension: string | null,
54 limit: number = 100,
55 precision: number = 0
56): Promise<TopNComparisonResult> {
57 if (dimension === '' || dimension == null) {
58 return { comparison: [], current_period: [], previous_period: [] };
59 }
60
61 const response = await HTTPUtils.post('events/topn', {
62 ...getBaseRequest(query),
63 dimension,
64 limit,
65 precision,
66 });
67
68 if (response.data.length > 0) {
69 return response.data[0].result;
70 }
71
72 return response.data;
73}
74
75export async function getGroupByApproximateCountResults(
76 query: BaseQueryRequest,
77 dimension: string | null
78): Promise<number | null> {
79 if (dimension === '' || dimension == null) {
80 return null;
81 }
82
83 const response = await HTTPUtils.post('events/groupby/approximate-count', {
84 ...getBaseRequest(query),
85 dimension,
86 });
87 return response.data?.count ?? null;
88}
89
90export async function getScanQueryResults(
91 query: BaseQueryRequest,
92 sortOrder: ScanQueryOrder,
93 limit: number,
94 offset?: string | null
95): Promise<ScanResult> {
96 const requestData: ScanQueryRequest = {
97 ...getBaseRequest(query),
98 order: sortOrder,
99 limit,
100 };
101
102 if (offset != null) {
103 requestData.next_page = offset; /* eslint-disable-line */
104 }
105
106 const response: HTTPResponse = await HTTPUtils.post('events/scan', requestData);
107
108 if (!response.ok) return { events: [], offset: null, queryStart: null };
109 return {
110 events: response.data.events,
111 offset: response.data.next_page,
112 queryStart: response.data.next_page != null ? query.start : null,
113 };
114}
115
116export async function getFeatureLocations(): Promise<FeatureLocations> {
117 const response: HTTPResponse = await HTTPUtils.get('docs/feature-locations', {
118 validateStatus: (status) => status === 200 || status === 401,
119 });
120
121 if (!response.ok || !response.data?.locations) return { locations: [] };
122 return response.data;
123}
124
125export async function getTopNQueryResultCSV(query: BaseQueryRequest, dimension: string, limit: number): Promise<void> {
126 const response: HTTPResponse = await HTTPUtils.post('events/topn/csv', {
127 ...getBaseRequest(query),
128 dimension,
129 limit,
130 });
131
132 if (response.ok) {
133 const fileBlob = new Blob([response.data], { type: 'text/csv' });
134 saveAs(fileBlob, `osprey-${query.start}-${query.end}.csv`);
135 } else {
136 throw new Error(response.error.message);
137 }
138}
139
140export async function getTimeseriesQueryResults(
141 query: BaseQueryRequest,
142 granularity: string
143): Promise<TimeseriesResult[]> {
144 const response: HTTPResponse = await HTTPUtils.post('events/timeseries', {
145 ...getBaseRequest(query),
146 granularity,
147 });
148
149 if (!response.ok) return [];
150 return response.data;
151}
152
153export async function getDetailedEventFeatures(eventId: string): Promise<StoredExecutionResult> {
154 const response: HTTPResponse = await HTTPUtils.get(`events/event/${eventId}`);
155
156 if (!response.ok) throw new Error('Failed to load detailed event features: ' + response.error.message);
157 return response.data;
158}
159
160export async function postTopBulkLabelTask(
161 query: BaseQueryRequest,
162 dimension: string,
163 excludedEntities: Set<string>,
164 entitiesToLabelCount: number,
165 noLimit: boolean,
166 labelMutation: LabelMutation
167): Promise<number | null> {
168 // The entity count is separate from the limiter; This check makes
169 // sure that a limited job will expect to label the correct amount of entities.
170 // If the expected count is more than 10% different from the actual count,
171 // the job will fail downstream.
172 if (!noLimit && entitiesToLabelCount > BULK_LABEL_DEFAULT_LIMIT) {
173 entitiesToLabelCount = BULK_LABEL_DEFAULT_LIMIT;
174 }
175 /* eslint-disable */
176 const response = await HTTPUtils.post('events/topn/bulk_label', {
177 ...getBaseRequest(query),
178 dimension,
179 excluded_entities: [...excludedEntities],
180 expected_entities: entitiesToLabelCount,
181 no_limit: noLimit,
182 label_name: labelMutation.label_name,
183 label_status: LabelStatusAPIMapping[labelMutation.status],
184 label_reason: labelMutation.reason,
185 label_expiry: labelMutation.expires_at,
186 });
187 /* eslint-enable */
188 return response.data?.task_id ?? null;
189}