plugins.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import {
  2. addErrorMessage,
  3. addLoadingMessage,
  4. addSuccessMessage,
  5. } from 'sentry/actionCreators/indicator';
  6. import type {RequestOptions} from 'sentry/api';
  7. import {Client} from 'sentry/api';
  8. import {t} from 'sentry/locale';
  9. import PluginsStore from 'sentry/stores/pluginsStore';
  10. import type {Plugin} from 'sentry/types/integrations';
  11. const activeFetch = {};
  12. // PluginsStore always exists, so api client should be independent of component lifecycle
  13. const api = new Client();
  14. type Slugs = {
  15. /**
  16. * Organization slug
  17. */
  18. orgId: string;
  19. /**
  20. * Plugin slug
  21. */
  22. pluginId: string;
  23. /**
  24. * Project slug
  25. */
  26. projectId: string;
  27. };
  28. type DoUpdateParams = Slugs & {
  29. update: Partial<Plugin>;
  30. } & Partial<RequestOptions>;
  31. function doUpdate({orgId, projectId, pluginId, update, ...params}: DoUpdateParams) {
  32. PluginsStore.onUpdate(pluginId, update);
  33. const request = api.requestPromise(
  34. `/projects/${orgId}/${projectId}/plugins/${pluginId}/`,
  35. {
  36. ...params,
  37. }
  38. );
  39. // This is intentionally not chained because we want the unhandled promise to be returned
  40. request
  41. .then(() => {
  42. PluginsStore.onUpdateSuccess(pluginId);
  43. })
  44. .catch(resp => {
  45. const err =
  46. typeof resp?.responseJSON?.detail === 'string'
  47. ? new Error(resp.responseJSON.detail)
  48. : new Error('Unable to update plugin');
  49. PluginsStore.onUpdateError(pluginId, err);
  50. });
  51. return request;
  52. }
  53. type FetchPluginsOptions = {
  54. /**
  55. * Reset will set loading state = true
  56. */
  57. resetLoading?: boolean;
  58. };
  59. /**
  60. * Fetches list of available plugins for a project
  61. */
  62. export function fetchPlugins(
  63. {orgId, projectId}: Pick<Slugs, 'orgId' | 'projectId'>,
  64. options?: FetchPluginsOptions
  65. ): Promise<Plugin[]> {
  66. const path = `/projects/${orgId}/${projectId}/plugins/`;
  67. // Make sure we throttle fetches
  68. if (activeFetch[path]) {
  69. return activeFetch[path];
  70. }
  71. PluginsStore.onFetchAll(options);
  72. const request = api.requestPromise(path, {
  73. method: 'GET',
  74. includeAllArgs: true,
  75. });
  76. activeFetch[path] = request;
  77. // This is intentionally not chained because we want the unhandled promise to be returned
  78. request
  79. .then(([data, _, resp]) => {
  80. PluginsStore.onFetchAllSuccess(data, {
  81. pageLinks: resp?.getResponseHeader('Link') ?? undefined,
  82. });
  83. return data;
  84. })
  85. .catch(err => {
  86. PluginsStore.onFetchAllError(err);
  87. throw new Error('Unable to fetch plugins');
  88. })
  89. .then(() => (activeFetch[path] = null));
  90. return request;
  91. }
  92. type EnableDisablePluginParams = Slugs;
  93. /**
  94. * Enables a plugin
  95. */
  96. export function enablePlugin(params: EnableDisablePluginParams) {
  97. addLoadingMessage(t('Enabling...'));
  98. return doUpdate({...params, update: {enabled: true}, method: 'POST'})
  99. .then(() => addSuccessMessage(t('Plugin was enabled')))
  100. .catch(() => addErrorMessage(t('Unable to enable plugin')));
  101. }
  102. /**
  103. * Disables a plugin
  104. */
  105. export function disablePlugin(params: EnableDisablePluginParams) {
  106. addLoadingMessage(t('Disabling...'));
  107. return doUpdate({...params, update: {enabled: false}, method: 'DELETE'})
  108. .then(() => addSuccessMessage(t('Plugin was disabled')))
  109. .catch(() => addErrorMessage(t('Unable to disable plugin')));
  110. }