Items with features view

Introduction

The "Items with features" view and its siblings "Products with features" and "Variants with features" views show all items/variants/products of a structure group and display their attributes in the context of the structure group features. This means values will be converted in the datatype and unit of the structure group feature.

images/download/attachments/513738918/ItemsWithFeatures.png

The old implementation of this view had some flaws due to its origin as a customer solution:

  • In the old view the decision whether an attribute is mapped to a feature has been done via the name. This could lead to free attributes being displayed in the view and other mapped attributes not being displayed because the name of the attribute was not the same as the name of the feature it was mapped to. In the new view the mapping is done through the AASGAM.

  • The old view would not work in a revision, the new one does.

We also added some new features to the view:

  • The field selection dialog lets you choose additional fields of the item/variant/product. These will be displayed to the left of the feature columns. In contrast to the feature columns, you can reorder and remove them by dragging them.

  • A second language for all feature value columns can be selected which will double the feature columns.

  • A header language can be selected. This will change the label in the column header of the feature columns.

  • An additional feature column filter has been added. By default, the number of columns is not limited but the number of displayed features is limited. This means if 10 features are configured you get 10 columns for the first language, 10 columns for the second language (if selected) plus additional second value columns in one or two languages for all features with datatype "range". At most you get 40 columns. You can adjust the number of features in the plugin_customization.ini as shown below.
    The feature columns are ordered by the value of the features' field "sequence", not alphabetically. If there are more features than the preference says, those with the highest sequence numbers won't be displayed. If you need to edit them, use the feature column filter.

    preference max.feature.count in plugin_customization.ini
    # Defines the maximum count of features displayed in the items with features (specialized) view. If you
    # increase this number be sure that your view still works correctely. There is a limitation on the character count
    # of all column headers of about 3000. If you exceed this limit it could be that the table will not be displayed
    # properly.
    # com.heiler.ppm.article.structure.core/max.feature.count = 50

Also good to know are these points:

  • When grouping or sorting, only the item/variant/product columns (sometimes called static columns) are taken into consideration.
    Also when saving the layout only the item/variant/product columns, the selected languages and the feature filter configuration are saved. The feature columns will not be saved in the layout because they are context dependant and most likely a different structure group will be selected when you open a layout.

  • The initial first language (in an empty workspace) is the client language. No second language and no header language is selected.

  • You need the article read action right to right see and open the view and the field rights for ArticleAttributeValue.Value and ArticleAttributeValue.SecondValue to see and edit attribute values.

The view

This is an overview of the view implementation. These are the core objects of the view and their interaction will be briefly described below. However, there are many many details which can't be described here.

images/download/attachments/513738918/ItemsWithFeaturesView.png

ArticlesInFeatureContextTableView

  • creates FeaturesManager, FeatureFilterManager and FeatureLanguageManager and registers as listener

  • when notified by the FeatureManager that features have changed, it asks the FeatureFieldFactory to generate the featureFields, sets them in the tableContainer and fires columnsChanged

  • when notified by the FeatureLanguageManager of a

    • new content language:

      • asks the FeatureFieldFactory to generate the featureFields, sets them in the tableContainer and fires columnsChanged

      • updates the table layout config for being able to restore the view later

    • new header language:

      • updates the headers of the columns using the FeatureFieldNameFactory which asks the FeatureLanguageManager for the current headerLanguage.

      • updates the table layout config for being able to restore the view later

  • when notified by the FeatureFilterManager that the filter settings changed, it is only stored in the table layout.

    • The columns (and content) are adjusted by the notification of the FeatureManager

FeatureManager

  • knows about the structureGroup context

  • knows about the preference indicating the number of features

  • manages the list of features which is updated when

    • the filter changed (= when notified by the FeatureFilterManager)

    • when the structureGroup context changed

FeatureFilterManager

  • contains the filter model (containing the settings) which is manipulated by the FeatureFilterDialog directly. When confirming the dialog with OK, it fires filterChanged().

FeatureLanguageManager

  • manages primary, secondary and header language

  • notifies its listeners when one of the content languages or the header language is changed

  • can persist its settings

Data Access and fragments

The old implementation of the view mapped attributes and features via their name which might be wrong. An attribute can have the same name as a feature but no mapping to it and the other way around, they can have different names but the attribute has a mapping to the feature.

