Adds popover help text to project details, and unifies those strings (used in the form and the details view) into 1 file (#12039)

This commit is contained in:
Alex Corey
2022-04-19 14:35:51 -04:00
committed by GitHub
parent 3a1268de1e
commit ae7960e9d7
14 changed files with 358 additions and 266 deletions

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { node, bool, string } from 'prop-types';
import { oneOfType, node, bool, string } from 'prop-types';
import { TextListItem, TextListItemVariants } from '@patternfly/react-core'; import { TextListItem, TextListItemVariants } from '@patternfly/react-core';
import styled from 'styled-components'; import styled from 'styled-components';
import Popover from '../Popover'; import Popover from '../Popover';
@@ -81,7 +82,7 @@ Detail.propTypes = {
value: node, value: node,
fullWidth: bool, fullWidth: bool,
alwaysVisible: bool, alwaysVisible: bool,
helpText: string, helpText: oneOfType([string, node]),
}; };
Detail.defaultProps = { Detail.defaultProps = {
value: null, value: null,

View File

@@ -10,6 +10,8 @@ const PopoverButton = styled.button`
padding: var(--pf-global--spacer--xs); padding: var(--pf-global--spacer--xs);
margin: -(var(--pf-global--spacer--xs)); margin: -(var(--pf-global--spacer--xs));
font-size: var(--pf-global--FontSize--sm); font-size: var(--pf-global--FontSize--sm);
--pf-c-form__group-label-help--Color: var(--pf-global--Color--200);
--pf-c-form__group-label-help--hover--Color: var(--pf-global--Color--100);
`; `;
function Popover({ ariaLabel, content, header, id, maxWidth, ...rest }) { function Popover({ ariaLabel, content, header, id, maxWidth, ...rest }) {

View File

@@ -27,7 +27,9 @@ import useRequest, { useDismissableError } from 'hooks/useRequest';
import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails'; import { relatedResourceDeleteRequests } from 'util/getRelatedResourceDeleteDetails';
import StatusLabel from 'components/StatusLabel'; import StatusLabel from 'components/StatusLabel';
import { formatDateString } from 'util/dates'; import { formatDateString } from 'util/dates';
import Popover from 'components/Popover';
import ProjectSyncButton from '../shared/ProjectSyncButton'; import ProjectSyncButton from '../shared/ProjectSyncButton';
import ProjectHelpTextStrings from '../shared/Project.helptext';
import useWsProject from './useWsProject'; import useWsProject from './useWsProject';
const Label = styled.span` const Label = styled.span`
@@ -57,7 +59,7 @@ function ProjectDetail({ project }) {
summary_fields, summary_fields,
} = useWsProject(project); } = useWsProject(project);
const history = useHistory(); const history = useHistory();
const projectHelpText = ProjectHelpTextStrings();
const { const {
request: deleteProject, request: deleteProject,
isLoading, isLoading,
@@ -82,29 +84,37 @@ function ProjectDetail({ project }) {
optionsList = ( optionsList = (
<TextList component={TextListVariants.ul}> <TextList component={TextListVariants.ul}>
{scm_clean && ( {scm_clean && (
<TextListItem <TextListItem component={TextListItemVariants.li}>
component={TextListItemVariants.li} {t`Discard local changes before syncing`}
>{t`Discard local changes before syncing`}</TextListItem> <Popover content={projectHelpText.options.clean} />
</TextListItem>
)} )}
{scm_delete_on_update && ( {scm_delete_on_update && (
<TextListItem <TextListItem component={TextListItemVariants.li}>
component={TextListItemVariants.li} {t`Delete the project before syncing`}{' '}
>{t`Delete the project before syncing`}</TextListItem> <Popover
content={projectHelpText.options.delete}
id="scm-delete-on-update"
/>
</TextListItem>
)} )}
{scm_track_submodules && ( {scm_track_submodules && (
<TextListItem <TextListItem component={TextListItemVariants.li}>
component={TextListItemVariants.li} {t`Track submodules latest commit on branch`}{' '}
>{t`Track submodules latest commit on branch`}</TextListItem> <Popover content={projectHelpText.options.trackSubModules} />
</TextListItem>
)} )}
{scm_update_on_launch && ( {scm_update_on_launch && (
<TextListItem <TextListItem component={TextListItemVariants.li}>
component={TextListItemVariants.li} {t`Update revision on job launch`}{' '}
>{t`Update revision on job launch`}</TextListItem> <Popover content={projectHelpText.options.updateOnLaunch} />
</TextListItem>
)} )}
{allow_override && ( {allow_override && (
<TextListItem <TextListItem component={TextListItemVariants.li}>
component={TextListItemVariants.li} {t`Allow branch override`}{' '}
>{t`Allow branch override`}</TextListItem> <Popover content={projectHelpText.options.allowBranchOverride} />
</TextListItem>
)} )}
</TextList> </TextList>
); );
@@ -134,7 +144,10 @@ function ProjectDetail({ project }) {
} else if (summary_fields?.last_job) { } else if (summary_fields?.last_job) {
job = summary_fields.last_job; job = summary_fields.last_job;
} }
const getSourceControlUrlHelpText = () =>
scm_type === 'git'
? projectHelpText.githubSourceControlUrl
: projectHelpText.svnSourceControlUrl;
return ( return (
<CardBody> <CardBody>
<DetailList gutter="sm"> <DetailList gutter="sm">
@@ -197,9 +210,25 @@ function ProjectDetail({ project }) {
} }
alwaysVisible alwaysVisible
/> />
<Detail label={t`Source Control URL`} value={scm_url} /> <Detail
<Detail label={t`Source Control Branch`} value={scm_branch} /> helpText={
<Detail label={t`Source Control Refspec`} value={scm_refspec} /> scm_type === 'git' || scm_type === 'svn'
? getSourceControlUrlHelpText()
: ''
}
label={t`Source Control URL`}
value={scm_url}
/>
<Detail
helpText={projectHelpText.branchFormField}
label={t`Source Control Branch`}
value={scm_branch}
/>
<Detail
helpText={projectHelpText.sourceControlRefspec}
label={t`Source Control Refspec`}
value={scm_refspec}
/>
{summary_fields.credential && ( {summary_fields.credential && (
<Detail <Detail
label={t`Source Control Credential`} label={t`Source Control Credential`}
@@ -217,16 +246,25 @@ function ProjectDetail({ project }) {
value={`${scm_update_cache_timeout} ${t`Seconds`}`} value={`${scm_update_cache_timeout} ${t`Seconds`}`}
/> />
<ExecutionEnvironmentDetail <ExecutionEnvironmentDetail
helpText={projectHelpText.executionEnvironment}
virtualEnvironment={custom_virtualenv} virtualEnvironment={custom_virtualenv}
executionEnvironment={summary_fields?.default_environment} executionEnvironment={summary_fields?.default_environment}
isDefaultEnvironment isDefaultEnvironment
/> />
<Config> <Config>
{({ project_base_dir }) => ( {({ project_base_dir }) => (
<Detail label={t`Project Base Path`} value={project_base_dir} /> <Detail
helpText={projectHelpText.projectBasePath}
label={t`Project Base Path`}
value={project_base_dir}
/>
)} )}
</Config> </Config>
<Detail label={t`Playbook Directory`} value={local_path} /> <Detail
helpText={projectHelpText.projectLocalPath}
label={t`Playbook Directory`}
value={local_path}
/>
<UserDateDetail <UserDateDetail
label={t`Created`} label={t`Created`}
date={created} date={created}

View File

@@ -20,6 +20,12 @@ jest.mock('react-router-dom', () => ({
url: '/projects/1/details', url: '/projects/1/details',
}), }),
})); }));
jest.mock('hooks/useBrandName', () => ({
__esModule: true,
default: () => ({
current: 'AWX',
}),
}));
describe('<ProjectDetail />', () => { describe('<ProjectDetail />', () => {
const mockProject = { const mockProject = {
id: 1, id: 1,
@@ -126,16 +132,18 @@ describe('<ProjectDetail />', () => {
'2019-10-10T01:15:06.780490Z' '2019-10-10T01:15:06.780490Z'
); );
expect( expect(
wrapper wrapper.find('Detail[label="Enabled Options"]').find('li')
.find('Detail[label="Enabled Options"]') ).toHaveLength(5);
.containsAllMatchingElements([ const options = [
<li>Discard local changes before syncing</li>, 'Discard local changes before syncing',
<li>Delete the project before syncing</li>, 'Delete the project before syncing',
<li>Track submodules latest commit on branch</li>, 'Track submodules latest commit on branch',
<li>Update revision on job launch</li>, 'Update revision on job launch',
<li>Allow branch override</li>, 'Allow branch override',
]) ];
).toEqual(true); wrapper.find('li').map((item, index) => {
expect(item.text().includes(options[index]));
});
}); });
test('should hide options label when all project options return false', () => { test('should hide options label when all project options return false', () => {
@@ -237,7 +245,7 @@ describe('<ProjectDetail />', () => {
expect(history.location.pathname).toEqual('/projects/1/edit'); expect(history.location.pathname).toEqual('/projects/1/edit');
}); });
test('sync button should call api to syn project', async () => { test('sync button should call api to sync project', async () => {
ProjectsAPI.readSync.mockResolvedValue({ data: { can_update: true } }); ProjectsAPI.readSync.mockResolvedValue({ data: { can_update: true } });
const wrapper = mountWithContexts(<ProjectDetail project={mockProject} />); const wrapper = mountWithContexts(<ProjectDetail project={mockProject} />);
await act(() => await act(() =>

View File

@@ -6,7 +6,12 @@ import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import ProjectsListItem from './ProjectListItem'; import ProjectsListItem from './ProjectListItem';
jest.mock('../../../api/models/Projects'); jest.mock('../../../api/models/Projects');
jest.mock('hooks/useBrandName', () => ({
__esModule: true,
default: () => ({
current: 'AWX',
}),
}));
describe('<ProjectsListItem />', () => { describe('<ProjectsListItem />', () => {
test('launch button shown to users with start capabilities', () => { test('launch button shown to users with start capabilities', () => {
const wrapper = mountWithContexts( const wrapper = mountWithContexts(

View File

@@ -0,0 +1,142 @@
import React from 'react';
import { t } from '@lingui/macro';
import getDocsBaseUrl from 'util/getDocsBaseUrl';
import { useConfig } from 'contexts/Config';
import useBrandName from 'hooks/useBrandName';
const ProjectHelpTextStrings = () => ({
executionEnvironment: t`The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level.`,
projectBasePath: (
<span>
{t`Base path used for locating playbooks. Directories
found inside this path will be listed in the playbook directory drop-down.
Together the base path and selected playbook directory provide the full
path used to locate playbooks.`}
<br />
<br />
{t`Change PROJECTS_ROOT when deploying
${useBrandName()} to change this location.`}
</span>
),
projectLocalPath: t`Select from the list of directories found in
the Project Base Path. Together the base path and the playbook
directory provide the full path used to locate playbooks.`,
githubSourceControlUrl: (
<span>
{t`Example URLs for GIT Source Control include:`}
<ul css="margin: 10px 0 10px 20px">
<li>
<code>https://github.com/ansible/ansible.git</code>
</li>
<li>
<code>git@github.com:ansible/ansible.git</code>
</li>
<li>
<code>git://servername.example.com/ansible.git</code>
</li>
</ul>
{t`Note: When using SSH protocol for GitHub or
Bitbucket, enter an SSH key only, do not enter a username
(other than git). Additionally, GitHub and Bitbucket do
not support password authentication when using SSH. GIT
read only protocol (git://) does not use username or
password information.`}
</span>
),
svnSourceControlUrl: (
<span>
{t`Example URLs for Subversion Source Control include:`}
<ul css={{ margin: '10px 0 10px 20px' }}>
<li>
<code>https://github.com/ansible/ansible</code>
</li>
<li>
<code>svn://servername.example.com/path</code>
</li>
<li>
<code>svn+ssh://servername.example.com/path</code>
</li>
</ul>
</span>
),
syncButtonDisabled: t`This project is currently on sync and cannot be clicked until sync process completed`,
archiveUrl: (
<span>
{t`Example URLs for Remote Archive Source Control include:`}
<ul css={{ margin: '10px 0 10px 20px' }}>
<li>
<code>https://github.com/username/project/archive/v0.0.1.tar.gz</code>
</li>
<li>
<code>https://github.com/username/project/archive/v0.0.2.zip</code>
</li>
</ul>
</span>
),
sourceControlRefspec: (
<span>
{t`A refspec to fetch (passed to the Ansible git
module). This parameter allows access to references via
the branch field not otherwise available.`}
<br />
<br />
{t`Note: This field assumes the remote name is "origin".`}
<br />
<br />
{t`Examples include:`}
<ul css={{ margin: '10px 0 10px 20px' }}>
<li>
<code>refs/*:refs/remotes/origin/*</code>
</li>
<li>
<code>refs/pull/62/head:refs/remotes/origin/pull/62/head</code>
</li>
</ul>
{t`The first fetches all references. The second
fetches the Github pull request number 62, in this example
the branch needs to be "pull/62/head".`}
<br />
<br />
{t`For more information, refer to the`}{' '}
<a
target="_blank"
rel="noopener noreferrer"
href={`${getDocsBaseUrl(
useConfig()
)}/html/userguide/projects.html#manage-playbooks-using-source-control`}
>
{t`Documentation.`}
</a>
</span>
),
branchFormField: t`Branch to checkout. In addition to branches,
you can input tags, commit hashes, and arbitrary refs. Some
commit hashes and refs may not be available unless you also
provide a custom refspec.`,
options: {
clean: t`Remove any local modifications prior to performing an update.`,
delete: t`Delete the local repository in its entirety prior to
performing an update. Depending on the size of the
repository this may significantly increase the amount
of time required to complete an update.`,
trackSubModules: t`Submodules will track the latest commit on
their master branch (or other branch specified in
.gitmodules). If no, submodules will be kept at
the revision specified by the main project.
This is equivalent to specifying the --remote
flag to git submodule update.`,
updateOnLaunch: t`Each time a job runs using this project, update the
revision of the project prior to starting the job.`,
allowBranchOverride: t`Allow changing the Source Control branch or revision in a job
template that uses this project.`,
cacheTimeout: t`Time in seconds to consider a project
to be current. During job runs and callbacks the task
system will evaluate the timestamp of the latest project
update. If it is older than Cache Timeout, it is not
considered current, and a new project update will be
performed.`,
},
});
export default ProjectHelpTextStrings;

