Browse Source

feat(integrations): Migrate Jira plugins to Jira integration (#37694)

Objective:
Adds a button to the Jira Cloud and Jira Server configuration pages that when clicked, migrates the plugin linked issues and ignored fields and disables the plugin(s). This should only appear if there are plugins to migrate.
NisanthanNanthakumar 2 years ago
parent
commit
a80152e0a6

+ 80 - 3
static/app/views/settings/organizationIntegrations/configureIntegration.tsx

@@ -2,8 +2,12 @@ import {Fragment} from 'react';
 import {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
 
+import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
+import {Client} from 'sentry/api';
+import Access from 'sentry/components/acl/access';
 import Alert from 'sentry/components/alert';
 import Button from 'sentry/components/button';
+import Confirm from 'sentry/components/confirm';
 import Form from 'sentry/components/forms/form';
 import JsonForm from 'sentry/components/forms/jsonForm';
 import List from 'sentry/components/list';
@@ -11,9 +15,15 @@ import ListItem from 'sentry/components/list/listItem';
 import NavTabs from 'sentry/components/navTabs';
 import {IconAdd, IconArrow} from 'sentry/icons';
 import {t} from 'sentry/locale';
-import {IntegrationProvider, IntegrationWithConfig, Organization} from 'sentry/types';
+import {
+  IntegrationProvider,
+  IntegrationWithConfig,
+  Organization,
+  PluginWithProjectList,
+} from 'sentry/types';
 import {trackIntegrationAnalytics} from 'sentry/utils/integrationUtil';
 import {singleLineRenderer} from 'sentry/utils/marked';
+import withApi from 'sentry/utils/withApi';
 import withOrganization from 'sentry/utils/withOrganization';
 import AsyncView from 'sentry/views/asyncView';
 import AddIntegration from 'sentry/views/organizationIntegrations/addIntegration';
@@ -34,6 +44,7 @@ type RouteParams = {
   providerKey: string;
 };
 type Props = RouteComponentProps<RouteParams, {}> & {
+  api: Client;
   organization: Organization;
 };
 
@@ -42,8 +53,10 @@ type Tab = 'repos' | 'codeMappings' | 'userMappings' | 'teamMappings' | 'setting
 type State = AsyncView['state'] & {
   config: {providers: IntegrationProvider[]};
   integration: IntegrationWithConfig;
+  plugins: PluginWithProjectList[] | null;
   tab?: Tab;
 };
+
 class ConfigureIntegration extends AsyncView<Props, State> {
   getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
     const {orgId, integrationId} = this.props.params;
@@ -51,6 +64,7 @@ class ConfigureIntegration extends AsyncView<Props, State> {
     return [
       ['config', `/organizations/${orgId}/config/integrations/`],
       ['integration', `/organizations/${orgId}/integrations/${integrationId}/`],
+      ['plugins', `/organizations/${orgId}/plugins/configs/`],
     ];
   }
 
@@ -126,8 +140,36 @@ class ConfigureIntegration extends AsyncView<Props, State> {
     this.setState(this.getDefaultState(), this.fetchData);
   };
 
+  handleJiraMigration = async () => {
+    try {
+      const {
+        params: {orgId, integrationId},
+      } = this.props;
+
+      await this.api.requestPromise(
+        `/organizations/${orgId}/integrations/${integrationId}/issues/`,
+        {
+          method: 'PUT',
+          data: {},
+        }
+      );
+      this.setState(
+        {
+          plugins: (this.state.plugins || []).filter(({id}) => id === 'jira'),
+        },
+        () => addSuccessMessage(t('Migration in progress.'))
+      );
+    } catch (error) {
+      addErrorMessage(t('Something went wrong! Please try again.'));
+    }
+  };
   getAction = (provider: IntegrationProvider | undefined) => {
-    const {integration} = this.state;
+    const {integration, plugins} = this.state;
+    const shouldMigrateJiraPlugin =
+      provider &&
+      ['jira', 'jira_server'].includes(provider.key) &&
+      (plugins || []).find(({id}) => id === 'jira');
+
     const action =
       provider && provider.key === 'pagerduty' ? (
         <AddIntegration
@@ -147,6 +189,41 @@ class ConfigureIntegration extends AsyncView<Props, State> {
             </Button>
           )}
         </AddIntegration>
+      ) : shouldMigrateJiraPlugin ? (
+        <Access access={['org:integrations']}>
+          {({hasAccess}) => (
+            <Confirm
+              disabled={!hasAccess}
+              header="Migrate Linked Issues from Jira Plugins"
+              renderMessage={() => (
+                <Fragment>
+                  <p>
+                    {t(
+                      'This will automatically associate all the Linked Issues of your Jira Plugins to this integration.'
+                    )}
+                  </p>
+                  <p>
+                    {t(
+                      'If the Jira Plugins had the option checked to automatically create a Jira ticket for every new Sentry issue checked, you will need to create alert rules to recreate this behavior. Jira Server does not have this feature.'
+                    )}
+                  </p>
+                  <p>
+                    {t(
+                      'Once the migration is complete, your Jira Plugins will be disabled.'
+                    )}
+                  </p>
+                </Fragment>
+              )}
+              onConfirm={() => {
+                this.handleJiraMigration();
+              }}
+            >
+              <Button priority="primary" size="md" disabled={!hasAccess}>
+                {t('Migrate Plugin')}
+              </Button>
+            </Confirm>
+          )}
+        </Access>
       ) : null;
 
     return action;
@@ -302,7 +379,7 @@ class ConfigureIntegration extends AsyncView<Props, State> {
   }
 }
 
-export default withOrganization(ConfigureIntegration);
+export default withOrganization(withApi(ConfigureIntegration));
 
 const CapitalizedLink = styled('a')`
   text-transform: capitalize;