So the right way to load data for the view is to go over the AASGAM. This wasn't possible. In the view we want to get all attributes mapped to the structureGroupAttributes of a specific structureGroup. When loading a listModel with attributes, you need a fieldPath to the aasgam fields. One of the logical keys of these fieldPaths is the attribute name. However, we don't know the names as they can be different to the structureGroupAttribute names.

Therefore we needed a new fragment. The idea is to ignore the name of the attribute but provide enough additional information to be able to retrieve all attributes mapped to a structureGroupAttribute of a certain structureGroup. In the aasgam entity we only wanted to use proxies, so the combination of structureGroup and structureFeature needed to uniquely identify a mapped attribute. Therfore it is no longer allowed to map two attributes of the same article to the same StructureGroupAttribute.

There is no update script handling the case of two attributes being mapped to the same StructureGroupAttribute. If the data base contains such data constellation it is not defined what happens in the view.

The new fragments of type ArticleAttributeSGContextFragment are contributed in com.heiler.ppm.article.structure.server. They are HQLFragments and use contributed Parameter and ParameterBuilder which replace the generic HQL generation for the ArticleAttribute.LK.Name with our own implementation which takes the given StructureGroup and StructureAttribute from the FieldPath and adds a condition which matches these values against the AASGAM.StructureGroupId and AASGAM.StructureAttributeId of the Attribute.

There are four contributions by intent because there are cases in which you wouldn't get data because of the join condition, if handled by one fragment instance. For example you wouldn't get a value for english if there is no name in english. With the AASGAM fragment there is a different problem. The AASGAM has two parents, the ArticleAttribute and the ArticleStructureMap. It is undefined which parent is used by the fragment, so sometimes it would use ArticleAttribute and because we don't know the attribute name, the resulting data wasn't correct/values were missing.

Fragment changes in general

In addition to the substitution of the name by the StructureGroup and StructureFeature, the new fragment and the old fragment have to be able to run in parallel. To make that possible we had to make some changes in the FragmentManager and FragmentRegistry. Now, they handle a list of fragments for each entityType or fieldType.
For new fragments there are two things to be aware of:

  1. The Fragment interface has an additional method boolean isResponsible( FieldPath fieldPath ). FragmentBaseImpl has a default implementation returning true.

  2. If you have two fragments for the same fieldType or entityType you need to specify a priority. If only one fragment is used everything will work fine without a priority.

When loading a listmodel with fieldPaths, all fragments which were contributed for the entityType or fieldType of the fieldPath will be asked if they feel responsible for this fieldPath. They will be asked in the order of their priority where the fragment with the highest priority will be asked first. The first fragment returning true when isResponsible is called will handle the fieldPath.

Displayed values and read validations

The Attribute has three values, the first value, the second value and the different default value, but we only have at most two columns for each feature. This leads to the question which value is displayed?

  • If a different default value is available, the different default value will be displayed

  • If no different default value is available, the value for identifier "Default" will be displayed. In short: This view only displayes values with identifier "Default" even if the attribute is multivalue and has multiple values.

  • If the datatype of the feature is range always the first and second value should be displayed, never the different default value.

Read validations

We added a variety of validations which are displayed in a lightweight dialog when you click on the information, warning or error icon in a cell.

Validation

Message

Type

Meaning

Class

Is an attribute mapped to the feature?

The feature is not yet associated with an attribute

Warning

The item doesn't have an aasgam with the selected structureGroup and the feature of the corresponding column

MappedAttributeExistsValueValidator

If the feature is mandatory, is there a value?

No value exists for this mandatory feature

Error

The feature is mandatory and the attribute neither has a value nor a different default value

MandatoryValueValidator

Are both, attribute and feature, multiValue or not?

The "multiple values" property of the attribute does not match that of the feature

Error

Cell will be read only

MultiValueFlagValueValidator

Are datatypes matching?

The data type check has detected incompatible data types

Error

see table below

DataTypeValueValidator

The data type check has detected different data types

Warning

Are Units convertible?

The attribute has a unit but the feature has no unit

Warning

UnitValueValidator

The units of the feature and attribute cannot be converted into each other

Error

Cell will be read only

Are datatype and unit of the feature matching?
Are datatype and unit of the attribute matching?

The attribute has a unit, but no data type

Warning

DatatypeUnitValueValidator

The feature has the 'Date' or 'Logical Value' data type and a unit

Error

Cell will be read only

