Disables template and workflow template fields for users that do not have permissions for those fields.

This commit is contained in:
Alex Corey 2020-08-24 11:18:24 -04:00
parent 9c90804300
commit fc4060778b
7 changed files with 66 additions and 50 deletions

View File

@ -19,7 +19,13 @@ const QS_CONFIG = getQSConfig('inventory', {
function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
const {
result: { inventories, count, relatedSearchableKeys, searchableKeys },
result: {
inventories,
count,
relatedSearchableKeys,
searchableKeys,
canEdit,
},
request: fetchInventories,
error,
isLoading,
@ -39,9 +45,16 @@ function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
canEdit: Boolean(actionsResponse.data.actions.POST),
};
}, [history.location]),
{ inventories: [], count: 0, relatedSearchableKeys: [], searchableKeys: [] }
{
inventories: [],
count: 0,
relatedSearchableKeys: [],
searchableKeys: [],
canEdit: false,
}
);
useEffect(() => {
@ -58,6 +71,7 @@ function InventoryLookup({ value, onChange, onBlur, required, i18n, history }) {
onBlur={onBlur}
required={required}
isLoading={isLoading}
isDisabled={!canEdit}
qsConfig={QS_CONFIG}
renderOptionsList={({ state, dispatch, canDelete }) => (
<OptionsList

View File

@ -27,6 +27,8 @@ import { QSConfig } from '../../types';
const ChipHolder = styled.div`
--pf-c-form-control--Height: auto;
background-color: ${props =>
props.isDisabled ? 'var(--pf-global--disabled-color--300)' : null};
`;
function Lookup(props) {
const {
@ -43,6 +45,7 @@ function Lookup(props) {
renderOptionsList,
history,
i18n,
isDisabled,
} = props;
const [state, dispatch] = useReducer(
@ -103,11 +106,15 @@ function Lookup(props) {
id={id}
onClick={() => dispatch({ type: 'TOGGLE_MODAL' })}
variant={ButtonVariant.control}
isDisabled={isLoading}
isDisabled={isLoading || isDisabled}
>
<SearchIcon />
</Button>
<ChipHolder className="pf-c-form-control">
<ChipHolder
isDisabled={isDisabled}
// css="background-color: #d2d2d2"
className="pf-c-form-control"
>
<ChipGroup numChips={5} totalChips={items.length}>
{items.map(item =>
renderItemChip({

View File

@ -32,7 +32,7 @@ function ProjectLookup({
history,
}) {
const {
result: { projects, count, relatedSearchableKeys, searchableKeys },
result: { projects, count, relatedSearchableKeys, searchableKeys, canEdit },
request: fetchProjects,
error,
isLoading,
@ -55,6 +55,7 @@ function ProjectLookup({
searchableKeys: Object.keys(
actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
canEdit: Boolean(actionsResponse.data.actions.POST),
};
}, [history.location.search, autocomplete]),
{
@ -62,6 +63,7 @@ function ProjectLookup({
projects: [],
relatedSearchableKeys: [],
searchableKeys: [],
canEdit: false,
}
);
@ -87,6 +89,7 @@ function ProjectLookup({
onChange={onChange}
required={required}
isLoading={isLoading}
isDisabled={!canEdit}
qsConfig={QS_CONFIG}
renderOptionsList={({ state, dispatch, canDelete }) => (
<OptionsList

View File

@ -11,12 +11,22 @@ export default function useSyncedSelectValue(value, onChange) {
const [selections, setSelections] = useState([]);
useEffect(() => {
const newOptions = [];
if (value !== selections && options.length) {
const syncedValue = value.map(item =>
options.find(i => i.id === item.id)
);
const syncedValue = value.map(item => {
const match = options.find(i => {
return i.id === item.id;
});
if (!match) {
newOptions.push(item);
}
return match || item;
});
setSelections(syncedValue);
}
if (newOptions.length > 0) {
setOptions(options.concat(newOptions));
}
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [value, options]);
@ -27,7 +37,6 @@ export default function useSyncedSelectValue(value, onChange) {
onChange(selections.concat(item));
}
};
return {
selections: options.length ? addToStringToObjects(selections) : [],
onSelect,

View File

@ -4,13 +4,11 @@ import { withRouter, Redirect } from 'react-router-dom';
import { CardBody } from '../../../components/Card';
import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading';
import { JobTemplatesAPI, ProjectsAPI } from '../../../api';
import { JobTemplatesAPI } from '../../../api';
import { JobTemplate } from '../../../types';
import { getAddedAndRemoved } from '../../../util/lists';
import JobTemplateForm from '../shared/JobTemplateForm';
const loadRelatedProjectPlaybooks = async project =>
ProjectsAPI.readPlaybooks(project);
class JobTemplateEdit extends Component {
static propTypes = {
template: JobTemplate.isRequired,
@ -43,17 +41,8 @@ class JobTemplateEdit extends Component {
}
async loadRelated() {
const {
template: { project },
} = this.props;
this.setState({ contentError: null, hasContentLoading: true });
try {
if (project) {
const { data: playbook = [] } = await loadRelatedProjectPlaybooks(
project
);
this.setState({ relatedProjectPlaybooks: playbook });
}
const [relatedCredentials] = await this.loadRelatedCredentials();
this.setState({
relatedCredentials,

View File

@ -39,7 +39,7 @@ import {
ProjectLookup,
MultiCredentialsLookup,
} from '../../../components/Lookup';
import { JobTemplatesAPI, ProjectsAPI } from '../../../api';
import { JobTemplatesAPI } from '../../../api';
import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect';
import WebhookSubForm from './WebhookSubForm';
@ -100,18 +100,6 @@ function JobTemplateForm({
'webhook_credential'
);
const {
request: fetchProject,
error: projectContentError,
contentLoading: hasProjectLoading,
} = useRequest(
useCallback(async () => {
if (template?.project) {
await ProjectsAPI.readDetail(template?.project);
}
}, [template])
);
const {
request: loadRelatedInstanceGroups,
error: instanceGroupError,
@ -127,10 +115,6 @@ function JobTemplateForm({
}, [setFieldValue, template])
);
useEffect(() => {
fetchProject();
}, [fetchProject]);
useEffect(() => {
loadRelatedInstanceGroups();
}, [loadRelatedInstanceGroups]);
@ -204,16 +188,12 @@ function JobTemplateForm({
callbackUrl = `${origin}${path}`;
}
if (instanceGroupLoading || hasProjectLoading) {
if (instanceGroupLoading) {
return <ContentLoading />;
}
if (contentError || instanceGroupError || projectContentError) {
return (
<ContentError
error={contentError || instanceGroupError || projectContentError}
/>
);
if (contentError || instanceGroupError) {
return <ContentError error={contentError || instanceGroupError} />;
}
return (

View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { number, string, oneOfType } from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
@ -7,6 +7,7 @@ import { ProjectsAPI } from '../../../api';
import useRequest from '../../../util/useRequest';
function PlaybookSelect({ projectId, isValid, field, onBlur, onError, i18n }) {
const [isDisabled, setIsDisabled] = useState(false);
const {
result: options,
request: fetchOptions,
@ -18,6 +19,7 @@ function PlaybookSelect({ projectId, isValid, field, onBlur, onError, i18n }) {
return [];
}
const { data } = await ProjectsAPI.readPlaybooks(projectId);
const opts = (data || []).map(playbook => ({
value: playbook,
key: playbook,
@ -33,7 +35,7 @@ function PlaybookSelect({ projectId, isValid, field, onBlur, onError, i18n }) {
});
return opts;
}, [projectId, i18n]),
[]
[field.value]
);
useEffect(() => {
@ -42,18 +44,30 @@ function PlaybookSelect({ projectId, isValid, field, onBlur, onError, i18n }) {
useEffect(() => {
if (error) {
onError(error);
if (error.response.status === 403) {
setIsDisabled(true);
} else {
onError(error);
}
}
}, [error, onError]);
const isDisabledData = [
{
value: field.value || '',
label: field.value || '',
key: 1,
isDisabled: true,
},
];
return (
<AnsibleSelect
id="template-playbook"
data={options}
data={isDisabled ? isDisabledData : options}
isValid={isValid}
{...field}
onBlur={onBlur}
isDisabled={isLoading}
isDisabled={isLoading || isDisabled}
/>
);
}