Skip to content

Commit

Permalink
restrict multiple selections of a revision in traffic modal
Browse files Browse the repository at this point in the history
  • Loading branch information
nemesis09 committed Aug 13, 2020
1 parent 9087721 commit 80b1922
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 29 deletions.
Expand Up @@ -31,6 +31,7 @@ import {
} from '../../utils/traffic-splitting-utils';
import { TrafficSplittingType } from '../traffic-splitting/TrafficSplitting';
import DeleteRevisionModal from './DeleteRevisionModal';
import { Traffic } from '../../types';

type ControllerProps = {
loaded?: boolean;
Expand Down Expand Up @@ -90,13 +91,18 @@ const Controller: React.FC<ControllerProps> = ({ loaded, resources, revision, ca
const deleteTraffic = traffic.find((t) => t.revisionName === revision.metadata.name);

const initialValues: TrafficSplittingType = {
trafficSplitting: traffic.reduce((acc, t) => {
trafficSplitting: traffic.reduce((acc: Traffic[], t) => {
if (!t.revisionName || revisions.find((r) => r.metadata.name === t.revisionName)) {
acc.push({
percent: t.percent,
tag: t.tag || '',
revisionName: t.revisionName || '',
});
const trafficIndex = acc.findIndex((val) => val.revisionName === t.revisionName);
if (trafficIndex >= 0) {
acc[trafficIndex].percent += t.percent;
} else {
acc.push({
percent: t.percent,
tag: t.tag || '',
revisionName: t.revisionName || '',
});
}
}
return acc;
}, []),
Expand Down
@@ -0,0 +1,33 @@
import * as React from 'react';
import { useField } from 'formik';
import { DropdownField } from '@console/shared';
import { RevisionItems } from '../../utils/traffic-splitting-utils';

type TrafficModalRevisionsDropdownFieldProps = {
revisionItems: RevisionItems;
name: string;
title: string;
};

const TrafficModalRevisionsDropdownField: React.FC<TrafficModalRevisionsDropdownFieldProps> = ({
revisionItems,
name,
title,
}) => {
const [field] = useField(name);
const dropdownItems =
!field.value || Object.values(revisionItems).includes(field.value)
? revisionItems
: { ...revisionItems, [field.value]: field.value };
return (
<DropdownField
name={name}
items={dropdownItems}
title={field.value || title}
fullWidth
required
/>
);
};

export default TrafficModalRevisionsDropdownField;
Expand Up @@ -4,6 +4,7 @@ import { K8sResourceKind, k8sUpdate } from '@console/internal/module/k8s';
import { ServiceModel } from '../../models';
import { getRevisionItems, constructObjForUpdate } from '../../utils/traffic-splitting-utils';
import TrafficSplittingModal from './TrafficSplittingModal';
import { Traffic } from '../../types';

export interface TrafficSplittingProps {
service: K8sResourceKind;
Expand All @@ -13,11 +14,7 @@ export interface TrafficSplittingProps {
}

export interface TrafficSplittingType {
trafficSplitting: {
percent: number;
tag: string;
revisionName: string;
}[];
trafficSplitting: Traffic[];
}

const TrafficSplitting: React.FC<TrafficSplittingProps> = ({
Expand All @@ -26,16 +23,25 @@ const TrafficSplitting: React.FC<TrafficSplittingProps> = ({
cancel,
close,
}) => {
const traffic = service.spec?.traffic ?? [{ percent: 0, tag: '', revisionName: '' }];
const traffic: Traffic[] = service.spec?.traffic ?? [{ percent: 0, tag: '', revisionName: '' }];
const latestCreatedRevName = service.status?.latestCreatedRevisionName;
const revisionItems = getRevisionItems(revisions);
const initialValues: TrafficSplittingType = {
trafficSplitting: traffic.map((t) => ({
percent: t.percent,
tag: t.tag || '',
revisionName:
t.revisionName || (t.latestRevision && latestCreatedRevName ? latestCreatedRevName : ''),
})),
trafficSplitting: traffic.reduce((acc: Traffic[], currentValue) => {
const trafficIndex = acc.findIndex((val) => val.revisionName === currentValue.revisionName);
if (trafficIndex >= 0) {
acc[trafficIndex].percent += currentValue.percent;
} else {
acc.push({
percent: currentValue.percent,
tag: currentValue.tag || '',
revisionName:
currentValue.revisionName ||
(currentValue.latestRevision && latestCreatedRevName ? latestCreatedRevName : ''),
});
}
return acc;
}, []),
};
const handleSubmit = (values: FormikValues, action: FormikHelpers<FormikValues>) => {
const obj = constructObjForUpdate(values.trafficSplitting, service);
Expand Down
@@ -1,8 +1,10 @@
import * as React from 'react';
import { FormikProps, FormikValues } from 'formik';
import { pickBy, size } from 'lodash';
import { TextInputTypes } from '@patternfly/react-core';
import { MultiColumnField, InputField, DropdownField } from '@console/shared';
import { MultiColumnField, InputField } from '@console/shared';
import { RevisionItems } from '../../utils/traffic-splitting-utils';
import TrafficModalRevisionsDropdownField from './TrafficModalRevisionsDropdownField';

interface TrafficSplittingFieldProps {
revisionItems: RevisionItems;
Expand All @@ -11,12 +13,17 @@ interface TrafficSplittingFieldProps {
type Props = FormikProps<FormikValues> & TrafficSplittingFieldProps;

const TrafficSplittingFields: React.FC<Props> = ({ revisionItems, values }) => {
const selectedRevisions: string[] = values.trafficSplitting.map(
(traffic) => traffic.revisionName,
);
const items = pickBy(revisionItems, (revisionItem) => !selectedRevisions.includes(revisionItem));
return (
<MultiColumnField
name="trafficSplitting"
headers={[{ name: 'Split', required: true }, 'Tag', { name: 'Revision', required: true }]}
emptyValues={{ percent: '', tag: '', revisionName: '' }}
disableDeleteRow={values.trafficSplitting.length === 1}
disableAddRow={values.trafficSplitting.length === size(revisionItems)}
spans={[2, 3, 7]}
>
<InputField
Expand All @@ -26,12 +33,10 @@ const TrafficSplittingFields: React.FC<Props> = ({ revisionItems, values }) => {
required
/>
<InputField name="tag" type={TextInputTypes.text} />
<DropdownField
<TrafficModalRevisionsDropdownField
name="revisionName"
items={revisionItems}
revisionItems={items}
title="Select a revision"
fullWidth
required
/>
</MultiColumnField>
);
Expand Down
@@ -0,0 +1,37 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { DropdownField } from '@console/shared';
import { formikFormProps } from '@console/shared/src/test-utils/formik-props-utils';
import TrafficModalRevisionsDropdownField from '../TrafficModalRevisionsDropdownField';

const props = {
...formikFormProps,
revisionItems: {
'overlayimage-bwpxq': 'overlayimage-bwpxq',
'overlayimage-n2b7n': 'overlayimage-n2b7n',
},
};

jest.mock('formik', () => ({
useField: jest.fn(() => [{ value: 'overlayimage-tkvz5' }, {}]),
}));

describe('TrafficModalRevisionsDropdownField', () => {
it('should include the current value of the field in the dropdown items', () => {
const wrapper = shallow(
<TrafficModalRevisionsDropdownField {...props} name="revisionName" title="Select Revision" />,
);
expect(
wrapper
.find(DropdownField)
.first()
.props().items,
).toHaveProperty('overlayimage-tkvz5', 'overlayimage-tkvz5');
expect(
wrapper
.find(DropdownField)
.first()
.props().title,
).toBe('overlayimage-tkvz5');
});
});
Expand Up @@ -7,6 +7,7 @@ import {
mockRevisionItems,
} from '../../../utils/__mocks__/traffic-splitting-utils-mock';
import TrafficSplittingFields from '../TrafficSplittingFields';
import TrafficModalRevisionsDropdownField from '../TrafficModalRevisionsDropdownField';

const formProps = {
...formikFormProps,
Expand All @@ -16,11 +17,10 @@ const formProps = {
};

describe('TrafficSplittingFields', () => {
it('should disable delete row button for one value', () => {
it('should disable delete row button but not add row button for one value', () => {
const wrapper = shallow(
<TrafficSplittingFields
{...formProps}
revisionItems={{ 'overlayimage-fdqsf': 'overlayimage-fdqsf' }}
values={{ trafficSplitting: [{ percent: 100, revisionName: 'overlayimage-fdqsf' }] }}
/>,
);
Expand All @@ -30,15 +30,62 @@ describe('TrafficSplittingFields', () => {
.first()
.props().disableDeleteRow,
).toBe(true);
expect(
wrapper
.find(MultiColumnField)
.first()
.props().disableAddRow,
).toBe(false);
});

it('should not disable delete row button for more than one values', () => {
const wrapper = shallow(<TrafficSplittingFields {...formProps} />);
it('should not disable delete row button or add row button if number of values is more than one but less than total number of revisions', () => {
const wrapper = shallow(
<TrafficSplittingFields
{...formProps}
values={{
trafficSplitting: [
{ percent: 50, tag: 'tag-1', revisionName: 'overlayimage-fdqsf' },
{ percent: 50, tag: 'tag-2', revisionName: 'overlayimage-tkvz5' },
],
}}
/>,
);
expect(
wrapper
.find(MultiColumnField)
.first()
.props().disableDeleteRow,
).toBe(false);
expect(
wrapper
.find(MultiColumnField)
.first()
.props().disableAddRow,
).toBe(false);
});

it('should disable add button when no. of revisionName fields equals number of revisions', () => {
const wrapper = shallow(<TrafficSplittingFields {...formProps} />);
expect(
wrapper
.find(MultiColumnField)
.first()
.props().disableAddRow,
).toBe(true);
});

it('should exclude the revisions present in values from dropdown items', () => {
const wrapper = shallow(
<TrafficSplittingFields
{...formProps}
values={{ trafficSplitting: [{ percent: 100, revisionName: 'overlayimage-fdqsf' }] }}
/>,
);
expect(
wrapper
.find(TrafficModalRevisionsDropdownField)
.first()
.props().revisionItems['overlayimage-fdqsf'],
).toBe(undefined);
});
});
Expand Up @@ -3,11 +3,11 @@ import {
knativeServiceObj,
revisionObj,
} from '../../topology/__tests__/topology-knative-test-data';
import { RevisionKind, ServiceKind as knativeServiceKind } from '../../types';
import { RevisionKind, ServiceKind as knativeServiceKind, Traffic } from '../../types';

export const mockServiceData: knativeServiceKind = _.cloneDeep(knativeServiceObj);

export const mockTrafficData = [
export const mockTrafficData: Traffic[] = [
{ percent: 25, tag: 'tag-1', revisionName: 'overlayimage-fdqsf' },
{ percent: 25, tag: 'tag-2', revisionName: 'overlayimage-tkvz5' },
{ percent: 25, tag: 'tag-3', revisionName: 'overlayimage-bwpxq' },
Expand Down

0 comments on commit 80b1922

Please sign in to comment.