The attribute has a unit, but has no data type

Warning

The attribute has the 'Date' or 'Logical Value' data type and a unit

Error

Cell will be read only

The attribute and feature have the "Character String" data type and different units

Warning

If feature and attribute both have datatype string and a unit and their units are convertible
(If their units are not convertible, the UnitValueValidator adds a validation result)

The attribute and feature have the "Character String" data type but only one of them has a unit

Warning

Is the value one of the preset values?
Is the domain value one of the preset values?

The divergent present value does not match any of the preset values

Error

Attribute is mapped, feature is not multiValue and different default value is present

PresetValueValidator

The value does not match any of the preset values

Error

Value is not checked if different default value is present (only if feature is multiValue)

No Validation;
Does datatype and unit conversions on db value.

No attribute value was maintained

Info

Different default value is displayed but there is no value.

ConvertValueValidator

The attribute value is <attributeValue>

Info

Shows the converted and formatted value as additional information if the different default value is displayed

The saved attribute value is incompatible with the attribute data type but is compatible with the feature data type

Warning

The saved attribute value is incompatible with both the attribute data type and the feature data type

Error

The unconverted db value is displayed. No unit conversion!

The attribute value was converted into the unit of the feature

Info

Converting the attribute value into the unit of the feature results in a loss of accuracy: the converted value would be <convertedValueWithFullPrecision> but, according to the feature data type, it should be <convertedValueInSgaDatatype>

Error

The converted attribute value is incompatible with the feature data type

Error

A problem occurred during the conversion

Error

Additional Information on DataTypeValueValidator

This is an overview which datatype combinations are allowed and which are not. If an error is detected, the cell will be read only.

Attribute datatype

null

String

Boolean

Date

Integer

Decimal

Double

Range

Feature
datatype

null

OK

OK

OK

OK

OK

OK

OK

OK

String

OK

OK

OK

OK

OK

OK

OK

OK

Boolean

OK

ERROR

OK

ERROR

ERROR

ERROR

ERROR

ERROR

Date

OK

ERROR

ERROR

OK

ERROR

ERROR

ERROR

ERROR

Integer

OK

WARNING

WARNING

ERROR

OK

WARNING

WARNING

ERROR

Decimal

OK

WARNING

WARNING

ERROR

OK

OK

WARNING

ERROR

Double

OK

WARNING

WARNING

ERROR

OK

OK

OK

ERROR

Range

OK

ERROR

ERROR

ERROR

ERROR

ERROR

ERROR

OK

Additional Information on ConvertValueValidator

The different default value is not converted at all. It is assumed that the value has a matching datatype and the value is already correct for the unit of the structure group attribute. This is because it was initially added to the feature groups list before it was assigned as different default value to the attribute. (Work will be done in this area in future versions.)

For a value the simplified conversion looks like this:

images/download/attachments/513738918/ConvertValueValidator.png

Adding your own validations

To add a validation to the "Items with features" view do the following:

  1. Write your own validations by implementing ListModelValueValidator.

  2. Extend the class FeatureContextValidationCreator and overwrite createValidators(). Add your own instances of ListModelValueValidator to the returned array.

  3. Extend the view and overwrite the method protected FeatureContextValidationCreator createValidationCreator( StructureGroupProxy structureGroupProxy, Long attributeKeyLanguageId ). Return your own FeatureContextValidationCreator in this method.

The interface ListModelValueValidator:

Interface ListModelValueValidator
public interface ListModelValueValidator extends Serializable
{
public FieldPath getTargetFieldPath();
public FieldPath[] getAdditionalFieldPaths();
public void validate( ValueValidationDataRow dataRow ) throws CoreException;
}

The getTargetFieldPath() method returns a fieldPath. The value in the column of this fieldPath is the one which is validated. If this fieldPath is not one of the requested fieldPaths, the additional field paths won't be added to requested fieldPaths.

In the method getAdditionalFieldPaths() you can request more information by returning fieldPaths leading to the information you need. This information will be present in the listModel

The method validate() is the one which is actually doing the validation (or calculation). Also this method takes responsibility for writing the result back into the listModel for later use. The ValueValidationDataRow is a wrapper for ListEntry and ListEntryBody which allows you to simply use getValue(FieldPath fieldPath) and setValue(FieldPath fieldPath, Object value) in virtual and non-virtual list model loading.