View File

@@ -16,6 +16,7 @@ import ExecutionEnvironmentLookup from 'components/Lookup/ExecutionEnvironmentLo
import { CredentialTypesAPI, ProjectsAPI } from 'api'; import { CredentialTypesAPI, ProjectsAPI } from 'api';
import { required } from 'util/validators'; import { required } from 'util/validators';
import { FormColumnLayout, SubFormLayout } from 'components/FormLayout'; import { FormColumnLayout, SubFormLayout } from 'components/FormLayout';
import ProjectHelpTextStrings from './Project.helptext';
import { import {
GitSubForm, GitSubForm,
SvnSubForm, SvnSubForm,
@@ -195,7 +196,7 @@ function ProjectFormFields({
} }
onBlur={() => executionEnvironmentHelpers.setTouched()} onBlur={() => executionEnvironmentHelpers.setTouched()}
value={executionEnvironmentField.value} value={executionEnvironmentField.value}
popoverContent={t`The execution environment that will be used for jobs that use this project. This will be used as fallback when an execution environment has not been explicitly assigned at the job template or workflow level.`} popoverContent={ProjectHelpTextStrings.execution_environment}
onChange={handleExecutionEnvironmentUpdate} onChange={handleExecutionEnvironmentUpdate}
tooltip={t`Select an organization before editing the default execution environment.`} tooltip={t`Select an organization before editing the default execution environment.`}
globallyAvailable globallyAvailable

View File

@@ -1,7 +1,7 @@
import 'styled-components/macro'; import 'styled-components/macro';
import React from 'react'; import React from 'react';
import ProjectHelpTextStrings from '../Project.helptext';
import { t } from '@lingui/macro';
import { import {
UrlFormField, UrlFormField,
ScmCredentialFormField, ScmCredentialFormField,
@@ -12,33 +12,18 @@ const ArchiveSubForm = ({
credential, credential,
onCredentialSelection, onCredentialSelection,
scmUpdateOnLaunch, scmUpdateOnLaunch,
}) => ( }) => {
<> const projectHelpText = ProjectHelpTextStrings();
<UrlFormField return (
tooltip={ <>
<span> <UrlFormField tooltip={projectHelpText.archiveUrl} />
{t`Example URLs for Remote Archive Source Control include:`} <ScmCredentialFormField
<ul css={{ margin: '10px 0 10px 20px' }}> credential={credential}
<li> onCredentialSelection={onCredentialSelection}
<code> />
https://github.com/username/project/archive/v0.0.1.tar.gz <ScmTypeOptions scmUpdateOnLaunch={scmUpdateOnLaunch} />
</code> </>
</li> );
<li> };
<code>
https://github.com/username/project/archive/v0.0.2.zip
</code>
</li>
</ul>
</span>
}
/>
<ScmCredentialFormField
credential={credential}
onCredentialSelection={onCredentialSelection}
/>
<ScmTypeOptions scmUpdateOnLaunch={scmUpdateOnLaunch} />
</>
);
export default ArchiveSubForm; export default ArchiveSubForm;

View File

@@ -1,10 +1,8 @@
import 'styled-components/macro'; import 'styled-components/macro';
import React from 'react'; import React from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import FormField from 'components/FormField'; import FormField from 'components/FormField';
import { useConfig } from 'contexts/Config';
import getDocsBaseUrl from 'util/getDocsBaseUrl';
import { import {
UrlFormField, UrlFormField,
BranchFormField, BranchFormField,
@@ -12,38 +10,17 @@ import {
ScmTypeOptions, ScmTypeOptions,
} from './SharedFields'; } from './SharedFields';
import ProjectHelpTextStrings from '../Project.helptext';
const GitSubForm = ({ const GitSubForm = ({
credential, credential,
onCredentialSelection, onCredentialSelection,
scmUpdateOnLaunch, scmUpdateOnLaunch,
}) => { }) => {
const config = useConfig(); const projectHelpStrings = ProjectHelpTextStrings();
return ( return (
<> <>
<UrlFormField <UrlFormField tooltip={projectHelpStrings.githubSourceControlUrl} />
tooltip={
<span>
{t`Example URLs for GIT Source Control include:`}
<ul css="margin: 10px 0 10px 20px">
<li>
<code>https://github.com/ansible/ansible.git</code>
</li>
<li>
<code>git@github.com:ansible/ansible.git</code>
</li>
<li>
<code>git://servername.example.com/ansible.git</code>
</li>
</ul>
{t`Note: When using SSH protocol for GitHub or
Bitbucket, enter an SSH key only, do not enter a username
(other than git). Additionally, GitHub and Bitbucket do
not support password authentication when using SSH. GIT
read only protocol (git://) does not use username or
password information.`}
</span>
}
/>
<BranchFormField label={t`Source Control Branch/Tag/Commit`} /> <BranchFormField label={t`Source Control Branch/Tag/Commit`} />
<FormField <FormField
id="project-scm-refspec" id="project-scm-refspec"
@@ -51,42 +28,7 @@ const GitSubForm = ({
name="scm_refspec" name="scm_refspec"
type="text" type="text"
tooltipMaxWidth="400px" tooltipMaxWidth="400px"
tooltip={ tooltip={projectHelpStrings.sourceControlRefspec}
<span>
{t`A refspec to fetch (passed to the Ansible git
module). This parameter allows access to references via
the branch field not otherwise available.`}
<br />
<br />
{t`Note: This field assumes the remote name is "origin".`}
<br />
<br />
{t`Examples include:`}
<ul css={{ margin: '10px 0 10px 20px' }}>
<li>
<code>refs/*:refs/remotes/origin/*</code>
</li>
<li>
<code>refs/pull/62/head:refs/remotes/origin/pull/62/head</code>
</li>
</ul>
{t`The first fetches all references. The second
fetches the Github pull request number 62, in this example
the branch needs to be "pull/62/head".`}
<br />
<br />
{t`For more information, refer to the`}{' '}
<a
target="_blank"
rel="noopener noreferrer"
href={`${getDocsBaseUrl(
config
)}/html/userguide/projects.html#manage-playbooks-using-source-control`}
>
{t`Documentation.`}
</a>
</span>
}
/> />
<ScmCredentialFormField <ScmCredentialFormField
credential={credential} credential={credential}

View File

@@ -8,6 +8,7 @@ import AnsibleSelect from 'components/AnsibleSelect';
import FormField from 'components/FormField'; import FormField from 'components/FormField';
import Popover from 'components/Popover'; import Popover from 'components/Popover';
import useBrandName from 'hooks/useBrandName'; import useBrandName from 'hooks/useBrandName';
import ProjectHelpStrings from '../Project.helptext';
const ManualSubForm = ({ const ManualSubForm = ({
localPath, localPath,
@@ -15,6 +16,7 @@ const ManualSubForm = ({
project_local_paths, project_local_paths,
}) => { }) => {
const brandName = useBrandName(); const brandName = useBrandName();
const projectHelpStrings = ProjectHelpStrings();
const localPaths = [...new Set([...project_local_paths, localPath])]; const localPaths = [...new Set([...project_local_paths, localPath])];
const options = [ const options = [
@@ -61,18 +63,7 @@ const ManualSubForm = ({
name="base_dir" name="base_dir"
type="text" type="text"
isReadOnly isReadOnly
tooltip={ tooltip={projectHelpStrings.projectBasePath}
<span>
{t`Base path used for locating playbooks. Directories
found inside this path will be listed in the playbook directory drop-down.
Together the base path and selected playbook directory provide the full
path used to locate playbooks.`}
<br />
<br />
{t`Change PROJECTS_ROOT when deploying
${brandName} to change this location.`}
</span>
}
/> />
<FormGroup <FormGroup
fieldId="project-local-path" fieldId="project-local-path"
@@ -80,13 +71,7 @@ const ManualSubForm = ({
isRequired isRequired
validated={!pathMeta.touched || !pathMeta.error ? 'default' : 'error'} validated={!pathMeta.touched || !pathMeta.error ? 'default' : 'error'}
label={t`Playbook Directory`} label={t`Playbook Directory`}
labelIcon={ labelIcon={<Popover content={projectHelpStrings.projectLocalPath} />}
<Popover
content={t`Select from the list of directories found in
the Project Base Path. Together the base path and the playbook
directory provide the full path used to locate playbooks.`}
/>
}
> >
<AnsibleSelect <AnsibleSelect
{...pathField} {...pathField}

View File

@@ -7,6 +7,7 @@ import CredentialLookup from 'components/Lookup/CredentialLookup';
import FormField, { CheckboxField } from 'components/FormField'; import FormField, { CheckboxField } from 'components/FormField';
import { required } from 'util/validators'; import { required } from 'util/validators';
import { FormCheckboxLayout, FormFullWidthLayout } from 'components/FormLayout'; import { FormCheckboxLayout, FormFullWidthLayout } from 'components/FormLayout';
import ProjectHelpTextStrings from '../Project.helptext';
export const UrlFormField = ({ tooltip }) => ( export const UrlFormField = ({ tooltip }) => (
<FormField <FormField
@@ -21,18 +22,18 @@ export const UrlFormField = ({ tooltip }) => (
/> />
); );
export const BranchFormField = ({ label }) => ( export const BranchFormField = ({ label }) => {
<FormField const projectHelpStrings = ProjectHelpTextStrings();
id="project-scm-branch" return (
name="scm_branch" <FormField
type="text" id="project-scm-branch"
label={label} name="scm_branch"
tooltip={t`Branch to checkout. In addition to branches, type="text"
you can input tags, commit hashes, and arbitrary refs. Some label={label}
commit hashes and refs may not be available unless you also tooltip={projectHelpStrings.branchFormField}
provide a custom refspec.`} />
/> );
); };
export const ScmCredentialFormField = ({ export const ScmCredentialFormField = ({
credential, credential,
@@ -59,74 +60,66 @@ export const ScmCredentialFormField = ({
); );
}; };
export const ScmTypeOptions = ({ scmUpdateOnLaunch, hideAllowOverride }) => ( export const ScmTypeOptions = ({ scmUpdateOnLaunch, hideAllowOverride }) => {
<FormFullWidthLayout> const projectHelpStrings = ProjectHelpTextStrings();
<FormGroup fieldId="project-option-checkboxes" label={t`Options`}> const { values } = useFormikContext();
<FormCheckboxLayout>
<CheckboxField
id="option-scm-clean"
name="scm_clean"
label={t`Clean`}
tooltip={t`Remove any local modifications prior to performing an update.`}
/>
<CheckboxField
id="option-scm-delete-on-update"
name="scm_delete_on_update"
label={t`Delete`}
tooltip={t`Delete the local repository in its entirety prior to
performing an update. Depending on the size of the
repository this may significantly increase the amount
of time required to complete an update.`}
/>
<CheckboxField
id="option-scm-track-submodules"
name="scm_track_submodules"
label={t`Track submodules`}
tooltip={t`Submodules will track the latest commit on
their master branch (or other branch specified in
.gitmodules). If no, submodules will be kept at
the revision specified by the main project.
This is equivalent to specifying the --remote
flag to git submodule update.`}
/>
<CheckboxField
id="option-scm-update-on-launch"
name="scm_update_on_launch"
label={t`Update Revision on Launch`}
tooltip={t`Each time a job runs using this project, update the
revision of the project prior to starting the job.`}
/>
{!hideAllowOverride && (
<CheckboxField
id="option-allow-override"
name="allow_override"
label={t`Allow Branch Override`}
tooltip={t`Allow changing the Source Control branch or revision in a job
template that uses this project.`}
/>
)}
</FormCheckboxLayout>
</FormGroup>
{scmUpdateOnLaunch && ( return (
<> <FormFullWidthLayout>
<Title size="md" headingLevel="h4"> <FormGroup fieldId="project-option-checkboxes" label={t`Options`}>
{t`Option Details`} <FormCheckboxLayout>
</Title> <CheckboxField
<FormField id="option-scm-clean"
id="project-cache-timeout" name="scm_clean"
name="scm_update_cache_timeout" label={t`Clean`}
type="number" tooltip={projectHelpStrings.options.clean}
min="0" />
label={t`Cache Timeout`} <CheckboxField
tooltip={t`Time in seconds to consider a project id="option-scm-delete-on-update"
to be current. During job runs and callbacks the task name="scm_delete_on_update"
system will evaluate the timestamp of the latest project label={t`Delete`}
update. If it is older than Cache Timeout, it is not tooltip={projectHelpStrings.options.delete}
considered current, and a new project update will be />
performed.`} {values.scm_type === 'git' ? (
/> <CheckboxField
</> id="option-scm-track-submodules"
)} name="scm_track_submodules"
</FormFullWidthLayout> label={t`Track submodules`}
); tooltip={projectHelpStrings.options.trackSubModules}
/>
) : null}
<CheckboxField
id="option-scm-update-on-launch"
name="scm_update_on_launch"
label={t`Update Revision on Launch`}
tooltip={projectHelpStrings.options.updateOnLaunch}
/>
{!hideAllowOverride && (
<CheckboxField
id="option-allow-override"
name="allow_override"
label={t`Allow Branch Override`}
tooltip={projectHelpStrings.options.allowBranchOverride}
/>
)}
</FormCheckboxLayout>
</FormGroup>
{scmUpdateOnLaunch && (
<>
<Title size="md" headingLevel="h4">
{t`Option Details`}
</Title>
<FormField
id="project-cache-timeout"
name="scm_update_cache_timeout"
type="number"
min="0"
label={t`Cache Timeout`}
tooltip={projectHelpStrings.options.cacheTimeout}
/>
</>
)}
</FormFullWidthLayout>
);
};

View File

@@ -1,7 +1,8 @@
import 'styled-components/macro'; import 'styled-components/macro';
import React from 'react'; import React from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import ProjectHelpTextStrings from '../Project.helptext';
import { import {
UrlFormField, UrlFormField,
BranchFormField, BranchFormField,
@@ -13,33 +14,19 @@ const SvnSubForm = ({
credential, credential,
onCredentialSelection, onCredentialSelection,
scmUpdateOnLaunch, scmUpdateOnLaunch,
}) => ( }) => {
<> const projectHelpStrings = ProjectHelpTextStrings();
<UrlFormField return (
tooltip={ <>
<span> <UrlFormField tooltip={projectHelpStrings.svnSourceControlUrl} />
{t`Example URLs for Subversion Source Control include:`} <BranchFormField label={t`Revision #`} />
<ul css={{ margin: '10px 0 10px 20px' }}> <ScmCredentialFormField
<li> credential={credential}
<code>https://github.com/ansible/ansible</code> onCredentialSelection={onCredentialSelection}
</li> />
<li> <ScmTypeOptions scmUpdateOnLaunch={scmUpdateOnLaunch} />
<code>svn://servername.example.com/path</code> </>
</li> );
<li> };
<code>svn+ssh://servername.example.com/path</code>
</li>
</ul>
</span>
}
/>
<BranchFormField label={t`Revision #`} />
<ScmCredentialFormField
credential={credential}
onCredentialSelection={onCredentialSelection}
/>
<ScmTypeOptions scmUpdateOnLaunch={scmUpdateOnLaunch} />
</>
);
export default SvnSubForm; export default SvnSubForm;

View File

@@ -11,6 +11,7 @@ import useRequest, { useDismissableError } from 'hooks/useRequest';
import AlertModal from 'components/AlertModal'; import AlertModal from 'components/AlertModal';
import ErrorDetail from 'components/ErrorDetail'; import ErrorDetail from 'components/ErrorDetail';
import { ProjectsAPI } from 'api'; import { ProjectsAPI } from 'api';
import ProjectHelpTextStrings from './Project.helptext';
function ProjectSyncButton({ projectId, lastJobStatus = null }) { function ProjectSyncButton({ projectId, lastJobStatus = null }) {
const match = useRouteMatch(); const match = useRouteMatch();
@@ -21,7 +22,7 @@ function ProjectSyncButton({ projectId, lastJobStatus = null }) {
}, [projectId]), }, [projectId]),
null null
); );
const projectHelpStrings = ProjectHelpTextStrings();
const { error, dismissError } = useDismissableError(syncError); const { error, dismissError } = useDismissableError(syncError);
const isDetailsView = match.url.endsWith('/details'); const isDetailsView = match.url.endsWith('/details');
const isDisabled = ['pending', 'waiting', 'running'].includes(lastJobStatus); const isDisabled = ['pending', 'waiting', 'running'].includes(lastJobStatus);
@@ -29,10 +30,7 @@ function ProjectSyncButton({ projectId, lastJobStatus = null }) {
return ( return (
<> <>
{isDisabled ? ( {isDisabled ? (
<Tooltip <Tooltip content={projectHelpStrings.syncButtonDisabled} position="top">
content={t`This project is currently on sync and cannot be clicked until sync process completed`}
position="top"
>
<div> <div>
<Button <Button
ouiaId={`${projectId}-sync-button`} ouiaId={`${projectId}-sync-button`}

View File

@@ -6,7 +6,12 @@ import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import ProjectSyncButton from './ProjectSyncButton'; import ProjectSyncButton from './ProjectSyncButton';
jest.mock('../../../api'); jest.mock('../../../api');
jest.mock('hooks/useBrandName', () => ({
__esModule: true,
default: () => ({
current: 'AWX',
}),
}));
describe('ProjectSyncButton', () => { describe('ProjectSyncButton', () => {
let wrapper; let wrapper;