|
@@ -1,5 +1,7 @@
|
|
|
import React from 'react';
|
|
|
import styled from '@emotion/styled';
|
|
|
+import Modal from 'react-bootstrap/lib/Modal';
|
|
|
+import sortBy from 'lodash/sortBy';
|
|
|
|
|
|
import AsyncComponent from 'app/components/asyncComponent';
|
|
|
import Button from 'app/components/button';
|
|
@@ -9,9 +11,9 @@ import RepositoryProjectPathConfigRow, {
|
|
|
NameRepoColumn,
|
|
|
OutputPathColumn,
|
|
|
InputPathColumn,
|
|
|
- DefaultBranchColumn,
|
|
|
ButtonColumn,
|
|
|
} from 'app/components/repositoryProjectPathConfigRow';
|
|
|
+import RepositoryProjectPathConfigForm from 'app/components/repositoryProjectPathConfigForm';
|
|
|
import {Panel, PanelBody, PanelHeader, PanelItem} from 'app/components/panels';
|
|
|
import space from 'app/styles/space';
|
|
|
import {t} from 'app/locale';
|
|
@@ -29,42 +31,83 @@ type Props = AsyncComponent['props'] & {
|
|
|
};
|
|
|
|
|
|
type State = AsyncComponent['state'] & {
|
|
|
- repoProjectPathConfigs: RepositoryProjectPathConfig[];
|
|
|
+ pathConfigs: RepositoryProjectPathConfig[];
|
|
|
repos: Repository[];
|
|
|
+ showModal: boolean;
|
|
|
};
|
|
|
|
|
|
class IntegrationCodeMappings extends AsyncComponent<Props, State> {
|
|
|
getDefaultState(): State {
|
|
|
return {
|
|
|
...super.getDefaultState(),
|
|
|
- repoProjectPathConfigs: [],
|
|
|
+ pathConfigs: [],
|
|
|
repos: [],
|
|
|
+ showModal: false,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ get integrationId() {
|
|
|
+ return this.props.integration.id;
|
|
|
+ }
|
|
|
+
|
|
|
+ get projects() {
|
|
|
+ return this.props.organization.projects;
|
|
|
+ }
|
|
|
+
|
|
|
+ get pathConfigs() {
|
|
|
+ // we want to sort by the project slug and the
|
|
|
+ // id of the config
|
|
|
+ return sortBy(this.state.pathConfigs, [
|
|
|
+ ({projectSlug}) => projectSlug,
|
|
|
+ ({id}) => parseInt(id, 10),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ get repos() {
|
|
|
+ //endpoint doesn't support loading only the repos for this integration
|
|
|
+ //but most people only have one source code repo so this should be fine
|
|
|
+ return this.state.repos.filter(repo => repo.integrationId === this.integrationId);
|
|
|
+ }
|
|
|
+
|
|
|
getEndpoints(): ReturnType<AsyncComponent['getEndpoints']> {
|
|
|
const orgSlug = this.props.organization.slug;
|
|
|
return [
|
|
|
[
|
|
|
- 'repoProjectPathConfigs',
|
|
|
- `/organizations/${orgSlug}/integrations/${this.props.integration.id}/repo-project-path-configs/`,
|
|
|
+ 'pathConfigs',
|
|
|
+ `/organizations/${orgSlug}/integrations/${this.integrationId}/repo-project-path-configs/`,
|
|
|
],
|
|
|
['repos', `/organizations/${orgSlug}/repos/`, {query: {status: 'active'}}],
|
|
|
];
|
|
|
}
|
|
|
|
|
|
- getMatchingRepo(repoProjectPathConfig: RepositoryProjectPathConfig) {
|
|
|
- return this.state.repos.find(repo => repo.id === repoProjectPathConfig.repoId);
|
|
|
+ getMatchingProject(pathConfig: RepositoryProjectPathConfig) {
|
|
|
+ return this.projects.find(project => project.id === pathConfig.projectId);
|
|
|
}
|
|
|
|
|
|
- getMatchingProject(repoProjectPathConfig: RepositoryProjectPathConfig) {
|
|
|
- return this.props.organization.projects.find(
|
|
|
- project => project.id === repoProjectPathConfig.projectId
|
|
|
- );
|
|
|
- }
|
|
|
+ openModal = () => {
|
|
|
+ this.setState({
|
|
|
+ showModal: true,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ closeModal = () => {
|
|
|
+ this.setState({
|
|
|
+ showModal: false,
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ handleSubmitSuccess = (pathConfig: RepositoryProjectPathConfig) => {
|
|
|
+ let {pathConfigs} = this.state;
|
|
|
+ // our getter handles the order of the configs
|
|
|
+ pathConfigs = pathConfigs.concat([pathConfig]);
|
|
|
+ this.setState({pathConfigs});
|
|
|
+ this.closeModal();
|
|
|
+ };
|
|
|
|
|
|
renderBody() {
|
|
|
- const {repoProjectPathConfigs} = this.state;
|
|
|
+ const {organization, integration} = this.props;
|
|
|
+ const {showModal} = this.state;
|
|
|
+ const pathConfigs = this.pathConfigs;
|
|
|
return (
|
|
|
<React.Fragment>
|
|
|
<Panel>
|
|
@@ -73,33 +116,34 @@ class IntegrationCodeMappings extends AsyncComponent<Props, State> {
|
|
|
<NameRepoColumn>{t('Code Path Mappings')}</NameRepoColumn>
|
|
|
<OutputPathColumn>{t('Output Path')}</OutputPathColumn>
|
|
|
<InputPathColumn>{t('Input Path')}</InputPathColumn>
|
|
|
- <DefaultBranchColumn>{t('Branch')}</DefaultBranchColumn>
|
|
|
<ButtonColumn>
|
|
|
- <AddButton size="xsmall" icon={<IconAdd size="xs" isCircled />}>
|
|
|
+ <AddButton
|
|
|
+ onClick={this.openModal}
|
|
|
+ size="xsmall"
|
|
|
+ icon={<IconAdd size="xs" isCircled />}
|
|
|
+ >
|
|
|
{t('Add Mapping')}
|
|
|
</AddButton>
|
|
|
</ButtonColumn>
|
|
|
</HeaderLayout>
|
|
|
</PanelHeader>
|
|
|
<PanelBody>
|
|
|
- {repoProjectPathConfigs.length === 0 && (
|
|
|
+ {pathConfigs.length === 0 && (
|
|
|
<EmptyMessage description={t('No code path mappings')} />
|
|
|
)}
|
|
|
- {repoProjectPathConfigs
|
|
|
- .map(repoProjectPathConfig => {
|
|
|
- const repo = this.getMatchingRepo(repoProjectPathConfig);
|
|
|
- const project = this.getMatchingProject(repoProjectPathConfig);
|
|
|
- // this should never happen since our repoProjectPathConfig would be deleted
|
|
|
- // if the repo or project were deleted
|
|
|
- if (!repo || !project) {
|
|
|
+ {pathConfigs
|
|
|
+ .map(pathConfig => {
|
|
|
+ const project = this.getMatchingProject(pathConfig);
|
|
|
+ // this should never happen since our pathConfig would be deleted
|
|
|
+ // if project was deleted
|
|
|
+ if (!project) {
|
|
|
return null;
|
|
|
}
|
|
|
return (
|
|
|
- <ConfigPanelItem key={repoProjectPathConfig.id}>
|
|
|
+ <ConfigPanelItem key={pathConfig.id}>
|
|
|
<Layout>
|
|
|
<RepositoryProjectPathConfigRow
|
|
|
- repoProjectPathConfig={repoProjectPathConfig}
|
|
|
- repo={repo}
|
|
|
+ pathConfig={pathConfig}
|
|
|
project={project}
|
|
|
/>
|
|
|
</Layout>
|
|
@@ -109,6 +153,25 @@ class IntegrationCodeMappings extends AsyncComponent<Props, State> {
|
|
|
.filter(item => !!item)}
|
|
|
</PanelBody>
|
|
|
</Panel>
|
|
|
+
|
|
|
+ <Modal
|
|
|
+ show={showModal}
|
|
|
+ onHide={this.closeModal}
|
|
|
+ enforceFocus={false}
|
|
|
+ backdrop="static"
|
|
|
+ animation={false}
|
|
|
+ >
|
|
|
+ <Modal.Header closeButton />
|
|
|
+ <Modal.Body>
|
|
|
+ <RepositoryProjectPathConfigForm
|
|
|
+ organization={organization}
|
|
|
+ integration={integration}
|
|
|
+ projects={this.projects}
|
|
|
+ repos={this.repos}
|
|
|
+ onSubmitSuccess={this.handleSubmitSuccess}
|
|
|
+ />
|
|
|
+ </Modal.Body>
|
|
|
+ </Modal>
|
|
|
</React.Fragment>
|
|
|
);
|
|
|
}
|
|
@@ -123,8 +186,8 @@ const Layout = styled('div')`
|
|
|
grid-column-gap: ${space(1)};
|
|
|
width: 100%;
|
|
|
align-items: center;
|
|
|
- grid-template-columns: 4fr 2fr 2fr 1.2fr 1.5fr;
|
|
|
- grid-template-areas: 'name-repo output-path input-path default-branch button';
|
|
|
+ grid-template-columns: 4.5fr 2.5fr 2.5fr 1.6fr;
|
|
|
+ grid-template-areas: 'name-repo output-path input-path button';
|
|
|
`;
|
|
|
|
|
|
const HeaderLayout = styled(Layout)`
|