We used a special object (FeatureValidationResult) which bundles the value, a list of validation results in form of StatusExt objects and additional information (also used when writing values later) and replaced the original value in the column of the according fieldPath with it.

Validation example (items with features view)

In this example an additional information - the article attribute value sequence - is displayed as validation result.

  • Write the validation

    Write a validation
    public class AttributeDisplayOrderValidation extends FeatureValueValidatorBaseImpl
    {
     
    private static final long serialVersionUID = 1L;
     
    private FieldPath targetFieldPath;
     
    private FieldPath additionalFieldPath;
     
    public AttributeDisplayOrderValidation( FieldPath targetFieldPath )
    {
    this.targetFieldPath = targetFieldPath;
    this.additionalFieldPath = createAdditionalFieldPath( targetFieldPath, "ArticleAttributeValueType.DisplayOrder" ); //$NON-NLS-1$
    }
     
    @Override
    public FieldPath getTargetFieldPath()
    {
    return this.targetFieldPath;
    }
     
    @Override
    public FieldPath[] getAdditionalFieldPaths()
    {
    return new FieldPath[] { this.additionalFieldPath };
    }
     
    @Override
    public void validate( ValueValidationDataRow dataRow ) throws CoreException
    {
    FeatureValidationResult validationResultObject = initValidationResultObject( dataRow );
    if ( validationResultObject.getOriginalValue() != null )
    {
    Object displayOrder = dataRow.getValue( this.additionalFieldPath );
    String message = "Attribute value sequence: " + displayOrder; //$NON-NLS-1$ //$NON-NLS-2$
    StatusExt status = new StatusExt( Categories.SUMMARY, Status.INFO, Activator.PLUGIN_ID, 0, message, null );
     
    validationResultObject.addResult( status );
    }
    }
    }

    The targetFieldPath is the fieldPath of the column the validation result is displayed in (usually a fieldPath to ArticleAttributeValueType.Value or ArticleAttributeValueType.SecondValue).
    The additionalFieldPath is the one of the additional information you want so display as a validation result.
    The validate method has to first call initValidationResultObject which replaces the original value by the newly created FeatureValidationResult. Then you just make a message with the value of the additionalFieldPath and add it to the list of validation results in the FeatureValidationResult. The ValueValidationDataRow is just a wrapper for the different interfaces used by virtual and non virtual listModel loading.

  • Write your own ValidationCreator and add your own validation

    Extend the ValidationCreator
    public class CustomFeatureContextValidationCreator extends FeatureContextValidationCreator
    {
    public CustomFeatureContextValidationCreator( StructureGroupProxy structureGroupProxy, Long attributeKeyLanguageId )
    {
    super( structureGroupProxy, attributeKeyLanguageId );
    }
    @Override
    protected ListModelValueValidator[] createValidators( Set< FieldPath > valueFieldPaths,
    Map< StructureFeatureProxy, StructureGroupAttributeDataForValidation > sgaDataForValidation,
    RevisionToken revisionToken ) throws CoreException
    {
    List< ListModelValueValidator > validators = new ArrayList< ListModelValueValidator >();
    for ( FieldPath fieldPath : valueFieldPaths )
    {
    validators.add( new AttributeDisplayOrderValidation( fieldPath ) );
    }
    ListModelValueValidator[] otherValidators = super.createValidators( valueFieldPaths, sgaDataForValidation,
    revisionToken );
    validators.addAll( Arrays.asList( otherValidators ) );
    return validators.toArray( new ListModelValueValidator[0] );
    }
    }

    Don't remove the standard validations! They write additional information in the FeatureValidationResult which is used when storing/saving a changed value.

  • Then extend the items with features view and add your validationCreator

    Extend the view
    public class ItemsWithFeaturesCustomTableView extends ArticlesInFeatureContextTableView
    {
    @Override
    protected FeatureContextValidationCreator createValidationCreator( StructureGroupProxy structureGroupProxy,
    Long attributeKeyLanguageId )
    {
    return new CustomFeatureContextValidationCreator( structureGroupProxy, attributeKeyLanguageId );
    }
    }

Validations outside of the "Items with features" view

You can add an array of ListModelValueValidators to the ListModeLoadParams when loading a listModel via the ListModelLoadService.

Note that this is not a feature of its own but more a by-product of another feature. Therefore there is no way to add additional columns to the listModel for validation results. If there is a general need for adding columns, please talk to PM and add a feature request in Jira.

