Skip to content

Commit

Permalink
US#343056 Support ExD in CFC
Browse files Browse the repository at this point in the history
* Run rules when mutipleentities custom field reset.
  So mutipleentities should also work.
* Fix running rules for non-process dependent entities,
  like user, team, etc.
  • Loading branch information
tsarevichdm committed Feb 11, 2021
1 parent fb570cf commit f314fa5
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 101 deletions.
2 changes: 2 additions & 0 deletions make-webpack-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ const makeWebpackConfig = (opts_) => {
'tau/components/component.page.base',
'tau/core/class',
'tau/core/extension.base',
'tau/api/internal/store/types',
'tau/core/bus.reg',
'tp3/mashups/storage',
'tp3/api/featureToggling/v1',
'tau/core/templates-factory',
'tau/core/view-base',
'tau/services/service.customFields.cached',
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "CustomFieldConstraints",
"version": "1.4.13",
"version": "1.5.0",
"description": "This mashup allows a custom field to be required when an entity is moved to a specific state or a specific value is selected in another custom field.",
"author": "Aliaksei Shytkin <[email protected]>",
"scripts": {
Expand Down
62 changes: 35 additions & 27 deletions src/screens/Form/components/TargetprocessFinder.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
import React, {PropTypes as T} from 'react';
import {noop} from 'underscore';
import {isEnabled} from 'tp3/api/featureToggling/v1';
import tauTypes from 'tau/api/internal/store/types';

import TargetprocessComponent from 'components/TargetprocessComponent';

const getDefaultEntityTypeNamesToFilterBy = () => {

// Same as in mode.finder.entity.data processOptions.
const extendableGeneralNamesSorted = tauTypes.getAll()
.filter((t) => t.isExtendableDomainType && t.isGeneral)
.map((t) => t.name.toLowerCase())
.sort();

return [
'project',
'program',
'release',
...(isEnabled('hideProjectIterations') ? [] : ['iteration']),
'teamiteration',
'testcase',
'testplan',
'build',
'impediment',
'portfolioepic',
'epic',
'feature',
'userstory',
'task',
'bug',
'testplanrun',
'request',
...extendableGeneralNamesSorted
];

};

export default class TargetprocessFinder extends React.Component {

static propTypes = {
Expand All @@ -27,34 +60,15 @@ export default class TargetprocessFinder extends React.Component {

static defaultProps = {
filterDsl: void 0,
filterEntityTypeName: [
'project',
'program',
'release',
'iteration',
'teamiteration',
'testcase',
'testplan',
'build',
'impediment',

'portfolioepic',
'epic',
'feature',
'userstory',
'task',
'bug',
'testplanrun',
'request'
],
filterEntityTypeName: getDefaultEntityTypeNamesToFilterBy(),
filterFields: {},
onAdjust: noop,
onSelect: noop
};

render() {

const {entity, customField, filterEntityTypeName, filterDsl, filterFields} = this.props;
const {entity, filterEntityTypeName, filterDsl, filterFields} = this.props;

const config = {
entityType: null,
Expand All @@ -71,12 +85,6 @@ export default class TargetprocessFinder extends React.Component {

}

if (customField) {

config.customField = customField;

}

const context = entity ? {entity} : null;

return (
Expand Down
8 changes: 7 additions & 1 deletion src/shared/services/CustomFieldValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ export const fromInputValue = (customField, inputValue) => {

};

// TP can send null when we uncheck checkbox custom field, override to correct value false.
// TP can send null or false when we uncheck checkbox custom field, override to correct value false.
// Can't normalize multipleentities here as type for it is not send.
export const getCustomFieldValue = (customField) => equalIgnoreCase(customField.type, 'checkbox')
? Boolean(customField.value) : customField.value;

// When custom field is removed (false for checkbox, null for all except multipleentities - empty string)
// then should run rules both for it and its dependent custom fields.
export const checkDependentCustomFields = (targetValue) => targetValue === null ||
isEmptyCheckboxValue(targetValue) || targetValue === '';
31 changes: 30 additions & 1 deletion src/shared/services/__tests__/CustomFieldValue.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {isEmptyCheckboxValue, fromServerValue, getCustomFieldValue} from 'services/CustomFieldValue';
import {isEmptyCheckboxValue, fromServerValue, getCustomFieldValue,
checkDependentCustomFields} from 'services/CustomFieldValue';
import dateUtils from 'tau/utils/utils.date';

describe('CustomFieldValue', () => {
Expand Down Expand Up @@ -315,4 +316,32 @@ describe('CustomFieldValue', () => {

});

describe('checkDependentCustomFields()', () => {

it('checks dependent custom fields rules are needed', () => {

expect(checkDependentCustomFields(null),
'should need dependents when custom field reset').to.be.true;
expect(checkDependentCustomFields(false),
'should need dependents when checkbox custom field reset').to.be.true;
expect(checkDependentCustomFields(''),
'should need dependents when multipleentities custom field reset').to.be.true;

expect(checkDependentCustomFields(0),
'should not need dependents when number custom field set to 0').to.be.false;
expect(checkDependentCustomFields(12),
'should not need dependents when number custom field set to non-0').to.be.false;
expect(checkDependentCustomFields('some text'),
'should not need dependents when text custom field set').to.be.false;
expect(checkDependentCustomFields(true),
'should not need dependents when checkbox custom field checked').to.be.false;
expect(checkDependentCustomFields({id: 1234, kind: 'Epic', name: 'Epic #1'}),
'should not need dependents when entity custom field set').to.be.false;
expect(checkDependentCustomFields('9811 epic, 5341 userstory'),
'should not need dependents when multipleentities custom field set').to.be.false;

});

});

});
71 changes: 63 additions & 8 deletions src/shared/services/__tests__/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,63 @@ import $, {when} from 'jquery';

$.whenList = (arr) => $.when(...arr);

const createSandbox = () => {

return {
obj: null,
propName: null,
propValue: null,
hasOwnProp: false,

stub(obj, prop) {

if (obj !== null && obj !== void 0) {

this.obj = obj;
this.propName = prop;
this.propValue = obj[prop];
this.hasOwnProp = Object.getOwnPropertyNames(obj).includes(prop);

const me = this; // eslint-disable-line consistent-this

return {

value(v) {

me.obj[prop] = v;

}

};

}

throw new Error('object is null or undefined');

},

restore() {

if (this.hasOwnProp) {

this.obj[this.propName] = this.propValue;

} else {

delete this.obj[this.propName];

}

}

};

};

describe('axes', () => {

let $ajax;
let windowSandbox;

const entity = {
id: 123,
Expand All @@ -18,18 +72,19 @@ describe('axes', () => {

$ajax = sinon.stub($, 'ajax');

windowSandbox = createSandbox();
windowSandbox.stub(window, 'tauFeatures').value({
systemCustomFields: false,
hideProjectIterations: false
});

});

afterEach(() => {

$ajax.restore();
getCustomFieldsForAxes.resetCache();

if (window.tauFeatures) {

delete window.tauFeatures;

}
windowSandbox.restore();

});

Expand Down Expand Up @@ -676,7 +731,7 @@ describe('axes', () => {

it('skips system custom fields if feature is enabled', () => {

window.tauFeatures = {systemCustomFields: true};
window.tauFeatures.systemCustomFields = true;

$ajax.onCall(0).returns(when({
items: [{
Expand Down Expand Up @@ -708,7 +763,7 @@ describe('axes', () => {

it('returns system custom fields if feature is disabled', () => {

window.tauFeatures = {systemCustomFields: false};
window.tauFeatures.systemCustomFields = false;

$ajax.onCall(0).returns(when({
items: [{
Expand Down
4 changes: 1 addition & 3 deletions src/shared/services/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
getCustomFieldsNamesForChangedCustomFields,
getCustomFieldsNamesForChangedCustomFieldsWithDependent
} from 'services/customFieldsRequirements';
import * as CustomFieldValue from 'services/CustomFieldValue';

const findInRealCustomFields = (customFieldsNames, realCustomFields) =>
customFieldsNames.reduce((res, v) => {
Expand Down Expand Up @@ -230,8 +229,7 @@ const getCustomFieldsForAxis = (config, axis, processes, entity, values = {}, op

if (axis.type === 'customfield') {

if (axis.checkDependent && (targetValue === null ||
CustomFieldValue.isEmptyCheckboxValue(targetValue))) {
if (axis.checkDependent) {

return getCustomFieldsNamesForChangedCustomFieldsWithDependent([realTargetValue.name],
entity.entityState ? entity.entityState : null, config, process, entity.entityType.name,
Expand Down
20 changes: 13 additions & 7 deletions src/shared/services/interrupt/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {equalIgnoreCase, isStateRelated, lc} from 'utils';

import {createInterrupter} from './base';
import {createRequirementsByTasks} from './requirementsByTasks';
import {getCustomFieldValue} from 'services/CustomFieldValue';
import {getCustomFieldValue, checkDependentCustomFields} from 'services/CustomFieldValue';

const getEntityFromChange = (sourceChange, changeValues) => {

Expand All @@ -22,12 +22,18 @@ const getEntityFromChange = (sourceChange, changeValues) => {

if (equalIgnoreCase(v.name, 'customfields')) {

return res.concat(v.value.map((vv) => ({
type: 'customfield',
customFieldName: vv.name,
targetValue: getCustomFieldValue(vv),
checkDependent: true
})));
return res.concat(v.value.map((vv) => {

const targetValue = getCustomFieldValue(vv);

return {
type: 'customfield',
customFieldName: vv.name,
targetValue,
checkDependent: checkDependentCustomFields(targetValue)
};

}));

}

Expand Down
38 changes: 30 additions & 8 deletions src/shared/services/loaders.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {filter, memoize, pluck, reject, isString} from 'underscore';
import {isEnabled} from 'tp3/api/featureToggling/v1';

import {isGeneral} from 'utils';
import store from 'services/store';
import store2 from 'services/store2';

const systemCustomFieldsEnabled = () => window.tauFeatures && window.tauFeatures.systemCustomFields;
const systemCustomFieldsEnabled = () => isEnabled('systemCustomFields');

export const getCustomFields = memoize((processId, entityType) => {

Expand Down Expand Up @@ -100,7 +101,21 @@ export const preloadParentEntityStates = (processes) => {

return cache;

}) : [];
}) : store2.get('EntityState', {
where: 'parentEntityState == null and process.id == null',
select: '{id,name,isInitial,isFinal,isDefaultFinal,isPlanned,' +
'workflow:{workflow.id,process:{id:null}},' +
'entityType:{entityType.name},subEntityStates:subEntityStates.Select(' +
'{id,name,entityType:{entityType.name},isInitial,isFinal,isDefaultFinal,isPlanned})}'
}).then((entityStates) => {

const cache = preloadParentEntityStates.cache = preloadParentEntityStates.cache || [];

cache.null = entityStates;

return cache;

});

};

Expand All @@ -121,12 +136,19 @@ preloadParentEntityStates.getStates = (processId) => {

export const loadSingleParentEntityState = memoize(({filter: whereFilter, field}, processId, entityType) => {

return store2.get('EntityState', {
where: `${field} == ${isString(whereFilter) ? `'${whereFilter}'` : whereFilter} ` +
`and workflow.process.id in [${processId}] and entityType.name == '${entityType.name}' ` +
`and parentEntityState != null`,
select: `{parentEntityState.${field}}`
}).then(([parentEntityState]) => parentEntityState && parentEntityState[field] || null);
return (processId !== null ?
store2.get('EntityState', {
where: `${field} == ${isString(whereFilter) ? `'${whereFilter}'` : whereFilter} ` +
`and workflow.process.id in [${processId}] and entityType.name == '${entityType.name}' ` +
`and parentEntityState != null`,
select: `{parentEntityState.${field}}`
}) : store2.get('EntityState', {
where: `${field} == ${isString(whereFilter) ? `'${whereFilter}'` : whereFilter} ` +
`and entityType.name=='${entityType.name}' ` +
`and parentEntityState != null`,
select: `{parentEntityState.${field}}`
}))
.then(([parentEntityState]) => parentEntityState && parentEntityState[field] || null);

}, ({filter: whereFilter, field}, processId, entityType) =>
`${isString(whereFilter) ? `'${whereFilter}'` : whereFilter}:${field}:${processId}:${entityType.name}`);
Expand Down
Loading

0 comments on commit f314fa5

Please sign in to comment.