withPlugins.tsx 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import {Component} from 'react';
  2. import {fetchPlugins} from 'sentry/actionCreators/plugins';
  3. import PluginsStore from 'sentry/stores/pluginsStore';
  4. import type {Plugin} from 'sentry/types/integrations';
  5. import type {Organization} from 'sentry/types/organization';
  6. import type {Project} from 'sentry/types/project';
  7. import {defined} from 'sentry/utils';
  8. import getDisplayName from 'sentry/utils/getDisplayName';
  9. import withOrganization from 'sentry/utils/withOrganization';
  10. import withProject from 'sentry/utils/withProject';
  11. type WithPluginProps = {
  12. organization: Organization;
  13. project?: Project;
  14. };
  15. type State = {
  16. loading: boolean;
  17. plugins: Plugin[];
  18. };
  19. /**
  20. * Higher order component that fetches list of plugins and
  21. * passes PluginsStore to component as `plugins`
  22. */
  23. function withPlugins<P extends WithPluginProps>(
  24. WrappedComponent: React.ComponentType<P>
  25. ) {
  26. class WithPlugins extends Component<Omit<P, 'plugins'> & WithPluginProps, State> {
  27. static displayName = `withPlugins(${getDisplayName(WrappedComponent)})`;
  28. state = {plugins: [], loading: true};
  29. componentDidMount() {
  30. this.fetchPlugins();
  31. }
  32. componentDidUpdate(prevProps, _prevState, prevContext) {
  33. const {organization, project} = this.props;
  34. // Only fetch plugins when a org slug or project slug has changed
  35. const prevOrg = prevProps.organization || prevContext?.organization;
  36. const prevProject = prevProps.project || prevContext?.project;
  37. // If previous org/project is undefined then it means:
  38. // the HoC has mounted, `fetchPlugins` has been called (via cDM), and
  39. // store was updated. We don't need to fetchPlugins again (or it will cause an infinite loop)
  40. //
  41. // This is for the unusual case where component is mounted and receives a new org/project prop
  42. // e.g. when switching projects via breadcrumbs in settings.
  43. if (!defined(prevProject) || !defined(prevOrg)) {
  44. return;
  45. }
  46. const isOrgSame = prevOrg.slug === organization.slug;
  47. const isProjectSame = prevProject.slug === project?.slug;
  48. // Don't do anything if org and project are the same
  49. if (isOrgSame && isProjectSame) {
  50. return;
  51. }
  52. this.fetchPlugins();
  53. }
  54. componentWillUnmount() {
  55. this.unsubscribe();
  56. }
  57. unsubscribe = PluginsStore.listen(({plugins, loading}: State) => {
  58. // State is destructured as store updates contain additional keys
  59. // that are not exposed by this HoC
  60. this.setState({plugins, loading});
  61. }, undefined);
  62. fetchPlugins() {
  63. const {organization, project} = this.props;
  64. if (!project || !organization) {
  65. return;
  66. }
  67. fetchPlugins({projectId: project.slug, orgId: organization.slug});
  68. }
  69. render() {
  70. return (
  71. <WrappedComponent {...(this.props as P & WithPluginProps)} plugins={this.state} />
  72. );
  73. }
  74. }
  75. return withOrganization(withProject(WithPlugins));
  76. }
  77. export default withPlugins;