Validation example

In this Example a validator should add "20" to the second value if first and second value are initially equal.

Create two items.
The first one has different first and second values. The second value will not be changed by the validator.
The second value has the same value (60) for first and second value. The validator will change the second value to 80.

Create data for validation
...
CatalogProxy catalog = this.articleUtils.createCatalog();
ArticleProxy article = this.articleUtils.createArticle( catalog, "article_1" );
this.articleStructureUtils.mapArticleToStructureGroup( article, structureGroup );
this.articleUtils.setArticleAttributeFirstAndSecondValue( article, this.featureName, Languages.GERMAN, "30", "50" );
 
ArticleProxy secondArticle = this.articleUtils.createArticle( catalog, "article_2" );
this.articleStructureUtils.mapArticleToStructureGroup( secondArticle, structureGroup );
this.articleUtils.setArticleAttributeFirstAndSecondValue( secondArticle, this.featureName, Languages.GERMAN,
"60", "60" );
...

Implement the validator. The targetFieldPath which is the secondValue is handed to the validator in the constructor. The validator returns the fieldPath for the firstValue because it needs it for its validation. This fieldPath will be added to the resulting listModel. The method validate changes the value of the secondValue.

Validator
public class AdjustSecondValue implements ListModelValueValidator
{
private FieldPath targetFieldPath;
private FieldPath firstValueFieldPath;
public AdjustSecondValue( FieldPath fieldPath )
{
this.targetFieldPath = fieldPath;
}
@Override
public FieldPath getTargetFieldPath()
{
return this.targetFieldPath;
}
@Override
public FieldPath[] getAdditionalFieldPaths()
{
EntityPath entityPath = this.targetFieldPath.getEntityPath();
this.firstValueFieldPath = new FieldPath( entityPath, "ArticleAttributeValueType.FirstValue" );
return new FieldPath[] { this.firstValueFieldPath };
}
@Override
public void validate( ValueValidationDataRow dataRow ) throws CoreException
{
String firstValueString = ( String ) dataRow.getValue( this.firstValueFieldPath );
String secondValueString = ( String ) dataRow.getValue( this.targetFieldPath );
Integer firstValue = Integer.parseInt( firstValueString );
Integer secondValue = Integer.parseInt( secondValueString );
int difference = secondValue.intValue() - firstValue.intValue();
if ( difference == 0 )
{
dataRow.setValue( this.targetFieldPath, secondValue + 20 );
}
}
}

Add the validation to the ListModelLoadParams when loading the data.

Loading data with validations
private ListModel loadListModel( EntityItemList articles, FieldPath[] fieldPaths ) throws CoreException,
InterruptedException
{
Entity startEntity = RepositoryComponent.getRepositoryService()
.getEntityByIdentifier( "Article" ); //$NON-NLS-1$
FieldPath targetFieldPath = fieldPaths[ 1 ];
ListModelValueValidator[] valueValidators = new ListModelValueValidator[] { new AdjustSecondValue( targetFieldPath ) };
ListModelLoadService listModelLoadService = EntityManagementComponent.getListModelLoadService();
ListModelLoadParams loadParams = new ListModelLoadParams( articles, startEntity, fieldPaths );
loadParams.setListModelValueValidators( valueValidators );
ListModel listModel = listModelLoadService.loadListModel( new NullProgressMonitor(), loadParams );
return listModel;
}

The resulting listModel contains the changed second value for article_2

3768[|], article_1, 3768, 1000, 3770[|], 50, 30
3769[|], article_2, 3769, 1000, 3770[|], 80, 60

Writing

The diagram below shows which value will be written, the ArticleAttribute.Value or the ArticleAttributeStructureGroupAttributeMap.DomainValue.
If a value is entered in the column of a second value (if available), it is written to ArticleAttributeValue.SecondValue. If the attribute has another datatype than "range", the value isn't written at all.

When a value is edited, it is edited in the context of the structureGroupAttribute, so it is first converted in the datatype of the feature, then converted into the attributes unit and then converted to the datatype of the attribute.

images/download/attachments/513738918/ArticlesWithFeaturesWriteFlowChart.png

Extending the feature filter

In addition to the standard filter, the view provides the possibility to filter the displayed features. This is an important feature regarding the restriction of a maximum of 50 features which can be displayed. By default the features can be filtered for their attribute type, mandatory value and purpose but it is also possible to extend the filter for custom (reserved) fields.

