index.tsx 6.6 KB

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