[ADF-4673] Add editable property to metadata config (#5557)

* [ADF-4673] Add editable property to metadata config

* Add readonly mode for aspect oriented config

* Fix linting
This commit is contained in:
davidcanonieto
2020-03-20 22:16:35 +00:00
committed by GitHub
parent 94f4b69b76
commit 3fcf965eca
10 changed files with 266 additions and 75 deletions

View File

@@ -22,7 +22,10 @@
"redirectSilentIframeUri": "{protocol}//{hostname}{:port}/assets/silent-refresh.html", "redirectSilentIframeUri": "{protocol}//{hostname}{:port}/assets/silent-refresh.html",
"redirectUri": "/", "redirectUri": "/",
"redirectUriLogout": "/logout", "redirectUriLogout": "/logout",
"publicUrls": ["**/preview/s/*", "**/settings"] "publicUrls": [
"**/preview/s/*",
"**/settings"
]
}, },
"application": { "application": {
"storagePrefix": "ADF", "storagePrefix": "ADF",
@@ -753,7 +756,7 @@
}, },
"adf-cloud-start-process": { "adf-cloud-start-process": {
"name": "My Default Cloud Name" "name": "My Default Cloud Name"
}, },
"adf-process-list": { "adf-process-list": {
"presets": { "presets": {
"default": [ "default": [

View File

@@ -18,6 +18,8 @@ Displays and edits metadata related to a node.
- [Properties](#properties) - [Properties](#properties)
- [Details](#details) - [Details](#details)
- [Application config presets](#application-config-presets) - [Application config presets](#application-config-presets)
- [Indifferent config](#indifferent-config)
- [Aspect oriented config](#aspect-oriented-config)
- [Layout oriented config](#layout-oriented-config) - [Layout oriented config](#layout-oriented-config)
- [Displaying all properties](#displaying-all-properties) - [Displaying all properties](#displaying-all-properties)
- [What happens when there is a whitelisted aspect in the config but the given node doesn't relate to that aspect](#what-happens-when-there-is-a-whitelisted-aspect-in-the-config-but-the-given-node-doesnt-relate-to-that-aspect) - [What happens when there is a whitelisted aspect in the config but the given node doesn't relate to that aspect](#what-happens-when-there-is-a-whitelisted-aspect-in-the-config-but-the-given-node-doesnt-relate-to-that-aspect)
@@ -229,6 +231,31 @@ The result of this config would be two accordion groups with the following prope
| kitten:favourite-food | | kitten:favourite-food |
| kitten:recommended-food | | kitten:recommended-food |
#### Making properties editable
When using the layout oriented config you can also set whether or not the properties are going to be editable.
```json
...
"content-metadata": {
"presets": {
"kitten-images": [{
"title": "TRANSLATABLE_TITLE_FOR_GROUP_1",
"items": [
{
"aspect": "custom:aspect",
"properties": "*",
"editable": false
}
]
}]
}
}
...
```
As seen above in the example the `custom:aspect` aspect will always be on read-only mode since these properties are not editable. If the editable is enabled, then these properties will be able to be edited by the user.
### Displaying all properties ### Displaying all properties
You can list all the properties by simply adding the `includeAll: boolean` to your config. This config will display all the aspects and properties available for that specific file. You can list all the properties by simply adding the `includeAll: boolean` to your config. This config will display all the aspects and properties available for that specific file.
@@ -282,6 +309,24 @@ example below shows this with an aspect-oriented config:
}, },
``` ```
### Making aspects and properties read only
Whenever you have properties that you want to protect from users editing their values you can add them to your configuration to make them read only. `readOnlyAspects` will make the whole aspect and its properties non editable.
If you want to disable the editing for specific properties you will need to add them to the `readOnlyProperties` property.
```json
"content-metadata": {
"presets": {
"default": {
"includeAll": true,
"readOnlyAspects": ["cm:author"],
"readOnlyProperties": ["cm:fileVersion"]
}
}
},
```
## What happens when there is a whitelisted aspect in the config but the given node doesn't relate to that aspect ## What happens when there is a whitelisted aspect in the config but the given node doesn't relate to that aspect
Nothing - since this aspect is not related to the node, it will simply be ignored and not Nothing - since this aspect is not related to the node, it will simply be ignored and not

View File

@@ -21,6 +21,7 @@ export interface LayoutOrientedConfigItem {
properties: string | string[]; properties: string | string[];
includeAll?: boolean; includeAll?: boolean;
exclude?: string | string[]; exclude?: string | string[];
editable?: boolean;
} }
export interface LayoutOrientedConfigLayoutBlock { export interface LayoutOrientedConfigLayoutBlock {

View File

@@ -23,4 +23,5 @@ export interface Property {
defaultValue?: any; defaultValue?: any;
mandatory: boolean; mandatory: boolean;
multiValued: boolean; multiValued: boolean;
editable?: boolean;
} }

View File

@@ -222,5 +222,35 @@ describe('AspectOrientedConfigService', () => {
}); });
}); });
}); });
it(`should set as readOnly the properties defined in the config inside readOnlyAspects`, () => {
const testCase = {
name: 'Not existing property',
config: {
includeAll: true,
readOnlyAspects: ['berseria'],
readOnlyProperties: ['property3']
},
expectations: [
{
title: 'Berseria',
properties: [ property1, property2 ]
},
{
title: 'Zestiria',
properties: [ property3, property4 ]
}
]
};
configService = createConfigService(testCase.config);
const organisedPropertyGroups = configService.appendAllPreset(propertyGroups);
expect(organisedPropertyGroups.length).toBe(testCase.expectations.length, 'Group count should match');
expect(organisedPropertyGroups[0].properties[0].editable).toBe(false);
expect(organisedPropertyGroups[0].properties[1].editable).toBe(false);
expect(organisedPropertyGroups[1].properties[0].editable).toBe(false);
expect(organisedPropertyGroups[1].properties[1].editable).toBe(undefined);
});
}); });
}); });

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { ContentMetadataConfig, OrganisedPropertyGroup, PropertyGroupContainer } from '../../interfaces/content-metadata.interfaces'; import { ContentMetadataConfig, OrganisedPropertyGroup, PropertyGroupContainer, Property } from '../../interfaces/content-metadata.interfaces';
import { getGroup, getProperty } from './property-group-reader'; import { getGroup, getProperty } from './property-group-reader';
export class AspectOrientedConfigService implements ContentMetadataConfig { export class AspectOrientedConfigService implements ContentMetadataConfig {
@@ -43,19 +43,53 @@ export class AspectOrientedConfigService implements ContentMetadataConfig {
} }
public appendAllPreset(propertyGroups: PropertyGroupContainer): OrganisedPropertyGroup[] { public appendAllPreset(propertyGroups: PropertyGroupContainer): OrganisedPropertyGroup[] {
const groups = Object.keys(propertyGroups) const groups = Object.keys(propertyGroups)
.map((groupName) => { .map((groupName) => {
const propertyGroup = propertyGroups[groupName], const propertyGroup = propertyGroups[groupName],
properties = propertyGroup.properties; properties = propertyGroup.properties;
if (this.isAspectReadOnly(groupName)) {
Object.keys(properties).map((propertyName) => this.setReadOnlyProperty(properties[propertyName]));
}
return Object.assign({}, propertyGroup, { return Object.assign({}, propertyGroup, {
properties: Object.keys(properties).map((propertyName) => properties[propertyName]) properties: Object.keys(properties).map((propertyName) => {
if (this.isPropertyReadOnly(propertyName)) {
this.setReadOnlyProperty(properties[propertyName]);
}
return properties[propertyName];
})
}); });
}); });
return groups; return groups;
} }
private setReadOnlyProperty(property: Property) {
property.editable = false;
}
private isPropertyReadOnly(propertyName: string): boolean {
const readOnlyAspects = this.config.readOnlyProperties;
if (Array.isArray(readOnlyAspects)) {
return readOnlyAspects.includes(propertyName);
} else {
return readOnlyAspects === propertyName;
}
}
private isAspectReadOnly(propertyGroupName: string): boolean {
const readOnlyAspects = this.config.readOnlyAspects;
if (Array.isArray(readOnlyAspects)) {
return readOnlyAspects.includes(propertyGroupName);
} else {
return readOnlyAspects === propertyGroupName;
}
}
public filterExcludedPreset(propertyGroups: OrganisedPropertyGroup[]): OrganisedPropertyGroup[] { public filterExcludedPreset(propertyGroups: OrganisedPropertyGroup[]): OrganisedPropertyGroup[] {
if (this.config.exclude) { if (this.config.exclude) {
return propertyGroups.filter((preset) => { return propertyGroups.filter((preset) => {

View File

@@ -45,9 +45,11 @@ describe('LayoutOrientedConfigService', () => {
groupNameToQuery: 'berseria' groupNameToQuery: 'berseria'
}, },
{ {
config: [{ title: 'Deamons', items: [ config: [{
{ aspect: 'zestiria', properties: '*' }, { aspect: 'berseria', properties: '*' } title: 'Deamons', items: [
]}], { aspect: 'zestiria', properties: '*' }, { aspect: 'berseria', properties: '*' }
]
}],
expectation: true, expectation: true,
groupNameToQuery: 'berseria' groupNameToQuery: 'berseria'
}, },
@@ -98,11 +100,14 @@ describe('LayoutOrientedConfigService', () => {
const property1 = <Property> { name: 'property1' }, const property1 = <Property> { name: 'property1' },
property2 = <Property> { name: 'property2' }, property2 = <Property> { name: 'property2' },
property3 = <Property> { name: 'property3' }, property3 = <Property> { name: 'property3' },
property4 = <Property> { name: 'property4' }; property4 = <Property> { name: 'property4' },
property5 = <Property> { name: 'property5' },
property6 = <Property> { name: 'property6' };
const propertyGroups: PropertyGroupContainer = { const propertyGroups: PropertyGroupContainer = {
berseria: { title: 'Berseria', description: '', name: 'berseria', properties: { property1, property2 } }, berseria: { title: 'Berseria', description: '', name: 'berseria', properties: { property1, property2 } },
zestiria: { title: 'Zestiria', description: '', name: 'zestiria', properties: { property3, property4 } } zestiria: { title: 'Zestiria', description: '', name: 'zestiria', properties: { property3, property4 } },
otherTales: { title: 'Other tales', description: '', name: 'otherTales', properties: { property5, property6 } }
}; };
const testCases: TestCase[] = [ const testCases: TestCase[] = [
@@ -114,118 +119,155 @@ describe('LayoutOrientedConfigService', () => {
{ {
name: 'First property of a group in one item', name: 'First property of a group in one item',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'berseria', properties: [ 'property1' ] } title: 'First group', items: [
]} { aspect: 'berseria', properties: ['property1'] }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property1 ] } { title: 'First group', properties: [property1] }
] ]
}, },
{ {
name: 'Second property of a group in one item', name: 'Second property of a group in one item',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'berseria', properties: [ 'property2' ] } title: 'First group', items: [
]} { aspect: 'berseria', properties: ['property2'] }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property2 ] } { title: 'First group', properties: [property2] }
]
},
{
name: 'Properties with editable flag',
config: [
{
title: 'Editable property', items: [
{ aspect: 'otherTales', properties: ['property5'], editable: true },
{ aspect: 'otherTales', properties: ['property6'], editable: false }
]
}
],
expectations: [
{ title: 'Editable property', properties: [property5, property6] }
] ]
}, },
{ {
name: 'More properties from one group in one item', name: 'More properties from one group in one item',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'berseria', properties: [ 'property2', 'property1' ] } title: 'First group', items: [
]} { aspect: 'berseria', properties: ['property2', 'property1'] }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property2, property1 ] } { title: 'First group', properties: [property2, property1] }
] ]
}, },
{ {
name: 'First property of the second group in one item', name: 'First property of the second group in one item',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'zestiria', properties: [ 'property4' ] } title: 'First group', items: [
]} { aspect: 'zestiria', properties: ['property4'] }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property4 ] } { title: 'First group', properties: [property4] }
] ]
}, },
{ {
name: 'One-one properties from multiple groups in one item', name: 'One-one properties from multiple groups in one item',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'zestiria', properties: [ 'property4' ] }, title: 'First group', items: [
{ aspect: 'berseria', properties: [ 'property1' ] } { aspect: 'zestiria', properties: ['property4'] },
]} { aspect: 'berseria', properties: ['property1'] }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property4, property1 ] } { title: 'First group', properties: [property4, property1] }
] ]
}, },
{ {
name: 'Multiple properties mixed from multiple groups in multiple items', name: 'Multiple properties mixed from multiple groups in multiple items',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'zestiria', properties: [ 'property4' ] }, title: 'First group', items: [
{ type: 'berseria', properties: [ 'property1' ] } { aspect: 'zestiria', properties: ['property4'] },
]}, { type: 'berseria', properties: ['property1'] }
{ title: 'Second group', items: [ ]
{ aspect: 'zestiria', properties: [ 'property3' ] }, },
{ type: 'berseria', properties: [ 'property2', 'property1' ] }, {
{ aspect: 'zestiria', properties: [ 'property4' ] } title: 'Second group', items: [
]} { aspect: 'zestiria', properties: ['property3'] },
{ type: 'berseria', properties: ['property2', 'property1'] },
{ aspect: 'zestiria', properties: ['property4'] }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property4, property1 ] }, { title: 'First group', properties: [property4, property1] },
{ title: 'Second group', properties: [ property3, property2, property1, property4 ] } { title: 'Second group', properties: [property3, property2, property1, property4] }
] ]
}, },
{ {
name: 'Multiple properties mixed from multiple groups in multiple items with "*"', name: 'Multiple properties mixed from multiple groups in multiple items with "*"',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'zestiria', properties: '*' }, title: 'First group', items: [
{ type: 'berseria', properties: [ 'property1' ] } { aspect: 'zestiria', properties: '*' },
]}, { type: 'berseria', properties: ['property1'] }
{ title: 'Second group', items: [ ]
{ type: 'berseria', properties: [ 'property2', 'property1' ] } },
]} {
title: 'Second group', items: [
{ type: 'berseria', properties: ['property2', 'property1'] }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property3, property4, property1 ] }, { title: 'First group', properties: [property3, property4, property1] },
{ title: 'Second group', properties: [ property2, property1 ] } { title: 'Second group', properties: [property2, property1] }
] ]
}, },
{ {
name: 'Not existing property', name: 'Not existing property',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'zestiria', properties: '*' }, title: 'First group', items: [
{ type: 'berseria', properties: [ 'not-existing-property' ] }, { aspect: 'zestiria', properties: '*' },
{ type: 'berseria', properties: [ 'property2' ] } { type: 'berseria', properties: ['not-existing-property'] },
]} { type: 'berseria', properties: ['property2'] }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property3, property4, property2 ] } { title: 'First group', properties: [property3, property4, property2] }
] ]
}, },
{ {
name: 'Not existing group', name: 'Not existing group',
config: [ config: [
{ title: 'First group', items: [ {
{ aspect: 'zestiria', properties: '*' }, title: 'First group', items: [
{ type: 'not-existing-group', properties: '*' }, { aspect: 'zestiria', properties: '*' },
{ type: 'berseria', properties: [ 'property2' ] }, { type: 'not-existing-group', properties: '*' },
{ type: 'not-existing-group', properties: 'not-existing-property' } { type: 'berseria', properties: ['property2'] },
]} { type: 'not-existing-group', properties: 'not-existing-property' }
]
}
], ],
expectations: [ expectations: [
{ title: 'First group', properties: [ property3, property4, property2 ] } { title: 'First group', properties: [property3, property4, property2] }
] ]
} }
]; ];
@@ -238,7 +280,7 @@ describe('LayoutOrientedConfigService', () => {
expect(organisedPropertyGroups.length).toBe(testCase.expectations.length, 'Group count should match'); expect(organisedPropertyGroups.length).toBe(testCase.expectations.length, 'Group count should match');
testCase.expectations.forEach((expectation, i) => { testCase.expectations.forEach((expectation, i) => {
expect(organisedPropertyGroups[i].title).toBe(expectation.title, 'Group\'s title should match' ); expect(organisedPropertyGroups[i].title).toBe(expectation.title, 'Group\'s title should match');
expect(organisedPropertyGroups[i].properties.length).toBe( expect(organisedPropertyGroups[i].properties.length).toBe(
expectation.properties.length, expectation.properties.length,
`Property count for "${organisedPropertyGroups[i].title}" group should match.` `Property count for "${organisedPropertyGroups[i].title}" group should match.`

View File

@@ -19,7 +19,8 @@ import {
ContentMetadataConfig, ContentMetadataConfig,
LayoutOrientedConfigItem, LayoutOrientedConfigItem,
OrganisedPropertyGroup, OrganisedPropertyGroup,
PropertyGroupContainer PropertyGroupContainer,
Property
} from '../../interfaces/content-metadata.interfaces'; } from '../../interfaces/content-metadata.interfaces';
import { getProperty } from './property-group-reader'; import { getProperty } from './property-group-reader';
@@ -40,7 +41,8 @@ export class LayoutOrientedConfigService implements ContentMetadataConfig {
const organisedPropertyGroup = layoutBlocks.map((layoutBlock) => { const organisedPropertyGroup = layoutBlocks.map((layoutBlock) => {
const flattenedItems = this.flattenItems(layoutBlock.items), const flattenedItems = this.flattenItems(layoutBlock.items),
properties = flattenedItems.reduce((props, explodedItem) => { properties = flattenedItems.reduce((props, explodedItem) => {
const property = getProperty(propertyGroups, explodedItem.groupName, explodedItem.propertyName) || []; let property = getProperty(propertyGroups, explodedItem.groupName, explodedItem.propertyName) || [];
property = this.setEditableProperty(property, explodedItem);
return props.concat(property); return props.concat(property);
}, []); }, []);
@@ -89,13 +91,24 @@ export class LayoutOrientedConfigService implements ContentMetadataConfig {
return includeAllProperty !== undefined ? includeAllProperty : false; return includeAllProperty !== undefined ? includeAllProperty : false;
} }
private setEditableProperty(propertyGroup: Property | Property[], itemConfig): Property | Property[] {
if (Array.isArray(propertyGroup)) {
propertyGroup.map((property) => property.editable = itemConfig.editable !== undefined ? itemConfig.editable : true);
} else {
propertyGroup.editable = itemConfig.editable !== undefined ? itemConfig.editable : true;
}
return propertyGroup;
}
private flattenItems(items) { private flattenItems(items) {
return items.reduce((accumulator, item) => { return items.reduce((accumulator, item) => {
const properties = Array.isArray(item.properties) ? item.properties : [item.properties]; const properties = Array.isArray(item.properties) ? item.properties : [item.properties];
const flattenedProperties = properties.map((propertyName) => { const flattenedProperties = properties.map((propertyName) => {
return { return {
groupName: item.aspect || item.type, groupName: item.aspect || item.type,
propertyName propertyName,
editable: item.editable
}; };
}); });

View File

@@ -87,7 +87,7 @@ export class PropertyGroupTranslatorService {
value: propertyValue, value: propertyValue,
key: `${prefix}${property.name}`, key: `${prefix}${property.name}`,
default: property.defaultValue, default: property.defaultValue,
editable: true editable: property.editable !== undefined ? property.editable : true
}; };
let cardViewItemProperty; let cardViewItemProperty;

View File

@@ -338,7 +338,9 @@
"type": "object", "type": "object",
"required": [ "required": [
"includeAll", "includeAll",
"exclude" "exclude",
"readOnlyAspects",
"readOnlyProperties"
], ],
"properties": { "properties": {
"includeAll": { "includeAll": {
@@ -350,7 +352,20 @@
"description": "Property name", "description": "Property name",
"type": "string" "type": "string"
} }
},
"readOnlyAspects": {
"type": "array",
"items": {
"description": "Disable editing in these aspects",
"type": "string"
}
},
"readOnlyProperties": {
"type": "array",
"items": {
"description": "Disable editing in these properties",
"type": "string"
}
} }
} }
} }
@@ -397,7 +412,8 @@
"type": "object", "type": "object",
"required": [ "required": [
"aspect", "aspect",
"properties" "properties",
"editing"
], ],
"properties": { "properties": {
"aspect": { "aspect": {
@@ -407,6 +423,10 @@
"properties": { "properties": {
"description": "list of aspect properties", "description": "list of aspect properties",
"type": "array" "type": "array"
},
"editing": {
"description": "Enable/disable editing for this aspect",
"type": "boolean"
} }
} }
}, },
@@ -892,7 +912,10 @@
}, },
"postfix": { "postfix": {
"description": "exclude", "description": "exclude",
"type": ["string","array"] "type": [
"string",
"array"
]
} }
} }
}, },
@@ -1209,8 +1232,7 @@
"name" "name"
], ],
"properties": { "properties": {
"id": { "id": {},
},
"name": { "name": {
"type": "string" "type": "string"
}, },