|
@@ -1,12 +1,19 @@
|
|
|
import {Fragment} from 'react';
|
|
|
import {RouteComponentProps} from 'react-router';
|
|
|
import styled from '@emotion/styled';
|
|
|
+import * as Sentry from '@sentry/react';
|
|
|
|
|
|
+import {
|
|
|
+ addErrorMessage,
|
|
|
+ addLoadingMessage,
|
|
|
+ addSuccessMessage,
|
|
|
+} from 'app/actionCreators/indicator';
|
|
|
import {openEditOwnershipRules, openModal} from 'app/actionCreators/modal';
|
|
|
import Access from 'app/components/acl/access';
|
|
|
import Feature from 'app/components/acl/feature';
|
|
|
import Alert from 'app/components/alert';
|
|
|
import Button from 'app/components/button';
|
|
|
+import HookOrDefault from 'app/components/hookOrDefault';
|
|
|
import ExternalLink from 'app/components/links/externalLink';
|
|
|
import {IconWarning} from 'app/icons';
|
|
|
import {t, tct} from 'app/locale';
|
|
@@ -41,6 +48,11 @@ type State = {
|
|
|
integrations: Integration[];
|
|
|
} & AsyncView['state'];
|
|
|
|
|
|
+const CodeOwnersHeader = HookOrDefault({
|
|
|
+ hookName: 'component:codeowners-header',
|
|
|
+ defaultComponent: () => <Fragment />,
|
|
|
+});
|
|
|
+
|
|
|
class ProjectOwnership extends AsyncView<Props, State> {
|
|
|
getTitle() {
|
|
|
const {project} = this.props;
|
|
@@ -135,6 +147,25 @@ tags.sku_class:enterprise #enterprise`;
|
|
|
});
|
|
|
};
|
|
|
|
|
|
+ handleAddCodeOwnerRequest = async () => {
|
|
|
+ const {organization, project} = this.props;
|
|
|
+ try {
|
|
|
+ addLoadingMessage(t('Requesting\u2026'));
|
|
|
+ await this.api.requestPromise(
|
|
|
+ `/projects/${organization.slug}/${project.slug}/codeowners-request/`,
|
|
|
+ {
|
|
|
+ method: 'POST',
|
|
|
+ data: {},
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ addSuccessMessage(t('Request Sent'));
|
|
|
+ } catch (err) {
|
|
|
+ addErrorMessage(t('Unable to send request'));
|
|
|
+ Sentry.captureException(err);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
renderCodeOwnerErrors = () => {
|
|
|
const {project, organization} = this.props;
|
|
|
const {codeowners} = this.state;
|
|
@@ -273,9 +304,9 @@ tags.sku_class:enterprise #enterprise`;
|
|
|
{t('View Issues')}
|
|
|
</Button>
|
|
|
<Feature features={['integrations-codeowners']}>
|
|
|
- <Access access={['project:write']}>
|
|
|
+ <Access access={['org:integrations']}>
|
|
|
{({hasAccess}) =>
|
|
|
- hasAccess && (
|
|
|
+ hasAccess ? (
|
|
|
<CodeOwnerButton
|
|
|
onClick={this.handleAddCodeOwner}
|
|
|
size="small"
|
|
@@ -284,6 +315,15 @@ tags.sku_class:enterprise #enterprise`;
|
|
|
>
|
|
|
{t('Add CODEOWNERS File')}
|
|
|
</CodeOwnerButton>
|
|
|
+ ) : (
|
|
|
+ <CodeOwnerButton
|
|
|
+ onClick={this.handleAddCodeOwnerRequest}
|
|
|
+ size="small"
|
|
|
+ priority="primary"
|
|
|
+ data-test-id="add-codeowner-request-button"
|
|
|
+ >
|
|
|
+ {t('Request to Add CODEOWNERS File')}
|
|
|
+ </CodeOwnerButton>
|
|
|
)
|
|
|
}
|
|
|
</Access>
|
|
@@ -292,6 +332,11 @@ tags.sku_class:enterprise #enterprise`;
|
|
|
}
|
|
|
/>
|
|
|
<IssueOwnerDetails>{this.getDetail()}</IssueOwnerDetails>
|
|
|
+ <CodeOwnersHeader
|
|
|
+ addCodeOwner={this.handleAddCodeOwner}
|
|
|
+ handleRequest={this.handleAddCodeOwnerRequest}
|
|
|
+ />
|
|
|
+
|
|
|
<PermissionAlert />
|
|
|
<FeedbackAlert />
|
|
|
{this.renderCodeOwnerErrors()}
|