123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- import {Fragment} from 'react';
- import {addErrorMessage} from 'sentry/actionCreators/indicator';
- import {RequestOptions} from 'sentry/api';
- import {Alert} from 'sentry/components/alert';
- import AsyncComponent from 'sentry/components/asyncComponent';
- import {Button} from 'sentry/components/button';
- import HookOrDefault from 'sentry/components/hookOrDefault';
- import {Panel, PanelItem} from 'sentry/components/panels';
- import {IconOpen} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import {Integration, IntegrationProvider, ObjectStatus} from 'sentry/types';
- import {getAlertText} from 'sentry/utils/integrationUtil';
- import {normalizeUrl} from 'sentry/utils/withDomainRequired';
- import withOrganization from 'sentry/utils/withOrganization';
- import AbstractIntegrationDetailedView from './abstractIntegrationDetailedView';
- import {AddIntegrationButton} from './addIntegrationButton';
- import InstalledIntegration from './installedIntegration';
- const FirstPartyIntegrationAlert = HookOrDefault({
- hookName: 'component:first-party-integration-alert',
- defaultComponent: () => null,
- });
- const FirstPartyIntegrationAdditionalCTA = HookOrDefault({
- hookName: 'component:first-party-integration-additional-cta',
- defaultComponent: () => null,
- });
- type State = {
- configurations: Integration[];
- information: {providers: IntegrationProvider[]};
- };
- class IntegrationDetailedView extends AbstractIntegrationDetailedView<
- AbstractIntegrationDetailedView['props'],
- State & AbstractIntegrationDetailedView['state']
- > {
- getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> {
- const {organization} = this.props;
- const {integrationSlug} = this.props.params;
- return [
- [
- 'information',
- `/organizations/${organization.slug}/config/integrations/?provider_key=${integrationSlug}`,
- ],
- [
- 'configurations',
- `/organizations/${organization.slug}/integrations/?provider_key=${integrationSlug}&includeConfig=0`,
- ],
- ];
- }
- get integrationType() {
- return 'first_party' as const;
- }
- get provider() {
- return this.state.information.providers[0];
- }
- get description() {
- return this.metadata.description;
- }
- get author() {
- return this.metadata.author;
- }
- get alerts() {
- const provider = this.provider;
- const metadata = this.metadata;
- // The server response for integration installations includes old icon CSS classes
- // We map those to the currently in use values to their react equivalents
- // and fallback to IconFlag just in case.
- const alerts = (metadata.aspects.alerts || []).map(item => ({
- ...item,
- showIcon: true,
- }));
- if (!provider.canAdd && metadata.aspects.externalInstall) {
- alerts.push({
- type: 'warning',
- showIcon: true,
- text: metadata.aspects.externalInstall.noticeText,
- });
- }
- return alerts;
- }
- get resourceLinks() {
- const metadata = this.metadata;
- return [
- {url: metadata.source_url, title: 'View Source'},
- {url: metadata.issue_url, title: 'Report Issue'},
- ];
- }
- get metadata() {
- return this.provider.metadata;
- }
- get isEnabled() {
- return this.state.configurations.length > 0;
- }
- get installationStatus() {
- const {configurations} = this.state;
- if (
- configurations.filter(i => i.organizationIntegrationStatus === 'disabled').length
- ) {
- return 'Disabled';
- }
- return configurations.length ? 'Installed' : 'Not Installed';
- }
- get integrationName() {
- return this.provider.name;
- }
- get featureData() {
- return this.metadata.features;
- }
- onInstall = (integration: Integration) => {
- // send the user to the configure integration view for that integration
- const {organization} = this.props;
- this.props.router.push(
- normalizeUrl(
- `/settings/${organization.slug}/integrations/${integration.provider.key}/${integration.id}/`
- )
- );
- };
- onRemove = (integration: Integration) => {
- const {organization} = this.props;
- const origIntegrations = [...this.state.configurations];
- const integrations = this.state.configurations.map(i =>
- i.id === integration.id
- ? {...i, organizationIntegrationStatus: 'pending_deletion' as ObjectStatus}
- : i
- );
- this.setState({configurations: integrations});
- const options: RequestOptions = {
- method: 'DELETE',
- error: () => {
- this.setState({configurations: origIntegrations});
- addErrorMessage(t('Failed to remove Integration'));
- },
- };
- this.api.request(
- `/organizations/${organization.slug}/integrations/${integration.id}/`,
- options
- );
- };
- onDisable = (integration: Integration) => {
- let url: string;
- const [domainName, orgName] = integration.domainName.split('/');
- if (integration.accountType === 'User') {
- url = `https://${domainName}/settings/installations/`;
- } else {
- url = `https://${domainName}/organizations/${orgName}/settings/installations/`;
- }
- window.open(url, '_blank');
- };
- handleExternalInstall = () => {
- this.trackIntegrationAnalytics('integrations.installation_start');
- };
- renderAlert() {
- return (
- <FirstPartyIntegrationAlert
- integrations={this.state.configurations ?? []}
- hideCTA
- />
- );
- }
- renderAdditionalCTA() {
- return (
- <FirstPartyIntegrationAdditionalCTA
- integrations={this.state.configurations ?? []}
- />
- );
- }
- renderTopButton(disabledFromFeatures: boolean, userHasAccess: boolean) {
- const {organization} = this.props;
- const provider = this.provider;
- const {metadata} = provider;
- const size = 'sm' as const;
- const priority = 'primary' as const;
- const buttonProps = {
- style: {marginBottom: space(1)},
- size,
- priority,
- 'data-test-id': 'install-button',
- disabled: disabledFromFeatures,
- organization,
- };
- if (!userHasAccess) {
- return this.renderRequestIntegrationButton();
- }
- if (provider.canAdd) {
- return (
- <AddIntegrationButton
- provider={provider}
- onAddIntegration={this.onInstall}
- analyticsParams={{
- view: 'integrations_directory_integration_detail',
- already_installed: this.installationStatus !== 'Not Installed',
- }}
- {...buttonProps}
- />
- );
- }
- if (metadata.aspects.externalInstall) {
- return (
- <Button
- icon={<IconOpen />}
- href={metadata.aspects.externalInstall.url}
- onClick={this.handleExternalInstall}
- external
- {...buttonProps}
- >
- {metadata.aspects.externalInstall.buttonText}
- </Button>
- );
- }
- // This should never happen but we can't return undefined without some refactoring.
- return <Fragment />;
- }
- renderConfigurations() {
- const {configurations} = this.state;
- const {organization} = this.props;
- const provider = this.provider;
- if (!configurations.length) {
- return this.renderEmptyConfigurations();
- }
- const alertText = getAlertText(configurations);
- return (
- <Fragment>
- {alertText && (
- <Alert type="warning" showIcon>
- {alertText}
- </Alert>
- )}
- <Panel>
- {configurations.map(integration => (
- <PanelItem key={integration.id}>
- <InstalledIntegration
- organization={organization}
- provider={provider}
- integration={integration}
- onRemove={this.onRemove}
- onDisable={this.onDisable}
- data-test-id={integration.id}
- trackIntegrationAnalytics={this.trackIntegrationAnalytics}
- requiresUpgrade={!!alertText}
- />
- </PanelItem>
- ))}
- </Panel>
- </Fragment>
- );
- }
- }
- export default withOrganization(IntegrationDetailedView);
|