pluginDetailedView.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as modal from 'sentry/actionCreators/modal';
  4. import AsyncComponent from 'sentry/components/asyncComponent';
  5. import Button from 'sentry/components/button';
  6. import ContextPickerModal from 'sentry/components/contextPickerModal';
  7. import {t} from 'sentry/locale';
  8. import space from 'sentry/styles/space';
  9. import {PluginProjectItem, PluginWithProjectList} from 'sentry/types';
  10. import withOrganization from 'sentry/utils/withOrganization';
  11. import AbstractIntegrationDetailedView from './abstractIntegrationDetailedView';
  12. import InstalledPlugin from './installedPlugin';
  13. import PluginDeprecationAlert from './pluginDeprecationAlert';
  14. type State = {
  15. plugins: PluginWithProjectList[];
  16. };
  17. type Tab = AbstractIntegrationDetailedView['state']['tab'];
  18. class PluginDetailedView extends AbstractIntegrationDetailedView<
  19. AbstractIntegrationDetailedView['props'],
  20. State & AbstractIntegrationDetailedView['state']
  21. > {
  22. getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> {
  23. const {orgId, integrationSlug} = this.props.params;
  24. return [
  25. ['plugins', `/organizations/${orgId}/plugins/configs/?plugins=${integrationSlug}`],
  26. ];
  27. }
  28. get integrationType() {
  29. return 'plugin' as const;
  30. }
  31. get plugin() {
  32. return this.state.plugins[0];
  33. }
  34. get description() {
  35. return this.plugin.description || '';
  36. }
  37. get author() {
  38. return this.plugin.author?.name;
  39. }
  40. get resourceLinks() {
  41. return this.plugin.resourceLinks || [];
  42. }
  43. get installationStatus() {
  44. return this.plugin.projectList.length > 0 ? 'Installed' : 'Not Installed';
  45. }
  46. get integrationName() {
  47. return `${this.plugin.name}${this.plugin.isHidden ? ' (Legacy)' : ''}`;
  48. }
  49. get featureData() {
  50. return this.plugin.featureDescriptions;
  51. }
  52. handleResetConfiguration = (projectId: string) => {
  53. // make a copy of our project list
  54. const projectList = this.plugin.projectList.slice();
  55. // find the index of the project
  56. const index = projectList.findIndex(item => item.projectId === projectId);
  57. // should match but quit if it doesn't
  58. if (index < 0) {
  59. return;
  60. }
  61. // remove from array
  62. projectList.splice(index, 1);
  63. // update state
  64. this.setState({
  65. plugins: [{...this.state.plugins[0], projectList}],
  66. });
  67. };
  68. handlePluginEnableStatus = (projectId: string, enable: boolean = true) => {
  69. // make a copy of our project list
  70. const projectList = this.plugin.projectList.slice();
  71. // find the index of the project
  72. const index = projectList.findIndex(item => item.projectId === projectId);
  73. // should match but quit if it doesn't
  74. if (index < 0) {
  75. return;
  76. }
  77. // update item in array
  78. projectList[index] = {
  79. ...projectList[index],
  80. enabled: enable,
  81. };
  82. // update state
  83. this.setState({
  84. plugins: [{...this.state.plugins[0], projectList}],
  85. });
  86. };
  87. handleAddToProject = () => {
  88. const plugin = this.plugin;
  89. const {organization, router} = this.props;
  90. this.trackIntegrationAnalytics('integrations.plugin_add_to_project_clicked');
  91. modal.openModal(
  92. modalProps => (
  93. <ContextPickerModal
  94. {...modalProps}
  95. nextPath={`/settings/${organization.slug}/projects/:projectId/plugins/${plugin.id}/`}
  96. needProject
  97. needOrg={false}
  98. onFinish={path => {
  99. modalProps.closeModal();
  100. router.push(path);
  101. }}
  102. />
  103. ),
  104. {allowClickClose: false}
  105. );
  106. };
  107. getTabDisplay(tab: Tab) {
  108. // we want to show project configurations to make it more clear
  109. if (tab === 'configurations') {
  110. return 'project configurations';
  111. }
  112. return 'overview';
  113. }
  114. renderTopButton(disabledFromFeatures: boolean, userHasAccess: boolean) {
  115. if (userHasAccess) {
  116. return (
  117. <AddButton
  118. data-test-id="install-button"
  119. disabled={disabledFromFeatures}
  120. onClick={this.handleAddToProject}
  121. size="sm"
  122. priority="primary"
  123. >
  124. {t('Add to Project')}
  125. </AddButton>
  126. );
  127. }
  128. return this.renderRequestIntegrationButton();
  129. }
  130. renderConfigurations() {
  131. const plugin = this.plugin;
  132. const {organization} = this.props;
  133. if (plugin.projectList.length) {
  134. return (
  135. <Fragment>
  136. <PluginDeprecationAlert organization={organization} plugin={plugin} />
  137. <div>
  138. {plugin.projectList.map((projectItem: PluginProjectItem) => (
  139. <InstalledPlugin
  140. key={projectItem.projectId}
  141. organization={organization}
  142. plugin={plugin}
  143. projectItem={projectItem}
  144. onResetConfiguration={this.handleResetConfiguration}
  145. onPluginEnableStatusChange={this.handlePluginEnableStatus}
  146. trackIntegrationAnalytics={this.trackIntegrationAnalytics}
  147. />
  148. ))}
  149. </div>
  150. </Fragment>
  151. );
  152. }
  153. return this.renderEmptyConfigurations();
  154. }
  155. }
  156. const AddButton = styled(Button)`
  157. margin-bottom: ${space(1)};
  158. `;
  159. export default withOrganization(PluginDetailedView);