The architecture is quite simple and based on a strict separation of model and UI. As you can see in the UML diagramm below the central class is the FeatureFilterManager which contains the extendable model.

images/download/attachments/513738918/FeatureFilterClassDiagramm.png

To extend the filter you have to do the following steps:

  • Sublcass the FeatureFilterManager and overwrite the methods createCategoryNodes() and createValueNodesForCategory() as shown in the example below

  • Subclass the ArticleInFeatureContextTableView and overwrite the method getFeatureFilterManager() like in the example below

  • Changes for the UI are not necessary

Sublcass the FeatureFilterManager and overwrite the methods createCategoryNodes() and createValueNodesForCategory()

Code example for a custom FeatureFilterManager
public class CustomFeatureFilterManager extends FeatureFilterManager
{
private FilterCategoryNode customCategoryNode;
 
 @Override
protected List< FilterCategoryNode > createCategoryNodes()
{
List< FilterCategoryNode > categoryNodes = super.createCategoryNodes();
Field customField = RepositoryComponent.getRepositoryService()
.getFieldByIdentifier( "StructureGroupAttribute.Grouping" ); //$NON-NLS-1$
this.customCategoryNode = new FilterCategoryNode( "Grouping", customField ); //$NON-NLS-1$
categoryNodes.add( this.customCategoryNode );
return categoryNodes;
}
 
 @Override
protected List< FilterValueNode > createValueNodesForCategory( FilterCategoryNode categoryNode )
{
if ( categoryNode.equals( this.customCategoryNode ) )
{
List< FilterValueNode > valueNodes = new ArrayList<>();
valueNodes.add( new FilterValueNode( "ID_BASEDATA", "Base data", "Base data" ) ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
valueNodes.add( new FilterValueNode( "ID_INTERFACE", "Interface", "Interface" ) ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return valueNodes;
}
return super.createValueNodesForCategory( categoryNode );
}
}

The Category nodes are the parent nodes and contain the field which has to be evaluated. In other words: Get this field from the structure group attribute and check its content vs. the selected values. Of course this field must be within the StructureGroupAttribute subentity. Don´t forget to call super.createCategoryNodes() so you will get the default filters, or ignore the call to remove them.

The Filtervalue nodes contain an id, a value and a displayname. The id is only used to persist and restore the state of the filter, so use an unique identifier. The value is is the real value which will be evaluated vs the value in the database.

If an enumprovider is contributed to your field, you can simply iterate over the entries and create a FilterValueNode for every entry. Use the label as displayName and the key as value.

For a better understanding see the screenshot of the filter below. The Category nodes are within a red frame while the Filtervalue nodes are within blue frames. Focusing on the "Grouping" filter it means "Get the value of the structure group attribute field "Grouping" and check if it is equal to the selected value "Base data" (Note that displayname and value are the same in this example). If the structure group attribute field is a List, it will be checked if this list contains the selected value.

images/download/attachments/513738918/ExtendedFeatureFilter.PNG

Subclass the ArticleInFeatureContextTableView and overwrite the method getFeatureFilterManager()

Example for subclassing the view
public class CustomArticlesInFeatureContextView extends ArticlesInFeatureContextTableView
{
@Override
public FeatureFilterManager getFeatureFilterManager()
{
if ( this.featureFilterManager == null )
{
this.featureFilterManager = new CustomFeatureFilterManager();
}
return this.featureFilterManager;
}
}

We try to prevent that views have to be subclassed in future. So we will see if we can provide a better solution for PIM 8 or later versions. Right now we see no other way to make the filter extendable since we don´t want to provide an extension point for such a small thing.


Changes for the UI are not necessary

The filter Dialog will be automatically generated from the model, so you don´t have to implement anything on UI side.

Questions and Answers

  • Why do I need the Article edit right to see the view?
    Actually you only need the Article read right to see the view.

  • Why can't I reorder and remove the feature columns?
    The features are ordered by their order number and can be filtered additionally. Reordering or removing feature columns would not be saved and so get lost if another structure group was selected or the current structure group was changed. This makes it nearly useless.

  • Are there similar read validations for detail models?
    Not yet! Perhaps we will add such validations when we refactor other views like the ArticleAttributeTableView (and don't merge it with the Article Attributes cumulative View which would change the data access to listModel loading).