withPlugins.tsx 2.8 KB

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