index.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import {Fragment, useState} from 'react';
  2. import {hasEveryAccess} from 'sentry/components/acl/access';
  3. import Feature from 'sentry/components/acl/feature';
  4. import FeatureDisabled from 'sentry/components/acl/featureDisabled';
  5. import {Alert} from 'sentry/components/alert';
  6. import MiniBarChart from 'sentry/components/charts/miniBarChart';
  7. import EmptyMessage from 'sentry/components/emptyMessage';
  8. import ExternalLink from 'sentry/components/links/externalLink';
  9. import LoadingError from 'sentry/components/loadingError';
  10. import LoadingIndicator from 'sentry/components/loadingIndicator';
  11. import Panel from 'sentry/components/panels/panel';
  12. import PanelBody from 'sentry/components/panels/panelBody';
  13. import PanelHeader from 'sentry/components/panels/panelHeader';
  14. import PluginList from 'sentry/components/pluginList';
  15. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  16. import {t, tct} from 'sentry/locale';
  17. import type {TimeseriesValue} from 'sentry/types/core';
  18. import type {Series} from 'sentry/types/echarts';
  19. import type {Plugin} from 'sentry/types/integrations';
  20. import type {Project} from 'sentry/types/project';
  21. import {useApiQuery} from 'sentry/utils/queryClient';
  22. import useOrganization from 'sentry/utils/useOrganization';
  23. import {useParams} from 'sentry/utils/useParams';
  24. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  25. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  26. import PermissionAlert from 'sentry/views/settings/project/permissionAlert';
  27. function DataForwardingStats() {
  28. const {orgId, projectId} = useParams<{orgId: string; projectId: string}>();
  29. const until = Math.floor(new Date().getTime() / 1000);
  30. const since = until - 3600 * 24 * 30;
  31. const options = {
  32. query: {
  33. since,
  34. until,
  35. resolution: '1d',
  36. stat: 'forwarded',
  37. },
  38. };
  39. const {
  40. data: stats,
  41. isPending,
  42. isError,
  43. refetch,
  44. } = useApiQuery<TimeseriesValue[]>(
  45. [`/projects/${orgId}/${projectId}/stats/`, options],
  46. {staleTime: 0}
  47. );
  48. if (isPending) {
  49. return <LoadingIndicator />;
  50. }
  51. if (isError) {
  52. return <LoadingError onRetry={refetch} />;
  53. }
  54. const series: Series = {
  55. seriesName: t('Forwarded'),
  56. data: stats.map(([timestamp, value]) => ({name: timestamp * 1000, value})),
  57. };
  58. const forwardedAny = series.data.some(({value}) => value > 0);
  59. return (
  60. <Panel>
  61. <SentryDocumentTitle title={t('Data Forwarding')} projectSlug={projectId} />
  62. <PanelHeader>{t('Forwarded events in the last 30 days (by day)')}</PanelHeader>
  63. <PanelBody withPadding>
  64. {forwardedAny ? (
  65. <MiniBarChart
  66. isGroupedByDate
  67. showTimeInTooltip
  68. labelYAxisExtents
  69. series={[series]}
  70. height={150}
  71. />
  72. ) : (
  73. <EmptyMessage
  74. title={t('Nothing forwarded in the last 30 days.')}
  75. description={t('Total events forwarded to third party integrations.')}
  76. />
  77. )}
  78. </PanelBody>
  79. </Panel>
  80. );
  81. }
  82. type Props = {
  83. project: Project;
  84. };
  85. function ProjectDataForwarding({project}: Props) {
  86. const organization = useOrganization();
  87. const {projectId} = useParams<{projectId: string}>();
  88. const [pluginState, setPluginState] = useState<Plugin[]>([]);
  89. const {
  90. data: fetchedPlugins,
  91. isPending,
  92. isError,
  93. refetch,
  94. } = useApiQuery<Plugin[]>([`/projects/${organization.slug}/${projectId}/plugins/`], {
  95. staleTime: 0,
  96. });
  97. if (isPending) {
  98. return <LoadingIndicator />;
  99. }
  100. if (isError) {
  101. return <LoadingError onRetry={refetch} />;
  102. }
  103. const plugins = pluginState.length ? pluginState : fetchedPlugins;
  104. const forwardingPlugins = () => {
  105. return plugins.filter(p => p.type === 'data-forwarding' && p.hasConfiguration);
  106. };
  107. const updatePlugin = (plugin: Plugin, enabled: boolean) => {
  108. const newPlugins = plugins.map(p => ({
  109. ...p,
  110. enabled: p.id === plugin.id ? enabled : p.enabled,
  111. }));
  112. setPluginState(newPlugins);
  113. };
  114. const onEnablePlugin = (plugin: Plugin) => updatePlugin(plugin, true);
  115. const onDisablePlugin = (plugin: Plugin) => updatePlugin(plugin, false);
  116. const hasAccess = hasEveryAccess(['project:write'], {organization, project});
  117. const pluginsPanel =
  118. plugins.length > 0 ? (
  119. <PluginList
  120. organization={organization}
  121. project={project}
  122. pluginList={forwardingPlugins()}
  123. onEnablePlugin={onEnablePlugin}
  124. onDisablePlugin={onDisablePlugin}
  125. />
  126. ) : (
  127. <Panel>
  128. <EmptyMessage
  129. title={t('There are no integrations available for data forwarding')}
  130. />
  131. </Panel>
  132. );
  133. return (
  134. <div data-test-id="data-forwarding-settings">
  135. <Feature
  136. features="projects:data-forwarding"
  137. hookName="feature-disabled:data-forwarding"
  138. >
  139. {({hasFeature, features}) => (
  140. <Fragment>
  141. <SettingsPageHeader title={t('Data Forwarding')} />
  142. <TextBlock>
  143. {tct(
  144. `Data Forwarding allows processed events to be sent to your
  145. favorite business intelligence tools. The exact payload and
  146. types of data depend on the integration you're using. Learn
  147. more about this functionality in our [link:documentation].`,
  148. {
  149. link: (
  150. <ExternalLink href="https://docs.sentry.io/product/data-management-settings/data-forwarding/" />
  151. ),
  152. }
  153. )}
  154. </TextBlock>
  155. <PermissionAlert project={project} />
  156. <Alert showIcon>
  157. {tct(
  158. `Sentry forwards [em:all applicable error events] to the provider, in
  159. some cases this may be a significant volume of data.`,
  160. {
  161. em: <strong />,
  162. }
  163. )}
  164. </Alert>
  165. {!hasFeature && (
  166. <FeatureDisabled
  167. alert
  168. featureName={t('Data Forwarding')}
  169. features={features}
  170. />
  171. )}
  172. <DataForwardingStats />
  173. {hasAccess && hasFeature && pluginsPanel}
  174. </Fragment>
  175. )}
  176. </Feature>
  177. </div>
  178. );
  179. }
  180. export default ProjectDataForwarding;