mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 18:07:36 -02:30
Merge pull request #9875 from keithjgrant/a11y-fixes
Accessibility fixes SUMMARY Fixes numerous accessibility issues, including: updates CodeEditor so label correctly points at associated textarea fixes issues with tabs on dashboard and details pages adds missings ids adds alt text to logo removes duplicate ids on some lists ISSUE TYPE Bugfix Pull Request COMPONENT NAME UI Reviewed-by: Kersom <None> Reviewed-by: Keith Grant <keithjgrant@gmail.com> Reviewed-by: Jake McDermott <yo@jakemcdermott.me> Reviewed-by: Tiago Góes <tiago.goes2009@gmail.com>
This commit is contained in:
@@ -60,6 +60,7 @@
|
|||||||
"mode",
|
"mode",
|
||||||
"aria-labelledby",
|
"aria-labelledby",
|
||||||
"aria-hidden",
|
"aria-hidden",
|
||||||
|
"aria-controls",
|
||||||
"sortKey",
|
"sortKey",
|
||||||
"ouiaId",
|
"ouiaId",
|
||||||
"credentialTypeNamespace",
|
"credentialTypeNamespace",
|
||||||
|
|||||||
@@ -138,10 +138,13 @@ function AppContainer({ i18n, navRouteConfig = [], children }) {
|
|||||||
}
|
}
|
||||||
}, [handleLogout, timeRemaining]);
|
}, [handleLogout, timeRemaining]);
|
||||||
|
|
||||||
|
const brandName = config?.license_info?.product_name;
|
||||||
|
const alt = brandName ? i18n._(t`${brandName} logo`) : i18n._(t`brand logo`);
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<PageHeader
|
<PageHeader
|
||||||
showNavToggle
|
showNavToggle
|
||||||
logo={<BrandLogo />}
|
logo={<BrandLogo alt={alt} />}
|
||||||
logoProps={{ href: '/' }}
|
logoProps={{ href: '/' }}
|
||||||
headerTools={
|
headerTools={
|
||||||
<PageHeaderToolbar
|
<PageHeaderToolbar
|
||||||
@@ -156,7 +159,7 @@ function AppContainer({ i18n, navRouteConfig = [], children }) {
|
|||||||
|
|
||||||
const simpleHeader = config.isLoading ? null : (
|
const simpleHeader = config.isLoading ? null : (
|
||||||
<PageHeader
|
<PageHeader
|
||||||
logo={<BrandLogo />}
|
logo={<BrandLogo alt={alt} />}
|
||||||
headerTools={
|
headerTools={
|
||||||
<PageHeaderTools>
|
<PageHeaderTools>
|
||||||
<PageHeaderToolsGroup>
|
<PageHeaderToolsGroup>
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const BrandImg = styled.img`
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const BrandLogo = () => <BrandImg src="/static/media/logo-header.svg" />;
|
const BrandLogo = ({ alt }) => (
|
||||||
|
<BrandImg src="/static/media/logo-header.svg" alt={alt} />
|
||||||
|
);
|
||||||
|
|
||||||
export default BrandLogo;
|
export default BrandLogo;
|
||||||
|
|||||||
@@ -96,11 +96,15 @@ function CodeEditor({
|
|||||||
useEffect(
|
useEffect(
|
||||||
function removeTextareaTabIndex() {
|
function removeTextareaTabIndex() {
|
||||||
const editorInput = editor.current.refEditor?.querySelector('textarea');
|
const editorInput = editor.current.refEditor?.querySelector('textarea');
|
||||||
if (editorInput && !readOnly) {
|
if (!editorInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!readOnly) {
|
||||||
editorInput.tabIndex = -1;
|
editorInput.tabIndex = -1;
|
||||||
}
|
}
|
||||||
|
editorInput.id = id;
|
||||||
},
|
},
|
||||||
[readOnly]
|
[readOnly, id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const listen = useCallback(event => {
|
const listen = useCallback(event => {
|
||||||
@@ -144,7 +148,7 @@ function CodeEditor({
|
|||||||
value={value}
|
value={value}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
name={id || 'code-editor'}
|
name={`${id}-editor` || 'code-editor'}
|
||||||
editorProps={{ $blockScrolling: true }}
|
editorProps={{ $blockScrolling: true }}
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
width="100%"
|
width="100%"
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ function VariablesDetail({
|
|||||||
css="grid-column: 1 / -1"
|
css="grid-column: 1 / -1"
|
||||||
>
|
>
|
||||||
<ModeToggle
|
<ModeToggle
|
||||||
|
id={`${dataCy}-preview`}
|
||||||
label={label}
|
label={label}
|
||||||
helpText={helpText}
|
helpText={helpText}
|
||||||
dataCy={dataCy}
|
dataCy={dataCy}
|
||||||
@@ -90,6 +91,7 @@ function VariablesDetail({
|
|||||||
css="grid-column: 1 / -1; margin-top: -20px"
|
css="grid-column: 1 / -1; margin-top: -20px"
|
||||||
>
|
>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
id={`${dataCy}-preview`}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
value={currentValue}
|
value={currentValue}
|
||||||
readOnly
|
readOnly
|
||||||
@@ -125,6 +127,7 @@ function VariablesDetail({
|
|||||||
>
|
>
|
||||||
<div className="pf-c-form">
|
<div className="pf-c-form">
|
||||||
<ModeToggle
|
<ModeToggle
|
||||||
|
id={`${dataCy}-preview-expanded`}
|
||||||
label={label}
|
label={label}
|
||||||
helpText={helpText}
|
helpText={helpText}
|
||||||
dataCy={dataCy}
|
dataCy={dataCy}
|
||||||
@@ -134,6 +137,7 @@ function VariablesDetail({
|
|||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
/>
|
/>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
id={`${dataCy}-preview-expanded`}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
value={currentValue}
|
value={currentValue}
|
||||||
readOnly
|
readOnly
|
||||||
@@ -160,6 +164,7 @@ VariablesDetail.defaultProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function ModeToggle({
|
function ModeToggle({
|
||||||
|
id,
|
||||||
label,
|
label,
|
||||||
helpText,
|
helpText,
|
||||||
dataCy,
|
dataCy,
|
||||||
@@ -173,7 +178,7 @@ function ModeToggle({
|
|||||||
<SplitItem isFilled>
|
<SplitItem isFilled>
|
||||||
<Split hasGutter css="align-items: baseline">
|
<Split hasGutter css="align-items: baseline">
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
<div className="pf-c-form__label">
|
<label className="pf-c-form__label" htmlFor={id}>
|
||||||
<span
|
<span
|
||||||
className="pf-c-form__label-text"
|
className="pf-c-form__label-text"
|
||||||
css="font-weight: var(--pf-global--FontWeight--bold)"
|
css="font-weight: var(--pf-global--FontWeight--bold)"
|
||||||
@@ -183,7 +188,7 @@ function ModeToggle({
|
|||||||
{helpText && (
|
{helpText && (
|
||||||
<Popover header={label} content={helpText} id={dataCy} />
|
<Popover header={label} content={helpText} id={dataCy} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</label>
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
<MultiButtonToggle
|
<MultiButtonToggle
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ describe('<VariablesDetail>', () => {
|
|||||||
expect(input2.prop('value')).toEqual('---foo: bar');
|
expect(input2.prop('value')).toEqual('---foo: bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render label and value= --- when there are no values', () => {
|
test('should render label and value --- when there are no values', () => {
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<VariablesDetail value="" label="Variables" />
|
<VariablesDetail value="" label="Variables" />
|
||||||
);
|
);
|
||||||
expect(wrapper.find('VariablesDetail___StyledCodeEditor').length).toBe(1);
|
expect(wrapper.find('VariablesDetail___StyledCodeEditor').length).toBe(1);
|
||||||
expect(wrapper.find('div.pf-c-form__label').text()).toBe('Variables');
|
expect(wrapper.find('.pf-c-form__label').text()).toBe('Variables');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should update value if prop changes', () => {
|
test('should update value if prop changes', () => {
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ function VariablesFieldInternals({
|
|||||||
<label htmlFor={id} className="pf-c-form__label">
|
<label htmlFor={id} className="pf-c-form__label">
|
||||||
<span className="pf-c-form__label-text">{label}</span>
|
<span className="pf-c-form__label-text">{label}</span>
|
||||||
</label>
|
</label>
|
||||||
{tooltip && <Popover content={tooltip} id={id} />}
|
{tooltip && <Popover content={tooltip} id={`${id}-tooltip`} />}
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
<MultiButtonToggle
|
<MultiButtonToggle
|
||||||
@@ -235,6 +235,7 @@ function VariablesFieldInternals({
|
|||||||
)}
|
)}
|
||||||
</FieldHeader>
|
</FieldHeader>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
|
id={id}
|
||||||
mode={mode}
|
mode={mode}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
{...field}
|
{...field}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ describe('VariablesField', () => {
|
|||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
);
|
);
|
||||||
expect(wrapper.find('Popover[data-cy="the-field"]').length).toBe(1);
|
expect(wrapper.find('Popover[data-cy="the-field-tooltip"]').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should submit value through Formik', async () => {
|
it('should submit value through Formik', async () => {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function FieldWithPrompt({
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
{tooltip && <Popover content={tooltip} id={fieldId} />}
|
{tooltip && <Popover content={tooltip} id={`${fieldId}-tooltip`} />}
|
||||||
</div>
|
</div>
|
||||||
<StyledCheckboxField
|
<StyledCheckboxField
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ describe('FieldWithPrompt', () => {
|
|||||||
</Formik>
|
</Formik>
|
||||||
);
|
);
|
||||||
expect(wrapper.find('.pf-c-form__label-required')).toHaveLength(1);
|
expect(wrapper.find('.pf-c-form__label-required')).toHaveLength(1);
|
||||||
expect(wrapper.find('Popover[data-cy="job-template-limit"]').length).toBe(
|
expect(
|
||||||
1
|
wrapper.find('Popover[data-cy="job-template-limit-tooltip"]').length
|
||||||
);
|
).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { func, string } from 'prop-types';
|
import { func, string } from 'prop-types';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
||||||
import { arrayToString, stringToArray } from '../../util/strings';
|
import { arrayToString, stringToArray } from '../../util/strings';
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ function TagMultiSelect({ onChange, value }) {
|
|||||||
}}
|
}}
|
||||||
selections={selections}
|
selections={selections}
|
||||||
isOpen={isExpanded}
|
isOpen={isExpanded}
|
||||||
aria-labelledby="tag-select"
|
typeAheadAriaLabel={t`Select tags`}
|
||||||
>
|
>
|
||||||
{renderOptions(options)}
|
{renderOptions(options)}
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ function PaginatedTable({
|
|||||||
}
|
}
|
||||||
onSetPage={handleSetPage}
|
onSetPage={handleSetPage}
|
||||||
onPerPageSelect={handleSetPageSize}
|
onPerPageSelect={handleSetPageSize}
|
||||||
|
titles={{
|
||||||
|
paginationTitle: t`Top Pagination`,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { shape, string, number, arrayOf, node, oneOfType } from 'prop-types';
|
|||||||
import { Tab, Tabs, TabTitleText } from '@patternfly/react-core';
|
import { Tab, Tabs, TabTitleText } from '@patternfly/react-core';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
function RoutedTabs(props) {
|
function RoutedTabs({ tabsArray }) {
|
||||||
const { tabsArray } = props;
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@@ -33,12 +32,12 @@ function RoutedTabs(props) {
|
|||||||
<Tabs activeKey={getActiveTabId()} onSelect={handleTabSelect}>
|
<Tabs activeKey={getActiveTabId()} onSelect={handleTabSelect}>
|
||||||
{tabsArray.map(tab => (
|
{tabsArray.map(tab => (
|
||||||
<Tab
|
<Tab
|
||||||
aria-label={typeof tab.name === 'string' ? tab.name : ''}
|
aria-label={typeof tab.name === 'string' ? tab.name : null}
|
||||||
eventKey={tab.id}
|
eventKey={tab.id}
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
link={tab.link}
|
link={tab.link}
|
||||||
title={<TabTitleText>{tab.name}</TabTitleText>}
|
title={<TabTitleText>{tab.name}</TabTitleText>}
|
||||||
role="tab"
|
aria-controls=""
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -50,13 +50,17 @@ describe('ScheduleListItem', () => {
|
|||||||
describe('User has edit permissions', () => {
|
describe('User has edit permissions', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<ScheduleListItem
|
<table>
|
||||||
isSelected={false}
|
<tbody>
|
||||||
onSelect={onSelect}
|
<ScheduleListItem
|
||||||
schedule={mockSchedule}
|
isSelected={false}
|
||||||
isMissingSurvey={false}
|
onSelect={onSelect}
|
||||||
isMissingInventory={false}
|
schedule={mockSchedule}
|
||||||
/>
|
isMissingSurvey={false}
|
||||||
|
isMissingInventory={false}
|
||||||
|
/>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -190,22 +194,26 @@ describe('ScheduleListItem', () => {
|
|||||||
describe('schedule has missing prompt data', () => {
|
describe('schedule has missing prompt data', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<ScheduleListItem
|
<table>
|
||||||
isSelected={false}
|
<tbody>
|
||||||
onSelect={onSelect}
|
<ScheduleListItem
|
||||||
schedule={{
|
isSelected={false}
|
||||||
...mockSchedule,
|
onSelect={onSelect}
|
||||||
summary_fields: {
|
schedule={{
|
||||||
...mockSchedule.summary_fields,
|
...mockSchedule,
|
||||||
user_capabilities: {
|
summary_fields: {
|
||||||
edit: false,
|
...mockSchedule.summary_fields,
|
||||||
delete: false,
|
user_capabilities: {
|
||||||
},
|
edit: false,
|
||||||
},
|
delete: false,
|
||||||
}}
|
},
|
||||||
isMissingInventory="Inventory Error"
|
},
|
||||||
isMissingSurvey="Survey Error"
|
}}
|
||||||
/>
|
isMissingInventory="Inventory Error"
|
||||||
|
isMissingSurvey="Survey Error"
|
||||||
|
/>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ function Search({
|
|||||||
variant={SelectVariant.single}
|
variant={SelectVariant.single}
|
||||||
className="simpleKeySelect"
|
className="simpleKeySelect"
|
||||||
aria-label={i18n._(t`Simple key select`)}
|
aria-label={i18n._(t`Simple key select`)}
|
||||||
|
typeAheadAriaLabel={i18n._(t`Simple key select`)}
|
||||||
onToggle={setIsSearchDropdownOpen}
|
onToggle={setIsSearchDropdownOpen}
|
||||||
onSelect={handleDropdownSelect}
|
onSelect={handleDropdownSelect}
|
||||||
selections={searchColumnName}
|
selections={searchColumnName}
|
||||||
@@ -212,6 +213,7 @@ function Search({
|
|||||||
<Select
|
<Select
|
||||||
variant={SelectVariant.checkbox}
|
variant={SelectVariant.checkbox}
|
||||||
aria-label={name}
|
aria-label={name}
|
||||||
|
typeAheadAriaLabel={name}
|
||||||
onToggle={setIsFilterDropdownOpen}
|
onToggle={setIsFilterDropdownOpen}
|
||||||
onSelect={(event, selection) =>
|
onSelect={(event, selection) =>
|
||||||
handleFilterDropdownSelect(key, event, selection)
|
handleFilterDropdownSelect(key, event, selection)
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ function ActivityStream({ i18n }) {
|
|||||||
maxHeight="480px"
|
maxHeight="480px"
|
||||||
variant={SelectVariant.single}
|
variant={SelectVariant.single}
|
||||||
aria-labelledby="grouped-type-select-id"
|
aria-labelledby="grouped-type-select-id"
|
||||||
|
typeAheadAriaLabel={t`Select an activity type`}
|
||||||
className="activityTypeSelect"
|
className="activityTypeSelect"
|
||||||
onToggle={setIsTypeDropdownOpen}
|
onToggle={setIsTypeDropdownOpen}
|
||||||
onSelect={(event, selection) => {
|
onSelect={(event, selection) => {
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ function CredentialFormFields({ i18n, initialTypeId, credentialTypes }) {
|
|||||||
isDisabled={isCredentialTypeDisabled}
|
isDisabled={isCredentialTypeDisabled}
|
||||||
ouiaId="CredentialForm-credential_type"
|
ouiaId="CredentialForm-credential_type"
|
||||||
aria-label={i18n._(t`Credential Type`)}
|
aria-label={i18n._(t`Credential Type`)}
|
||||||
|
typeAheadAriaLabel={i18n._(t`Select Credential Type`)}
|
||||||
isOpen={isSelectOpen}
|
isOpen={isSelectOpen}
|
||||||
variant={SelectVariant.typeahead}
|
variant={SelectVariant.typeahead}
|
||||||
onToggle={setIsSelectOpen}
|
onToggle={setIsSelectOpen}
|
||||||
@@ -203,7 +204,7 @@ function CredentialFormFields({ i18n, initialTypeId, credentialTypes }) {
|
|||||||
{isCredentialTypeDisabled ? (
|
{isCredentialTypeDisabled ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={i18n._(
|
content={i18n._(
|
||||||
`You cannot change the credential type of a credential,
|
`You cannot change the credential type of a credential,
|
||||||
as it may break the functionality of the resources using it.`
|
as it may break the functionality of the resources using it.`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ function BecomeMethodField({ fieldOptions, isRequired }) {
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
ouiaId={`CredentialForm-${fieldOptions.id}`}
|
ouiaId={`CredentialForm-${fieldOptions.id}`}
|
||||||
|
typeAheadAriaLabel={fieldOptions.label}
|
||||||
maxHeight={200}
|
maxHeight={200}
|
||||||
variant={SelectVariant.typeahead}
|
variant={SelectVariant.typeahead}
|
||||||
onToggle={setIsOpen}
|
onToggle={setIsOpen}
|
||||||
|
|||||||
@@ -170,86 +170,94 @@ function Dashboard({ i18n }) {
|
|||||||
aria-label={i18n._(t`Job status graph tab`)}
|
aria-label={i18n._(t`Job status graph tab`)}
|
||||||
eventKey={0}
|
eventKey={0}
|
||||||
title={<TabTitleText>{i18n._(t`Job status`)}</TabTitleText>}
|
title={<TabTitleText>{i18n._(t`Job status`)}</TabTitleText>}
|
||||||
/>
|
>
|
||||||
|
<Fragment>
|
||||||
|
<GraphCardHeader>
|
||||||
|
<GraphCardActions>
|
||||||
|
<Select
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
placeholderText={i18n._(t`Select period`)}
|
||||||
|
aria-label={i18n._(t`Select period`)}
|
||||||
|
typeAheadAriaLabel={i18n._(t`Select period`)}
|
||||||
|
className="periodSelect"
|
||||||
|
onToggle={setIsPeriodDropdownOpen}
|
||||||
|
onSelect={(event, selection) =>
|
||||||
|
setPeriodSelection(selection)
|
||||||
|
}
|
||||||
|
selections={periodSelection}
|
||||||
|
isOpen={isPeriodDropdownOpen}
|
||||||
|
>
|
||||||
|
<SelectOption key="month" value="month">
|
||||||
|
{i18n._(t`Past month`)}
|
||||||
|
</SelectOption>
|
||||||
|
<SelectOption key="two_weeks" value="two_weeks">
|
||||||
|
{i18n._(t`Past two weeks`)}
|
||||||
|
</SelectOption>
|
||||||
|
<SelectOption key="week" value="week">
|
||||||
|
{i18n._(t`Past week`)}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
placeholderText={i18n._(t`Select job type`)}
|
||||||
|
aria-label={i18n._(t`Select job type`)}
|
||||||
|
className="jobTypeSelect"
|
||||||
|
onToggle={setIsJobTypeDropdownOpen}
|
||||||
|
onSelect={(event, selection) =>
|
||||||
|
setJobTypeSelection(selection)
|
||||||
|
}
|
||||||
|
selections={jobTypeSelection}
|
||||||
|
isOpen={isJobTypeDropdownOpen}
|
||||||
|
>
|
||||||
|
<SelectOption key="all" value="all">
|
||||||
|
{i18n._(t`All job types`)}
|
||||||
|
</SelectOption>
|
||||||
|
<SelectOption key="inv_sync" value="inv_sync">
|
||||||
|
{i18n._(t`Inventory sync`)}
|
||||||
|
</SelectOption>
|
||||||
|
<SelectOption key="scm_update" value="scm_update">
|
||||||
|
{i18n._(t`SCM update`)}
|
||||||
|
</SelectOption>
|
||||||
|
<SelectOption key="playbook_run" value="playbook_run">
|
||||||
|
{i18n._(t`Playbook run`)}
|
||||||
|
</SelectOption>
|
||||||
|
</Select>
|
||||||
|
</GraphCardActions>
|
||||||
|
</GraphCardHeader>
|
||||||
|
<CardBody>
|
||||||
|
<LineChart
|
||||||
|
height={390}
|
||||||
|
id="d3-line-chart-root"
|
||||||
|
data={jobGraphData}
|
||||||
|
/>
|
||||||
|
</CardBody>
|
||||||
|
</Fragment>
|
||||||
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
aria-label={i18n._(t`Recent Jobs list tab`)}
|
aria-label={i18n._(t`Recent Jobs list tab`)}
|
||||||
eventKey={1}
|
eventKey={1}
|
||||||
title={<TabTitleText>{i18n._(t`Recent Jobs`)}</TabTitleText>}
|
title={<TabTitleText>{i18n._(t`Recent Jobs`)}</TabTitleText>}
|
||||||
/>
|
>
|
||||||
|
<div>
|
||||||
|
{activeTabId === 1 && (
|
||||||
|
<JobList defaultParams={{ page_size: 5 }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
aria-label={i18n._(t`Recent Templates list tab`)}
|
aria-label={i18n._(t`Recent Templates list tab`)}
|
||||||
eventKey={2}
|
eventKey={2}
|
||||||
title={
|
title={
|
||||||
<TabTitleText>{i18n._(t`Recent Templates`)}</TabTitleText>
|
<TabTitleText>{i18n._(t`Recent Templates`)}</TabTitleText>
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
<div>
|
||||||
|
{activeTabId === 2 && (
|
||||||
|
<TemplateList defaultParams={{ page_size: 5 }} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{activeTabId === 0 && (
|
|
||||||
<Fragment>
|
|
||||||
<GraphCardHeader>
|
|
||||||
<GraphCardActions>
|
|
||||||
<Select
|
|
||||||
variant={SelectVariant.single}
|
|
||||||
placeholderText={i18n._(t`Select period`)}
|
|
||||||
aria-label={i18n._(t`Select period`)}
|
|
||||||
className="periodSelect"
|
|
||||||
onToggle={setIsPeriodDropdownOpen}
|
|
||||||
onSelect={(event, selection) =>
|
|
||||||
setPeriodSelection(selection)
|
|
||||||
}
|
|
||||||
selections={periodSelection}
|
|
||||||
isOpen={isPeriodDropdownOpen}
|
|
||||||
>
|
|
||||||
<SelectOption key="month" value="month">
|
|
||||||
{i18n._(t`Past month`)}
|
|
||||||
</SelectOption>
|
|
||||||
<SelectOption key="two_weeks" value="two_weeks">
|
|
||||||
{i18n._(t`Past two weeks`)}
|
|
||||||
</SelectOption>
|
|
||||||
<SelectOption key="week" value="week">
|
|
||||||
{i18n._(t`Past week`)}
|
|
||||||
</SelectOption>
|
|
||||||
</Select>
|
|
||||||
<Select
|
|
||||||
variant={SelectVariant.single}
|
|
||||||
placeholderText={i18n._(t`Select job type`)}
|
|
||||||
aria-label={i18n._(t`Select job type`)}
|
|
||||||
className="jobTypeSelect"
|
|
||||||
onToggle={setIsJobTypeDropdownOpen}
|
|
||||||
onSelect={(event, selection) =>
|
|
||||||
setJobTypeSelection(selection)
|
|
||||||
}
|
|
||||||
selections={jobTypeSelection}
|
|
||||||
isOpen={isJobTypeDropdownOpen}
|
|
||||||
>
|
|
||||||
<SelectOption key="all" value="all">
|
|
||||||
{i18n._(t`All job types`)}
|
|
||||||
</SelectOption>
|
|
||||||
<SelectOption key="inv_sync" value="inv_sync">
|
|
||||||
{i18n._(t`Inventory sync`)}
|
|
||||||
</SelectOption>
|
|
||||||
<SelectOption key="scm_update" value="scm_update">
|
|
||||||
{i18n._(t`SCM update`)}
|
|
||||||
</SelectOption>
|
|
||||||
<SelectOption key="playbook_run" value="playbook_run">
|
|
||||||
{i18n._(t`Playbook run`)}
|
|
||||||
</SelectOption>
|
|
||||||
</Select>
|
|
||||||
</GraphCardActions>
|
|
||||||
</GraphCardHeader>
|
|
||||||
<CardBody>
|
|
||||||
<LineChart
|
|
||||||
height={390}
|
|
||||||
id="d3-line-chart-root"
|
|
||||||
data={jobGraphData}
|
|
||||||
/>
|
|
||||||
</CardBody>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
{activeTabId === 1 && <JobList defaultParams={{ page_size: 5 }} />}
|
|
||||||
{activeTabId === 2 && (
|
|
||||||
<TemplateList defaultParams={{ page_size: 5 }} />
|
|
||||||
)}
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</MainPageSection>
|
</MainPageSection>
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ function LineChart({ id, data, height, i18n, pageContext }) {
|
|||||||
.style('fill', () => colors(0))
|
.style('fill', () => colors(0))
|
||||||
.attr('cx', d => x(d.DATE))
|
.attr('cx', d => x(d.DATE))
|
||||||
.attr('cy', d => y(d.FAIL))
|
.attr('cy', d => y(d.FAIL))
|
||||||
.attr('id', d => `success-dot-${dateFormat(d.DATE)}`)
|
.attr('id', d => `fail-dot-${dateFormat(d.DATE)}`)
|
||||||
.on('mouseover', handleMouseOver)
|
.on('mouseover', handleMouseOver)
|
||||||
.on('mousemove', handleMouseMove)
|
.on('mousemove', handleMouseMove)
|
||||||
.on('mouseout', handleMouseOut);
|
.on('mouseout', handleMouseOut);
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ function ExecutionEnvironmentListItem({
|
|||||||
setIsDisabled(false);
|
setIsDisabled(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const labelId = `check-action-${executionEnvironment.id}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr id={`ee-row-${executionEnvironment.id}`}>
|
<Tr id={`ee-row-${executionEnvironment.id}`}>
|
||||||
<Td
|
<Td
|
||||||
@@ -56,15 +54,13 @@ function ExecutionEnvironmentListItem({
|
|||||||
}}
|
}}
|
||||||
dataLabel={i18n._(t`Selected`)}
|
dataLabel={i18n._(t`Selected`)}
|
||||||
/>
|
/>
|
||||||
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
|
<Td id={`ee-name-${executionEnvironment.id}`} dataLabel={i18n._(t`Name`)}>
|
||||||
<Link to={`${detailUrl}`}>
|
<Link to={`${detailUrl}`}>
|
||||||
<b>{executionEnvironment.name}</b>
|
<b>{executionEnvironment.name}</b>
|
||||||
</Link>
|
</Link>
|
||||||
</Td>
|
</Td>
|
||||||
<Td id={labelId} dataLabel={i18n._(t`Image`)}>
|
<Td dataLabel={i18n._(t`Image`)}>{executionEnvironment.image}</Td>
|
||||||
{executionEnvironment.image}
|
<Td dataLabel={i18n._(t`Organization`)}>
|
||||||
</Td>
|
|
||||||
<Td id={labelId} dataLabel={i18n._(t`Organization`)}>
|
|
||||||
{executionEnvironment.organization ? (
|
{executionEnvironment.organization ? (
|
||||||
<Link
|
<Link
|
||||||
to={`/organizations/${executionEnvironment?.summary_fields?.organization?.id}/details`}
|
to={`/organizations/${executionEnvironment?.summary_fields?.organization?.id}/details`}
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ const SCMSubForm = ({ autoPopulateProject, i18n }) => {
|
|||||||
sourcePathHelpers.setValue(value);
|
sourcePathHelpers.setValue(value);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Select source path`)}
|
aria-label={i18n._(t`Select source path`)}
|
||||||
|
typeAheadAriaLabel={i18n._(t`Select source path`)}
|
||||||
placeholder={i18n._(t`Select source path`)}
|
placeholder={i18n._(t`Select source path`)}
|
||||||
createText={i18n._(t`Set source path to`)}
|
createText={i18n._(t`Set source path to`)}
|
||||||
isCreatable
|
isCreatable
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ function ProjectListItem({
|
|||||||
/>
|
/>
|
||||||
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
|
<Td id={labelId} dataLabel={i18n._(t`Name`)}>
|
||||||
<span>
|
<span>
|
||||||
<Link id={labelId} to={`${detailUrl}`}>
|
<Link to={`${detailUrl}`}>
|
||||||
<b>{project.name}</b>
|
<b>{project.name}</b>
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ function SurveyPreviewModal({
|
|||||||
id={`survey-preview-multipleChoice-${q.variable}`}
|
id={`survey-preview-multipleChoice-${q.variable}`}
|
||||||
isDisabled
|
isDisabled
|
||||||
aria-label={i18n._(t`Multiple Choice`)}
|
aria-label={i18n._(t`Multiple Choice`)}
|
||||||
|
typeAheadAriaLabel={i18n._(t`Multiple Choice`)}
|
||||||
placeholderText={q.default}
|
placeholderText={q.default}
|
||||||
onToggle={() => {}}
|
onToggle={() => {}}
|
||||||
/>
|
/>
|
||||||
@@ -109,6 +110,7 @@ function SurveyPreviewModal({
|
|||||||
}
|
}
|
||||||
onToggle={() => {}}
|
onToggle={() => {}}
|
||||||
aria-label={i18n._(t`Multi-Select`)}
|
aria-label={i18n._(t`Multi-Select`)}
|
||||||
|
typeAheadAriaLabel={i18n._(t`Multi-Select`)}
|
||||||
id={`survey-preview-multiSelect-${q.variable}`}
|
id={`survey-preview-multiSelect-${q.variable}`}
|
||||||
>
|
>
|
||||||
{q.choices.length > 0 &&
|
{q.choices.length > 0 &&
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ function NodeTypeStep({ i18n }) {
|
|||||||
setIsConvergenceOpen(false);
|
setIsConvergenceOpen(false);
|
||||||
}}
|
}}
|
||||||
aria-label={i18n._(t`Convergence select`)}
|
aria-label={i18n._(t`Convergence select`)}
|
||||||
|
typeAheadAriaLabel={i18n._(t`Convergence select`)}
|
||||||
className="convergenceSelect"
|
className="convergenceSelect"
|
||||||
ouiaId="convergenceSelect"
|
ouiaId="convergenceSelect"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { func, arrayOf, number, shape, string, oneOfType } from 'prop-types';
|
import { func, arrayOf, number, shape, string, oneOfType } from 'prop-types';
|
||||||
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
import { LabelsAPI } from '../../../api';
|
import { LabelsAPI } from '../../../api';
|
||||||
import { useSyncedSelectValue } from '../../../components/MultiSelect';
|
import { useSyncedSelectValue } from '../../../components/MultiSelect';
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ function LabelSelect({ value, placeholder, onChange, onError, createText }) {
|
|||||||
isDisabled={isLoading}
|
isDisabled={isLoading}
|
||||||
selections={selections}
|
selections={selections}
|
||||||
isOpen={isExpanded}
|
isOpen={isExpanded}
|
||||||
aria-labelledby="label-select"
|
typeAheadAriaLabel={t`Select Labels`}
|
||||||
placeholderText={placeholder}
|
placeholderText={placeholder}
|
||||||
createText={createText}
|
createText={createText}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ function PlaybookSelect({
|
|||||||
selections={selected}
|
selections={selected}
|
||||||
onToggle={setIsOpen}
|
onToggle={setIsOpen}
|
||||||
placeholderText={i18n._(t`Select a playbook`)}
|
placeholderText={i18n._(t`Select a playbook`)}
|
||||||
|
typeAheadAriaLabel={i18n._(t`Select a playbook`)}
|
||||||
isCreateable={false}
|
isCreateable={false}
|
||||||
onSelect={(event, value) => {
|
onSelect={(event, value) => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ function WorkflowJobTemplateForm({
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
<FieldWithPrompt
|
<FieldWithPrompt
|
||||||
fieldId="wjft-limit"
|
fieldId="wfjt-limit"
|
||||||
label={i18n._(t`Limit`)}
|
label={i18n._(t`Limit`)}
|
||||||
promptId="template-ask-limit-on-launch"
|
promptId="template-ask-limit-on-launch"
|
||||||
promptName="ask_limit_on_launch"
|
promptName="ask_limit_on_launch"
|
||||||
@@ -169,7 +169,7 @@ function WorkflowJobTemplateForm({
|
|||||||
documentation for more information and examples on patterns.`)}
|
documentation for more information and examples on patterns.`)}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
id="text-wfjt-limit"
|
id="wfjt-limit"
|
||||||
{...limitField}
|
{...limitField}
|
||||||
validated={
|
validated={
|
||||||
!limitMeta.touched || !limitMeta.error ? 'default' : 'error'
|
!limitMeta.touched || !limitMeta.error ? 'default' : 'error'
|
||||||
@@ -190,7 +190,7 @@ function WorkflowJobTemplateForm({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
id="text-wfjt-scm-branch"
|
id="wfjt-scm-branch"
|
||||||
{...scmField}
|
{...scmField}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
scmHelpers.setValue(value);
|
scmHelpers.setValue(value);
|
||||||
|
|||||||
@@ -226,22 +226,14 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
|
|
||||||
test('test changes in FieldWithPrompt', async () => {
|
test('test changes in FieldWithPrompt', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('TextInputBase#text-wfjt-scm-branch').prop('onChange')(
|
wrapper.find('TextInputBase#wfjt-scm-branch').prop('onChange')('main');
|
||||||
'main'
|
wrapper.find('TextInputBase#wfjt-limit').prop('onChange')(1234567890);
|
||||||
);
|
|
||||||
wrapper.find('TextInputBase#text-wfjt-limit').prop('onChange')(
|
|
||||||
1234567890
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
expect(wrapper.find('input#text-wfjt-scm-branch').prop('value')).toEqual(
|
expect(wrapper.find('input#wfjt-scm-branch').prop('value')).toEqual('main');
|
||||||
'main'
|
expect(wrapper.find('input#wfjt-limit').prop('value')).toEqual(1234567890);
|
||||||
);
|
|
||||||
expect(wrapper.find('input#text-wfjt-limit').prop('value')).toEqual(
|
|
||||||
1234567890
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('webhooks and enable concurrent jobs functions properly', async () => {
|
test('webhooks and enable concurrent jobs functions properly', async () => {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function UserListItem({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Td id={labelId} dataLabel={i18n._(t`Username`)}>
|
<Td id={labelId} dataLabel={i18n._(t`Username`)}>
|
||||||
<Link to={`${detailUrl}`} id={labelId}>
|
<Link to={`${detailUrl}`}>
|
||||||
<b>{user.username}</b>
|
<b>{user.username}</b>
|
||||||
</Link>
|
</Link>
|
||||||
{ldapUser && (
|
{ldapUser && (
|
||||||
|
|||||||
Reference in New Issue
Block a user