import * as Sentry from '@sentry/react'; import type {RequestCallbacks, RequestOptions} from 'sentry/api'; import {Client} from 'sentry/api'; import GroupStore from 'sentry/stores/groupStore'; import type {Actor} from 'sentry/types/core'; import type {Group, Tag as GroupTag, TagValue} from 'sentry/types/group'; import {buildTeamId, buildUserId} from 'sentry/utils'; import {uniqueId} from 'sentry/utils/guid'; import type {ApiQueryKey, UseApiQueryOptions} from 'sentry/utils/queryClient'; import {useApiQuery} from 'sentry/utils/queryClient'; type AssignedBy = 'suggested_assignee' | 'assignee_selector'; export function clearAssignment( groupId: string, orgSlug: string, assignedBy: AssignedBy ): Promise { const api = new Client(); const endpoint = `/organizations/${orgSlug}/issues/${groupId}/`; const id = uniqueId(); GroupStore.onAssignTo(id, groupId, { email: '', }); const request = api.requestPromise(endpoint, { method: 'PUT', // Sending an empty value to assignedTo is the same as "clear" data: { assignedTo: '', assignedBy, }, }); request .then(data => { GroupStore.onAssignToSuccess(id, groupId, data); return data; }) .catch(data => { GroupStore.onAssignToError(id, groupId, data); throw data; }); return request; } type AssignToActorParams = { actor: Pick; assignedBy: AssignedBy; /** * Issue id */ id: string; orgSlug: string; }; export function assignToActor({ id, actor, assignedBy, orgSlug, }: AssignToActorParams): Promise { const api = new Client(); const endpoint = `/organizations/${orgSlug}/issues/${id}/`; const guid = uniqueId(); let actorId = ''; GroupStore.onAssignTo(guid, id, {email: ''}); switch (actor.type) { case 'user': actorId = buildUserId(actor.id); break; case 'team': actorId = buildTeamId(actor.id); break; default: Sentry.withScope(scope => { scope.setExtra('actor', actor); Sentry.captureException('Unknown assignee type'); }); } return api .requestPromise(endpoint, { method: 'PUT', data: {assignedTo: actorId, assignedBy}, }) .then(data => { GroupStore.onAssignToSuccess(guid, id, data); return data; }) .catch(data => { GroupStore.onAssignToSuccess(guid, id, data); throw data; }); } type ParamsType = { environment?: string | string[] | null; itemIds?: string[]; project?: number[] | string[] | null; query?: string; }; type UpdateParams = ParamsType & { orgId: string; projectId?: string; }; type QueryArgs = | { query: string; environment?: string | string[]; project?: Array; } | { id: number[] | string[]; environment?: string | string[]; project?: Array; } | { environment?: string | string[]; project?: Array; }; /** * Converts input parameters to API-compatible query arguments */ export function paramsToQueryArgs(params: ParamsType): QueryArgs { const p: QueryArgs = params.itemIds ? {id: params.itemIds} // items matching array of itemids : params.query ? {query: params.query} // items matching search query : {}; // all items // only include environment if it is not null/undefined if (params.query && params.environment !== null && params.environment !== undefined) { p.environment = params.environment; } // only include projects if it is not null/undefined/an empty array if (params.project?.length) { p.project = params.project; } // only include date filters if they are not null/undefined if (params.query) { ['start', 'end', 'period', 'utc'].forEach(prop => { if ( params[prop as keyof typeof params] !== null && params[prop as keyof typeof params] !== undefined ) { (p as any)[prop === 'period' ? 'statsPeriod' : prop] = params[prop as keyof typeof params]; } }); } return p; } function getUpdateUrl({projectId, orgId}: UpdateParams) { return projectId ? `/projects/${orgId}/${projectId}/issues/` : `/organizations/${orgId}/issues/`; } function chainUtil( ...funcs: Array<((...args: Args) => any) | undefined> ) { const filteredFuncs = funcs.filter( (f): f is (...args: Args) => any => typeof f === 'function' ); return (...args: Args): void => { filteredFuncs.forEach(func => { func.apply(funcs, args); }); }; } function wrapRequest( api: Client, path: string, options: RequestOptions, extraParams: RequestCallbacks = {} ) { options.success = chainUtil(options.success, extraParams.success); options.error = chainUtil(options.error, extraParams.error); options.complete = chainUtil(options.complete, extraParams.complete); return api.request(path, options); } type BulkDeleteParams = UpdateParams; export function bulkDelete( api: Client, params: BulkDeleteParams, options: RequestCallbacks ) { const {itemIds} = params; const path = getUpdateUrl(params); const query: QueryArgs = paramsToQueryArgs(params); const id = uniqueId(); GroupStore.onDelete(id, itemIds); return wrapRequest( api, path, { query, method: 'DELETE', success: response => { GroupStore.onDeleteSuccess(id, itemIds, response); }, error: error => { GroupStore.onDeleteError(id, itemIds, error); }, }, options ); } type BulkUpdateParams = UpdateParams & { data?: any; failSilently?: boolean; }; export function bulkUpdate( api: Client, params: BulkUpdateParams, options: RequestCallbacks ) { const {itemIds, failSilently, data} = params; const path = getUpdateUrl(params); const query: QueryArgs = paramsToQueryArgs(params); const id = uniqueId(); GroupStore.onUpdate(id, itemIds, data); return wrapRequest( api, path, { query, method: 'PUT', data, success: response => { GroupStore.onUpdateSuccess(id, itemIds, response); }, error: () => { GroupStore.onUpdateError(id, itemIds, !!failSilently); }, }, options ); } type MergeGroupsParams = UpdateParams; export function mergeGroups( api: Client, params: MergeGroupsParams, options: RequestCallbacks ) { const {itemIds} = params; const path = getUpdateUrl(params); const query: QueryArgs = paramsToQueryArgs(params); const id = uniqueId(); GroupStore.onMerge(id, itemIds); return wrapRequest( api, path, { query, method: 'PUT', data: {merge: 1}, success: response => { GroupStore.onMergeSuccess(id, itemIds, response); }, error: error => { GroupStore.onMergeError(id, itemIds, error); }, }, options ); } type FetchIssueTagValuesParameters = { groupId: string; orgSlug: string; tagKey: string; cursor?: string; environment?: string[]; sort?: string | string[]; }; export const makeFetchIssueTagValuesQueryKey = ({ orgSlug, groupId, tagKey, environment, sort, cursor, }: FetchIssueTagValuesParameters): ApiQueryKey => [ `/organizations/${orgSlug}/issues/${groupId}/tags/${tagKey}/values/`, {query: {environment, sort, cursor}}, ]; export function useFetchIssueTagValues( parameters: FetchIssueTagValuesParameters, options: Partial> = {} ) { return useApiQuery(makeFetchIssueTagValuesQueryKey(parameters), { staleTime: 0, retry: false, ...options, }); } type FetchIssueTagParameters = { groupId: string; orgSlug: string; tagKey: string; }; export const makeFetchIssueTagQueryKey = ({ orgSlug, groupId, tagKey, environment, sort, }: FetchIssueTagValuesParameters): ApiQueryKey => [ `/organizations/${orgSlug}/issues/${groupId}/tags/${tagKey}/`, {query: {environment, sort}}, ]; export function useFetchIssueTag( parameters: FetchIssueTagParameters, options: Partial> = {} ) { return useApiQuery(makeFetchIssueTagQueryKey(parameters), { staleTime: 0, retry: false, ...options, }); }