diff --git a/awx/ui_next/.eslintrc b/awx/ui_next/.eslintrc index b7c86c305a..350f50379b 100644 --- a/awx/ui_next/.eslintrc +++ b/awx/ui_next/.eslintrc @@ -78,7 +78,8 @@ "src", "theme", "gridColumns", - "rows" + "rows", + "href" ], "ignore": ["Ansible", "Tower", "JSON", "YAML", "lg"], "ignoreComponent": [ diff --git a/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx index 1238665601..bd8abe0849 100644 --- a/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx +++ b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx @@ -22,6 +22,8 @@ import { } from '@patternfly/react-icons'; import { WorkflowApprovalsAPI } from '../../api'; import useRequest from '../../util/useRequest'; +import getDocsBaseUrl from '../../util/getDocsBaseUrl'; +import { useConfig } from '../../contexts/Config'; import useWsPendingApprovalCount from './useWsPendingApprovalCount'; const PendingWorkflowApprovals = styled.div` @@ -35,9 +37,6 @@ const PendingWorkflowApprovalBadge = styled(Badge)` margin-left: 10px; `; -const DOCLINK = - 'https://docs.ansible.com/ansible-tower/latest/html/userguide/index.html'; - function PageHeaderToolbar({ isAboutDisabled, onAboutClick, @@ -47,6 +46,7 @@ function PageHeaderToolbar({ }) { const [isHelpOpen, setIsHelpOpen] = useState(false); const [isUserOpen, setIsUserOpen] = useState(false); + const config = useConfig(); const { request: fetchPendingApprovalCount, @@ -101,37 +101,39 @@ function PageHeaderToolbar({ - {i18n._(t`Info`)}}> - - - - - } - dropdownItems={[ - - {i18n._(t`Help`)} - , - - {i18n._(t`About`)} - , - ]} - /> - - + + + + + } + dropdownItems={[ + + {i18n._(t`Help`)} + , + + {i18n._(t`About`)} + , + ]} + /> + {i18n._(t`User`)}}> { // keeps page from fully reloading @@ -262,6 +266,19 @@ function AdvancedSearch({ + + + ); } diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.jsx index 7b3f4b1d00..8729445851 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.jsx @@ -12,6 +12,8 @@ import { HostFilterField, } from './SharedFields'; import { required } from '../../../../util/validators'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../contexts/Config'; const AzureSubForm = ({ autoPopulateCredential, i18n }) => { const { setFieldValue } = useFormikContext(); @@ -19,6 +21,7 @@ const AzureSubForm = ({ autoPopulateCredential, i18n }) => { name: 'credential', validate: required(i18n._(t`Select a value for this field`), i18n), }); + const config = useConfig(); const handleCredentialUpdate = useCallback( value => { @@ -27,8 +30,9 @@ const AzureSubForm = ({ autoPopulateCredential, i18n }) => { [setFieldValue] ); - const pluginLink = - 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins'; + const pluginLink = `${getDocsBaseUrl( + config + )}/html/userguide/inventories.html#inventory-plugins`; const configLink = 'https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_inventory.html'; diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.jsx index d4f41879ba..c5a988fa3d 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.jsx @@ -11,10 +11,13 @@ import { EnabledValueField, HostFilterField, } from './SharedFields'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../contexts/Config'; const EC2SubForm = ({ i18n }) => { const { setFieldValue } = useFormikContext(); const [credentialField] = useField('credential'); + const config = useConfig(); const handleCredentialUpdate = useCallback( value => { @@ -23,8 +26,9 @@ const EC2SubForm = ({ i18n }) => { [setFieldValue] ); - const pluginLink = - 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins'; + const pluginLink = `${getDocsBaseUrl( + config + )}/html/userguide/inventories.html#inventory-plugins`; const configLink = 'https://docs.ansible.com/ansible/latest/collections/amazon/aws/aws_ec2_inventory.html'; diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.jsx index eee39c1d93..793211107a 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.jsx @@ -12,6 +12,8 @@ import { SourceVarsField, } from './SharedFields'; import { required } from '../../../../util/validators'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../contexts/Config'; const GCESubForm = ({ autoPopulateCredential, i18n }) => { const { setFieldValue } = useFormikContext(); @@ -19,6 +21,7 @@ const GCESubForm = ({ autoPopulateCredential, i18n }) => { name: 'credential', validate: required(i18n._(t`Select a value for this field`), i18n), }); + const config = useConfig(); const handleCredentialUpdate = useCallback( value => { @@ -27,8 +30,9 @@ const GCESubForm = ({ autoPopulateCredential, i18n }) => { [setFieldValue] ); - const pluginLink = - 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins'; + const pluginLink = `${getDocsBaseUrl( + config + )}/html/userguide/inventories.html#inventory-plugins`; const configLink = 'https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_inventory.html'; diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.jsx index 7404bfea6e..3d85189b10 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.jsx @@ -12,6 +12,8 @@ import { HostFilterField, } from './SharedFields'; import { required } from '../../../../util/validators'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../contexts/Config'; const OpenStackSubForm = ({ autoPopulateCredential, i18n }) => { const { setFieldValue } = useFormikContext(); @@ -19,6 +21,7 @@ const OpenStackSubForm = ({ autoPopulateCredential, i18n }) => { name: 'credential', validate: required(i18n._(t`Select a value for this field`), i18n), }); + const config = useConfig(); const handleCredentialUpdate = useCallback( value => { @@ -27,8 +30,9 @@ const OpenStackSubForm = ({ autoPopulateCredential, i18n }) => { [setFieldValue] ); - const pluginLink = - 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins'; + const pluginLink = `${getDocsBaseUrl( + config + )}/html/userguide/inventories.html#inventory-plugins`; const configLink = 'https://docs.ansible.com/ansible/latest/collections/openstack/cloud/openstack_inventory.html'; diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.jsx index 90dd5e8e0d..0d27c3b298 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.jsx @@ -12,6 +12,8 @@ import { HostFilterField, } from './SharedFields'; import { required } from '../../../../util/validators'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../contexts/Config'; const SatelliteSubForm = ({ autoPopulateCredential, i18n }) => { const { setFieldValue } = useFormikContext(); @@ -19,6 +21,7 @@ const SatelliteSubForm = ({ autoPopulateCredential, i18n }) => { name: 'credential', validate: required(i18n._(t`Select a value for this field`), i18n), }); + const config = useConfig(); const handleCredentialUpdate = useCallback( value => { @@ -27,8 +30,9 @@ const SatelliteSubForm = ({ autoPopulateCredential, i18n }) => { [setFieldValue] ); - const pluginLink = - 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins'; + const pluginLink = `${getDocsBaseUrl( + config + )}/html/userguide/inventories.html#inventory-plugins`; const configLink = 'https://docs.ansible.com/ansible/latest/collections/theforeman/foreman/foreman_inventory.html'; diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/TowerSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/TowerSubForm.jsx index 7898e05400..dd3f57df41 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/TowerSubForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/TowerSubForm.jsx @@ -12,6 +12,8 @@ import { SourceVarsField, } from './SharedFields'; import { required } from '../../../../util/validators'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../contexts/Config'; const TowerSubForm = ({ autoPopulateCredential, i18n }) => { const { setFieldValue } = useFormikContext(); @@ -19,6 +21,7 @@ const TowerSubForm = ({ autoPopulateCredential, i18n }) => { name: 'credential', validate: required(i18n._(t`Select a value for this field`), i18n), }); + const config = useConfig(); const handleCredentialUpdate = useCallback( value => { @@ -27,8 +30,9 @@ const TowerSubForm = ({ autoPopulateCredential, i18n }) => { [setFieldValue] ); - const pluginLink = - 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins'; + const pluginLink = `${getDocsBaseUrl( + config + )}/html/userguide/inventories.html#inventory-plugins`; const configLink = 'https://docs.ansible.com/ansible/latest/collections/awx/awx/tower_inventory.html'; diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.jsx index 89025d5fd3..f9b0b16eb4 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.jsx @@ -12,6 +12,8 @@ import { HostFilterField, } from './SharedFields'; import { required } from '../../../../util/validators'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../contexts/Config'; const VMwareSubForm = ({ autoPopulateCredential, i18n }) => { const { setFieldValue } = useFormikContext(); @@ -19,6 +21,7 @@ const VMwareSubForm = ({ autoPopulateCredential, i18n }) => { name: 'credential', validate: required(i18n._(t`Select a value for this field`), i18n), }); + const config = useConfig(); const handleCredentialUpdate = useCallback( value => { @@ -27,8 +30,9 @@ const VMwareSubForm = ({ autoPopulateCredential, i18n }) => { [setFieldValue] ); - const pluginLink = - 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins'; + const pluginLink = `${getDocsBaseUrl( + config + )}/html/userguide/inventories.html#inventory-plugins`; const configLink = 'https://docs.ansible.com/ansible/latest/collections/community/vmware/vmware_vm_inventory_inventory.html'; diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.jsx index d338d1554a..e081d9f629 100644 --- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.jsx +++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.jsx @@ -12,6 +12,8 @@ import { SourceVarsField, } from './SharedFields'; import { required } from '../../../../util/validators'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../contexts/Config'; const VirtualizationSubForm = ({ autoPopulateCredential, i18n }) => { const { setFieldValue } = useFormikContext(); @@ -19,6 +21,7 @@ const VirtualizationSubForm = ({ autoPopulateCredential, i18n }) => { name: 'credential', validate: required(i18n._(t`Select a value for this field`), i18n), }); + const config = useConfig(); const handleCredentialUpdate = useCallback( value => { @@ -27,8 +30,9 @@ const VirtualizationSubForm = ({ autoPopulateCredential, i18n }) => { [setFieldValue] ); - const pluginLink = - 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins'; + const pluginLink = `${getDocsBaseUrl( + config + )}/html/userguide/inventories.html#inventory-plugins`; const configLink = 'https://docs.ansible.com/ansible/latest/collections/ovirt/ovirt/ovirt_inventory.html'; diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx index 293986b779..06df7fe3cd 100644 --- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx +++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx @@ -21,7 +21,7 @@ import { ToolbarToggleGroup, Tooltip, } from '@patternfly/react-core'; -import { SearchIcon } from '@patternfly/react-icons'; +import { SearchIcon, QuestionCircleIcon } from '@patternfly/react-icons'; import AlertModal from '../../../components/AlertModal'; import { CardBody as _CardBody } from '../../../components/Card'; @@ -47,6 +47,8 @@ import { removeParams, getQSConfig, } from '../../../util/qs'; +import getDocsBaseUrl from '../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../contexts/Config'; const QS_CONFIG = getQSConfig('job_output', { order_by: 'start_line', @@ -280,6 +282,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) { const jobSocketCounter = useRef(0); const interval = useRef(null); const history = useHistory(); + const config = useConfig(); const [contentError, setContentError] = useState(null); const [cssMap, setCssMap] = useState({}); const [currentlyLoading, setCurrentlyLoading] = useState([]); @@ -730,6 +733,21 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) { ) : ( renderSearchComponent(i18n) )} + + + diff --git a/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx b/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx index fca1b0317d..42fe7a65e2 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx @@ -1,6 +1,6 @@ import 'styled-components/macro'; import React, { useEffect, useRef } from 'react'; -import { Trans, withI18n } from '@lingui/react'; +import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { useField, useFormikContext } from 'formik'; import { Switch, Text } from '@patternfly/react-core'; @@ -9,6 +9,8 @@ import { SubFormLayout, } from '../../../components/FormLayout'; import CodeEditorField from '../../../components/CodeEditor/CodeEditorField'; +import { useConfig } from '../../../contexts/Config'; +import getDocsBaseUrl from '../../../util/getDocsBaseUrl'; function CustomMessagesSubForm({ defaultMessages, type, i18n }) { const [useCustomField, , useCustomHelpers] = useField('useCustomMessages'); @@ -16,6 +18,7 @@ function CustomMessagesSubForm({ defaultMessages, type, i18n }) { const showBodies = ['email', 'pagerduty', 'webhook'].includes(type); const { setFieldValue } = useFormikContext(); + const config = useConfig(); const mountedRef = useRef(null); useEffect( function resetToDefaultMessages() { @@ -69,11 +72,9 @@ function CustomMessagesSubForm({ defaultMessages, type, i18n }) { css="margin-bottom: var(--pf-c-content--MarginBottom)" > - - Use custom messages to change the content of notifications sent - when a job starts, succeeds, or fails. Use curly braces to - access information about the job:{' '} - + {i18n._(t`Use custom messages to change the content of + notifications sent when a job starts, succeeds, or fails. Use + curly braces to access information about the job:`)}{' '} {'{{'} job_friendly_name {'}}'} @@ -81,23 +82,22 @@ function CustomMessagesSubForm({ defaultMessages, type, i18n }) { {'{{'} url {'}}'} - , or attributes of the job such as{' '} + ,{' '} {'{{'} job.status {'}}'} .{' '} - - You may apply a number of possible variables in the message. - Refer to the{' '} - {' '} + {i18n._(t`You may apply a number of possible variables in the + message. For more information, refer to the`)}{' '} - Ansible Tower documentation - {' '} - for more details. + {i18n._(t`Ansible Tower Documentation.`)} + diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx index 32d2d85565..dc90e12df3 100644 --- a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx +++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx @@ -9,90 +9,97 @@ import { ScmCredentialFormField, ScmTypeOptions, } from './SharedFields'; +import { useConfig } from '../../../../contexts/Config'; +import getDocsBaseUrl from '../../../../util/getDocsBaseUrl'; const GitSubForm = ({ i18n, credential, onCredentialSelection, scmUpdateOnLaunch, -}) => ( - <> - - {i18n._(t`Example URLs for GIT Source Control include:`)} -
    -
  • - https://github.com/ansible/ansible.git -
  • -
  • - git@github.com:ansible/ansible.git -
  • -
  • - git://servername.example.com/ansible.git -
  • -
- {i18n._(t`Note: When using SSH protocol for GitHub or +}) => { + const config = useConfig(); + return ( + <> + + {i18n._(t`Example URLs for GIT Source Control include:`)} +
    +
  • + https://github.com/ansible/ansible.git +
  • +
  • + git@github.com:ansible/ansible.git +
  • +
  • + git://servername.example.com/ansible.git +
  • +
+ {i18n._(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.`)} - - } - /> - - - {i18n._(t`A refspec to fetch (passed to the Ansible git + + } + /> + + + {i18n._(t`A refspec to fetch (passed to the Ansible git module). This parameter allows access to references via the branch field not otherwise available.`)} -
-
- {i18n._(t`Note: This field assumes the remote name is "origin".`)} -
-
- {i18n._(t`Examples include:`)} -
    -
  • - refs/*:refs/remotes/origin/* -
  • -
  • - refs/pull/62/head:refs/remotes/origin/pull/62/head -
  • -
- {i18n._(t`The first fetches all references. The second +
+
+ {i18n._(t`Note: This field assumes the remote name is "origin".`)} +
+
+ {i18n._(t`Examples include:`)} +
    +
  • + refs/*:refs/remotes/origin/* +
  • +
  • + refs/pull/62/head:refs/remotes/origin/pull/62/head +
  • +
+ {i18n._(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".`)} -
-
- {i18n._(t`For more information, refer to the`)}{' '} - - {i18n._(t`Ansible Tower Documentation.`)} - - - } - /> - - - -); +
+
+ {i18n._(t`For more information, refer to the`)}{' '} + + {i18n._(t`Ansible Tower Documentation.`)} + + + } + /> + + + + ); +}; export default withI18n()(GitSubForm); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.jsx index 8478305ed4..3ae4894e85 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.jsx @@ -26,6 +26,8 @@ import JobTemplatesList from './JobTemplatesList'; import ProjectsList from './ProjectsList'; import WorkflowJobTemplatesList from './WorkflowJobTemplatesList'; import FormField from '../../../../../../components/FormField'; +import getDocsBaseUrl from '../../../../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../../../../contexts/Config'; const NodeTypeErrorAlert = styled(Alert)` margin-bottom: 20px; @@ -59,6 +61,7 @@ function NodeTypeStep({ i18n }) { const [convergenceField, , convergenceFieldHelpers] = useField('convergence'); const [isConvergenceOpen, setIsConvergenceOpen] = useState(false); + const config = useConfig(); const isValid = !approvalNameMeta.touched || !approvalNameMeta.error; return ( @@ -212,7 +215,9 @@ function NodeTypeStep({ i18n }) { t`Preconditions for running this node when there are multiple parents. Refer to the` )}{' '} diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.jsx index 48b616656c..c0c18e2ab7 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/VisualizerToolbar.jsx @@ -23,6 +23,8 @@ import { WorkflowDispatchContext, WorkflowStateContext, } from '../../../contexts/Workflow'; +import getDocsBaseUrl from '../../../util/getDocsBaseUrl'; +import { useConfig } from '../../../contexts/Config'; const Badge = styled(PFBadge)` align-items: center; @@ -47,9 +49,6 @@ const ActionButton = styled(Button)` `; ActionButton.displayName = 'ActionButton'; -const DOCLINK = - 'https://docs.ansible.com/ansible-tower/latest/html/userguide/workflow_templates.html#ug-wf-editor'; - function VisualizerToolbar({ i18n, onClose, @@ -59,8 +58,8 @@ function VisualizerToolbar({ readOnly, }) { const dispatch = useContext(WorkflowDispatchContext); - const { nodes, showLegend, showTools } = useContext(WorkflowStateContext); + const config = useConfig(); const totalNodes = nodes.reduce((n, node) => n + !node.isDeleted, 0) - 1; @@ -113,7 +112,9 @@ function VisualizerToolbar({ variant="plain" component="a" target="_blank" - href={DOCLINK} + href={`${getDocsBaseUrl( + config + )}/html/userguide/workflow_templates.html#ug-wf-editor`} > diff --git a/awx/ui_next/src/util/getDocsBaseUrl.js b/awx/ui_next/src/util/getDocsBaseUrl.js new file mode 100644 index 0000000000..69347e481c --- /dev/null +++ b/awx/ui_next/src/util/getDocsBaseUrl.js @@ -0,0 +1,8 @@ +export default function getDocsBaseUrl(config) { + let version = 'latest'; + const licenseType = config?.license_info?.license_type; + if (licenseType && licenseType !== 'open') { + version = config?.version ? config.version.split('-')[0] : 'latest'; + } + return `https://docs.ansible.com/ansible-tower/${version}`; +} diff --git a/awx/ui_next/src/util/getDocsBaseUrl.test.js b/awx/ui_next/src/util/getDocsBaseUrl.test.js new file mode 100644 index 0000000000..f2e6a5e8aa --- /dev/null +++ b/awx/ui_next/src/util/getDocsBaseUrl.test.js @@ -0,0 +1,44 @@ +import getDocsBaseUrl from './getDocsBaseUrl'; + +describe('getDocsBaseUrl', () => { + it('should return latest version for open license', () => { + const result = getDocsBaseUrl({ + license_info: { + license_type: 'open', + }, + version: '18.0.0', + }); + + expect(result).toEqual('https://docs.ansible.com/ansible-tower/latest'); + }); + + it('should return current version for enterprise license', () => { + const result = getDocsBaseUrl({ + license_info: { + license_type: 'enterprise', + }, + version: '4.0.0', + }); + + expect(result).toEqual('https://docs.ansible.com/ansible-tower/4.0.0'); + }); + + it('should strip version info after hyphen', () => { + const result = getDocsBaseUrl({ + license_info: { + license_type: 'enterprise', + }, + version: '4.0.0-beta', + }); + + expect(result).toEqual('https://docs.ansible.com/ansible-tower/4.0.0'); + }); + + it('should return latest version if license info missing', () => { + const result = getDocsBaseUrl({ + version: '18.0.0', + }); + + expect(result).toEqual('https://docs.ansible.com/ansible-tower/latest'); + }); +});