prompts.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import {useCallback} from 'react';
  2. import type {Client} from 'sentry/api';
  3. import type {Organization, OrganizationSummary} from 'sentry/types';
  4. import {promptIsDismissed} from 'sentry/utils/promptIsDismissed';
  5. import type {ApiQueryKey, UseApiQueryOptions} from 'sentry/utils/queryClient';
  6. import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient';
  7. import useApi from 'sentry/utils/useApi';
  8. type PromptsUpdateParams = {
  9. /**
  10. * The prompt feature name
  11. */
  12. feature: string;
  13. organization: OrganizationSummary;
  14. status: 'snoozed' | 'dismissed';
  15. /**
  16. * The numeric project ID as a string
  17. */
  18. projectId?: string;
  19. };
  20. /**
  21. * Update the status of a prompt
  22. */
  23. export function promptsUpdate(api: Client, params: PromptsUpdateParams) {
  24. const url = `/organizations/${params.organization.slug}/prompts-activity/`;
  25. return api.requestPromise(url, {
  26. method: 'PUT',
  27. data: {
  28. organization_id: params.organization.id,
  29. project_id: params.projectId,
  30. feature: params.feature,
  31. status: params.status,
  32. },
  33. });
  34. }
  35. type PromptCheckParams = {
  36. /**
  37. * The prompt feature name
  38. */
  39. feature: string | string[];
  40. organization: OrganizationSummary;
  41. /**
  42. * The numeric project ID as a string
  43. */
  44. projectId?: string;
  45. };
  46. export type PromptResponseItem = {
  47. dismissed_ts?: number;
  48. snoozed_ts?: number;
  49. };
  50. export type PromptResponse = {
  51. data?: PromptResponseItem;
  52. features?: {[key: string]: PromptResponseItem};
  53. };
  54. export type PromptData = null | {
  55. dismissedTime?: number;
  56. snoozedTime?: number;
  57. };
  58. /**
  59. * Get the status of a prompt
  60. */
  61. export async function promptsCheck(
  62. api: Client,
  63. params: PromptCheckParams
  64. ): Promise<PromptData> {
  65. const query = {
  66. feature: params.feature,
  67. organization_id: params.organization.id,
  68. ...(params.projectId === undefined ? {} : {project_id: params.projectId}),
  69. };
  70. const url = `/organizations/${params.organization.slug}/prompts-activity/`;
  71. const response: PromptResponse = await api.requestPromise(url, {
  72. query,
  73. });
  74. if (response?.data) {
  75. return {
  76. dismissedTime: response.data.dismissed_ts,
  77. snoozedTime: response.data.snoozed_ts,
  78. };
  79. }
  80. return null;
  81. }
  82. export const makePromptsCheckQueryKey = ({
  83. feature,
  84. organization,
  85. projectId,
  86. }: PromptCheckParams): ApiQueryKey => {
  87. const url = `/organizations/${organization.slug}/prompts-activity/`;
  88. return [
  89. url,
  90. {query: {feature, organization_id: organization.id, project_id: projectId}},
  91. ];
  92. };
  93. /**
  94. * @param organizationId org numerical id, not the slug
  95. */
  96. export function usePromptsCheck(
  97. {feature, organization, projectId}: PromptCheckParams,
  98. options: Partial<UseApiQueryOptions<PromptResponse>> = {}
  99. ) {
  100. return useApiQuery<PromptResponse>(
  101. makePromptsCheckQueryKey({feature, organization, projectId}),
  102. {
  103. staleTime: 120000,
  104. retry: false,
  105. ...options,
  106. }
  107. );
  108. }
  109. export function usePrompt({
  110. feature,
  111. organization,
  112. projectId,
  113. }: {
  114. feature: string;
  115. organization: Organization;
  116. projectId?: string;
  117. }) {
  118. const api = useApi({persistInFlight: true});
  119. const prompt = usePromptsCheck({feature, organization, projectId});
  120. const queryClient = useQueryClient();
  121. const isPromptDismissed =
  122. prompt.isSuccess && prompt.data.data
  123. ? promptIsDismissed({
  124. dismissedTime: prompt.data.data.dismissed_ts,
  125. snoozedTime: prompt.data.data.snoozed_ts,
  126. })
  127. : undefined;
  128. const dismissPrompt = useCallback(() => {
  129. promptsUpdate(api, {
  130. organization,
  131. projectId,
  132. feature,
  133. status: 'dismissed',
  134. });
  135. // Update cached query data
  136. // Will set prompt to dismissed
  137. setApiQueryData<PromptResponse>(
  138. queryClient,
  139. makePromptsCheckQueryKey({
  140. organization,
  141. feature,
  142. projectId,
  143. }),
  144. () => {
  145. const dimissedTs = new Date().getTime() / 1000;
  146. return {
  147. data: {dismissed_ts: dimissedTs},
  148. features: {[feature]: {dismissed_ts: dimissedTs}},
  149. };
  150. }
  151. );
  152. }, [api, feature, organization, projectId, queryClient]);
  153. return {
  154. isLoading: prompt.isLoading,
  155. isError: prompt.isError,
  156. isPromptDismissed,
  157. dismissPrompt,
  158. };
  159. }
  160. /**
  161. * Get the status of many prompt
  162. */
  163. export async function batchedPromptsCheck<T extends readonly string[]>(
  164. api: Client,
  165. features: T,
  166. params: {
  167. organization: OrganizationSummary;
  168. projectId?: string;
  169. }
  170. ): Promise<{[key in T[number]]: PromptData}> {
  171. const query = {
  172. feature: features,
  173. organization_id: params.organization.id,
  174. ...(params.projectId === undefined ? {} : {project_id: params.projectId}),
  175. };
  176. const url = `/organizations/${params.organization.slug}/prompts-activity/`;
  177. const response: PromptResponse = await api.requestPromise(url, {
  178. query,
  179. });
  180. const responseFeatures = response?.features;
  181. const result: {[key in T[number]]?: PromptData} = {};
  182. if (!responseFeatures) {
  183. return result as {[key in T[number]]: PromptData};
  184. }
  185. for (const featureName of features) {
  186. const item = responseFeatures[featureName];
  187. if (item) {
  188. result[featureName] = {
  189. dismissedTime: item.dismissed_ts,
  190. snoozedTime: item.snoozed_ts,
  191. };
  192. } else {
  193. result[featureName] = null;
  194. }
  195. }
  196. return result as {[key in T[number]]: PromptData};
  197. }