Merge branch 'mkoller/nodeprovider' into mkoller/webclient
Conflicts:
api/atfxadapter/openatfx/build.gradle
Change-Id: Ieab74c9222711b539e0f6d962208824631f54116
diff --git a/WebContent/META-INF/MANIFEST.MF b/WebContent/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..254272e
--- /dev/null
+++ b/WebContent/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path:
+
diff --git a/api/atfxadapter/openatfx/build.gradle b/api/atfxadapter/openatfx/build.gradle
index 904c096..c0a9e5c 100644
--- a/api/atfxadapter/openatfx/build.gradle
+++ b/api/atfxadapter/openatfx/build.gradle
@@ -19,7 +19,7 @@
import de.undercouch.gradle.tasks.download.Download
-def atfxVersion = '0.7.4'
+def atfxVersion = '0.8.10'
description = 'Downloads openATFX and publishes it to the local maven repository'
dependencies {
@@ -31,8 +31,9 @@
compile project(":api:odsadapter")
testCompile 'junit:junit:4.12'
testCompile 'org.apache.commons:commons-math:2.2'
+ testCompile 'org.mockito:mockito-core:2.13.0'
testCompile 'org.assertj:assertj-core:3.6.2'
- testCompile 'org.mockito:mockito-core:2.13.0'
+
}
configurations.all {
@@ -64,9 +65,15 @@
outputs.dir file("${buildDir}/openatfx-${atfxVersion}")
}
+
task copySource(dependsOn: unzipOpenATFX, type: Copy) {
from file("${buildDir}/openatfx-${atfxVersion}/src")
into 'src'
+ /*
+ * openATFX is implemented against the ODS API compiled by JacORB. The JacORB IDL compiler generates SelItem#operator() while SunORB IDL compiler generates SelItem#_operator().
+ * Since openMDM uses SunORB to generate the ODS API from IDL, openATFX source code is missing SelItem#operator(). Therefore we change it with a simple replace and add the underscore.
+ */
+ filter { line -> line.replaceAll('SelOperator.AND.equals\\(condition.operator\\(\\)', 'SelOperator.AND.equals\\(condition._operator\\(\\)').replaceAll('return new TS_ValueSeq\\(new TS_UnionSeq\\(\\), new short\\[0\\]\\)', 'return ODSHelper.tsValue2tsValueSeq\\(new TS_Value\\[0\\], dt\\)') }
}
compileJava.dependsOn copySource
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/AnyTypeValuesBuilder.java b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/AnyTypeValuesBuilder.java
index 208edf9..0b0f40d 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/AnyTypeValuesBuilder.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/AnyTypeValuesBuilder.java
@@ -15,12 +15,9 @@
package org.eclipse.mdm.api.base.massdata;
import java.time.LocalDateTime;
-import java.util.Arrays;
-import java.util.List;
import org.eclipse.mdm.api.base.model.FileLink;
import org.eclipse.mdm.api.base.model.ScalarType;
-import org.eclipse.mdm.api.base.model.SequenceRepresentation;
/**
* This builder adds values to the {@link WriteRequest} of types listed below.
@@ -206,44 +203,4 @@
createValues(ScalarType.BLOB, values);
throw new UnsupportedOperationException("Not implemented.");
}
-
- public UnitIndependentBuilder externalComponent(ScalarType scalarType, ExternalComponent externalComponent) {
- return externalComponents(scalarType, null, externalComponent == null ? null : Arrays.asList(externalComponent));
- }
-
- public UnitIndependentBuilder externalComponents(ScalarType scalarType, Short globalFlag, ExternalComponent externalComponent) {
- return externalComponents(scalarType, globalFlag, externalComponent == null ? null : Arrays.asList(externalComponent));
- }
-
- public UnitIndependentBuilder externalComponents(ScalarType scalarType, List<ExternalComponent> externalComponents) {
- return externalComponents(scalarType, null, externalComponents);
- }
-
- public UnitIndependentBuilder externalComponents(ScalarType scalarType, Short globalFlag, List<ExternalComponent> externalComponents) {
- SequenceRepresentation seqRep = getWriteRequest().getSequenceRepresentation();
- if (SequenceRepresentation.EXPLICIT.equals(seqRep)) {
- seqRep = SequenceRepresentation.EXPLICIT_EXTERNAL;
- } else if (SequenceRepresentation.RAW_LINEAR.equals(seqRep)) {
- seqRep = SequenceRepresentation.RAW_LINEAR_EXTERNAL;
- } else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED.equals(seqRep)) {
- seqRep = SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL;
- } else if (SequenceRepresentation.RAW_POLYNOMIAL.equals(seqRep)) {
- seqRep = SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL;
- } else if (null == seqRep || !seqRep.isExternal()) {
- seqRep = SequenceRepresentation.EXPLICIT_EXTERNAL;
- }
-
- WriteRequest writeRequest = getWriteRequest();
- writeRequest.setSequenceRepresentation(seqRep);
- writeRequest.setRawScalarType(scalarType);
- writeRequest.setGlobalFlag(globalFlag);
- if (externalComponents != null) {
- for (ExternalComponent externalComponent : externalComponents) {
- writeRequest.addExternalComponent(externalComponent);
- }
- }
-
- return new UnitIndependentBuilder(writeRequest);
- }
-
}
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/ExternalComponentBuilder.java b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/ExternalComponentBuilder.java
new file mode 100644
index 0000000..1a6d00f
--- /dev/null
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/ExternalComponentBuilder.java
@@ -0,0 +1,83 @@
+/********************************************************************************
+ * Copyright (c) 2015-2021 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+package org.eclipse.mdm.api.base.massdata;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.mdm.api.base.model.ScalarType;
+import org.eclipse.mdm.api.base.model.SequenceRepresentation;
+
+/**
+ * This builder adds {@link ExternalComponent}s to the {@link WriteRequest} . It
+ * is possible to add a sequence where all of its values are valid.
+ *
+ */
+public class ExternalComponentBuilder extends BaseValuesBuilder {
+
+ /**
+ * Constructor.
+ *
+ * @param writeRequest The {@link WriteRequest} given {@link ExternalComponent}s
+ * will be added to.
+ */
+ ExternalComponentBuilder(WriteRequest writeRequest) {
+ super(writeRequest);
+ }
+
+ public UnitIndependentBuilder externalComponent(ScalarType scalarType, ExternalComponent externalComponent) {
+ return externalComponents(scalarType, null,
+ externalComponent == null ? null : Arrays.asList(externalComponent));
+ }
+
+ public UnitIndependentBuilder externalComponents(ScalarType scalarType, Short globalFlag,
+ ExternalComponent externalComponent) {
+ return externalComponents(scalarType, globalFlag,
+ externalComponent == null ? null : Arrays.asList(externalComponent));
+ }
+
+ public UnitIndependentBuilder externalComponents(ScalarType scalarType,
+ List<ExternalComponent> externalComponents) {
+ return externalComponents(scalarType, null, externalComponents);
+ }
+
+ public UnitIndependentBuilder externalComponents(ScalarType scalarType, Short globalFlag,
+ List<ExternalComponent> externalComponents) {
+ SequenceRepresentation seqRep = getWriteRequest().getSequenceRepresentation();
+ if (SequenceRepresentation.EXPLICIT.equals(seqRep)) {
+ seqRep = SequenceRepresentation.EXPLICIT_EXTERNAL;
+ } else if (SequenceRepresentation.RAW_LINEAR.equals(seqRep)) {
+ seqRep = SequenceRepresentation.RAW_LINEAR_EXTERNAL;
+ } else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED.equals(seqRep)) {
+ seqRep = SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL;
+ } else if (SequenceRepresentation.RAW_POLYNOMIAL.equals(seqRep)) {
+ seqRep = SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL;
+ } else if (null == seqRep || !seqRep.isExternal()) {
+ seqRep = SequenceRepresentation.EXPLICIT_EXTERNAL;
+ }
+
+ WriteRequest writeRequest = getWriteRequest();
+ writeRequest.setSequenceRepresentation(seqRep);
+ writeRequest.setRawScalarType(scalarType);
+ writeRequest.setGlobalFlag(globalFlag);
+ if (externalComponents != null) {
+ for (ExternalComponent externalComponent : externalComponents) {
+ writeRequest.addExternalComponent(externalComponent);
+ }
+ }
+
+ return new UnitIndependentBuilder(writeRequest);
+ }
+}
diff --git a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/WriteRequestBuilder.java b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/WriteRequestBuilder.java
index f810efc..6477ee4 100644
--- a/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/WriteRequestBuilder.java
+++ b/api/base/src/main/java/org/eclipse/mdm/api/base/massdata/WriteRequestBuilder.java
@@ -56,6 +56,18 @@
}
/**
+ * Configures the {@link WriteRequest} to create an explicit sequence of
+ * measured values provided as {@link ExternalComponent}s.
+ *
+ * @return An {@link ExternalComponentBuilder} is returned.
+ * @see SequenceRepresentation#EXPLICIT_EXTERNAL
+ */
+ public ExternalComponentBuilder explicitExternal() {
+ getWriteRequest().setSequenceRepresentation(SequenceRepresentation.EXPLICIT_EXTERNAL);
+ return new ExternalComponentBuilder(getWriteRequest());
+ }
+
+ /**
* Configures the {@link WriteRequest} to create an implicit constant sequence
* of measured values. An implicit sequence allows only numerical
* {@link ScalarType}s as listed below:
@@ -225,6 +237,21 @@
}
/**
+ * Configures the {@link WriteRequest} to create a raw linear sequence of
+ * measured values provided as {@link ExternalComponent}s.
+ *
+ * @param offset The offset for each value.
+ * @param factor The factor for each value.
+ * @return A {@link ExternalComponentBuilder} is returned.
+ * @see SequenceRepresentation#RAW_LINEAR_EXTERNAL
+ */
+ public ExternalComponentBuilder rawLinearExternal(double offset, double factor) {
+ getWriteRequest().setSequenceRepresentation(SequenceRepresentation.RAW_LINEAR_EXTERNAL);
+ getWriteRequest().setGenerationParameters(new double[] { offset, factor });
+ return new ExternalComponentBuilder(getWriteRequest());
+ }
+
+ /**
* Configures the {@link WriteRequest} to create a raw polynomial sequence of
* measured values.
*
@@ -254,6 +281,35 @@
}
/**
+ * Configures the {@link WriteRequest} to create a raw polynomial sequence of
+ * measured values provided as {@link ExternalComponent}s.
+ *
+ * @param coefficients At least 2 coefficients must be provided.
+ * @return A {@link ExternalComponentBuilder} is returned.
+ * @throws IllegalArgumentException Thrown if coefficients are missing or their
+ * length is less than 2.
+ * @see SequenceRepresentation#RAW_POLYNOMIAL_EXTERNAL
+ */
+ public ExternalComponentBuilder rawPolynomialExternal(double... coefficients) {
+ if (coefficients == null || coefficients.length < 2) {
+ throw new IllegalArgumentException(
+ "Coefficients either missing or their length is " + "inconsitent with given grade");
+ }
+
+ getWriteRequest().setSequenceRepresentation(SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL);
+
+ double[] generationParameters = new double[coefficients.length + 1];
+ generationParameters[0] = coefficients.length - 1;
+ System.arraycopy(coefficients, 0, generationParameters, 1, coefficients.length);
+ getWriteRequest().setGenerationParameters(generationParameters);
+
+ // TODO: currently it is possible to define such a channel as
+ // independent
+ // should we prevent this?!
+ return new ExternalComponentBuilder(getWriteRequest());
+ }
+
+ /**
* Configures the {@link WriteRequest} to create a raw linear calibrated
* sequence of measured values.
*
@@ -268,4 +324,20 @@
getWriteRequest().setGenerationParameters(new double[] { offset, factor, calibration });
return new ComplexNumericalValuesBuilder(getWriteRequest());
}
+
+ /**
+ * Configures the {@link WriteRequest} to create a raw linear calibrated
+ * sequence of measured values provided as {@link ExternalComponent}s.
+ *
+ * @param offset The offset for each value.
+ * @param factor The factor for each value.
+ * @param calibration The calibration factor.
+ * @return A {@link ExternalComponentBuilder} is returned.
+ * @see SequenceRepresentation#RAW_LINEAR_CALIBRATED_EXTERNAL
+ */
+ public ExternalComponentBuilder rawLinearCalibratedExternal(double offset, double factor, double calibration) {
+ getWriteRequest().setSequenceRepresentation(SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL);
+ getWriteRequest().setGenerationParameters(new double[] { offset, factor, calibration });
+ return new ExternalComponentBuilder(getWriteRequest());
+ }
}
diff --git a/api/base/src/test/java/org/eclipse/mdm/api/base/WriteRequestBuilderTest.java b/api/base/src/test/java/org/eclipse/mdm/api/base/WriteRequestBuilderTest.java
index 3e791b8..f80d7c0 100644
--- a/api/base/src/test/java/org/eclipse/mdm/api/base/WriteRequestBuilderTest.java
+++ b/api/base/src/test/java/org/eclipse/mdm/api/base/WriteRequestBuilderTest.java
@@ -36,7 +36,7 @@
@Test
public void testOneExternalComponent() {
- WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicit()
+ WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicitExternal()
.externalComponent(ScalarType.INTEGER, externalComponent1).independent().build();
assertThat(r1).hasFieldOrPropertyWithValue("channelGroup", channelGroup)
@@ -46,7 +46,7 @@
@Test
public void testMultipleExternalComponents() {
- WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicit()
+ WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicitExternal()
.externalComponents(ScalarType.INTEGER, Arrays.asList(externalComponent1, externalComponent2))
.independent().build();
@@ -58,29 +58,26 @@
@Test
public void testMultipleExternalComponentsCopyExtComp() {
- WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicit().externalComponents(
- ScalarType.INTEGER,
- Arrays.asList(new ExternalComponent[] { (new ExternalComponent())
- .setTypeSpecification(externalComponent1.getTypeSpecification())
- .setStartOffset(externalComponent1.getStartOffset())
- .setBlocksize(externalComponent1.getBlocksize())
- .setValuesPerBlock(externalComponent1.getValuesPerBlock())
- .setLength(externalComponent1.getLength())
- .setFileLink(externalComponent1.getFileLink())
- .setFlagsFileLink(externalComponent1.getFlagsFileLink())
- .setFlagsStartOffset(externalComponent1.getFlagsStartOffset())
- .setBitCount(externalComponent1.getBitCount())
- .setBitOffset(externalComponent1.getBitOffset()),
- (new ExternalComponent()).setTypeSpecification(externalComponent2.getTypeSpecification())
- .setStartOffset(externalComponent2.getStartOffset())
- .setBlocksize(externalComponent2.getBlocksize())
- .setValuesPerBlock(externalComponent2.getValuesPerBlock())
- .setLength(externalComponent2.getLength())
- .setFileLink(externalComponent2.getFileLink())
- .setFlagsFileLink(externalComponent2.getFlagsFileLink())
- .setFlagsStartOffset(externalComponent2.getFlagsStartOffset())
- .setBitCount(externalComponent2.getBitCount())
- .setBitOffset(externalComponent2.getBitOffset())}))
+ WriteRequest r1 = WriteRequest.create(channelGroup, channel, axisType).explicitExternal()
+ .externalComponents(ScalarType.INTEGER, Arrays.asList(new ExternalComponent[] {
+ (new ExternalComponent()).setTypeSpecification(externalComponent1.getTypeSpecification())
+ .setStartOffset(externalComponent1.getStartOffset())
+ .setBlocksize(externalComponent1.getBlocksize())
+ .setValuesPerBlock(externalComponent1.getValuesPerBlock())
+ .setLength(externalComponent1.getLength()).setFileLink(externalComponent1.getFileLink())
+ .setFlagsFileLink(externalComponent1.getFlagsFileLink())
+ .setFlagsStartOffset(externalComponent1.getFlagsStartOffset())
+ .setBitCount(externalComponent1.getBitCount())
+ .setBitOffset(externalComponent1.getBitOffset()),
+ (new ExternalComponent()).setTypeSpecification(externalComponent2.getTypeSpecification())
+ .setStartOffset(externalComponent2.getStartOffset())
+ .setBlocksize(externalComponent2.getBlocksize())
+ .setValuesPerBlock(externalComponent2.getValuesPerBlock())
+ .setLength(externalComponent2.getLength()).setFileLink(externalComponent2.getFileLink())
+ .setFlagsFileLink(externalComponent2.getFlagsFileLink())
+ .setFlagsStartOffset(externalComponent2.getFlagsStartOffset())
+ .setBitCount(externalComponent2.getBitCount())
+ .setBitOffset(externalComponent2.getBitOffset()) }))
.independent().build();
assertThat(r1).hasFieldOrPropertyWithValue("channelGroup", channelGroup)
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
index 25e3481..0ba1064 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/ReadRequestHandler.java
@@ -16,6 +16,8 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -42,6 +44,7 @@
import org.eclipse.mdm.api.base.model.Channel;
import org.eclipse.mdm.api.base.model.Entity;
import org.eclipse.mdm.api.base.model.MeasuredValues;
+import org.eclipse.mdm.api.base.model.ScalarType;
import org.eclipse.mdm.api.base.model.SequenceRepresentation;
import org.eclipse.mdm.api.base.model.Unit;
import org.eclipse.mdm.api.base.model.Value;
@@ -54,6 +57,8 @@
import org.eclipse.mdm.api.odsadapter.query.ODSEntityType;
import org.eclipse.mdm.api.odsadapter.query.ODSModelManager;
import org.eclipse.mdm.api.odsadapter.utils.ODSConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.google.common.collect.Lists;
@@ -64,6 +69,8 @@
* @author Viktor Stoehr, Gigatronik Ingolstadt GmbH
*/
public final class ReadRequestHandler {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ReadRequestHandler.class);
+
public static final class ColumnAttributes {
private String name;
private String id;
@@ -72,9 +79,15 @@
private boolean independent;
private AxisType axisType;
private Short globalFlag;
+ private ScalarType dataType;
+ private ScalarType rawDataType;
+
+ private String unit;
+ private String meaQuantityId;
public ColumnAttributes(String name, String columnId, SequenceRepresentation sequenceRepresentation,
- double[] generationParameters, boolean independent, AxisType axisType, Short globalFlag) {
+ double[] generationParameters, boolean independent, AxisType axisType, Short globalFlag,
+ ScalarType rawDataType, String meaQuantityId) {
this.name = name;
this.id = columnId;
this.sequenceRepresentation = sequenceRepresentation;
@@ -82,6 +95,8 @@
this.independent = independent;
this.axisType = axisType;
this.globalFlag = globalFlag;
+ this.rawDataType = rawDataType;
+ this.meaQuantityId = meaQuantityId;
}
public String getName() {
@@ -140,6 +155,34 @@
this.globalFlag = globalFlag;
}
+ public ScalarType getRawDataType() {
+ return rawDataType;
+ }
+
+ public void setRawDataType(ScalarType rawDataType) {
+ this.rawDataType = rawDataType;
+ }
+
+ public ScalarType getDataType() {
+ return dataType;
+ }
+
+ public void setDataType(ScalarType dataType) {
+ this.dataType = dataType;
+ }
+
+ public String getMeaQuantityId() {
+ return meaQuantityId;
+ }
+
+ public String getUnit() {
+ return unit;
+ }
+
+ public void setUnit(String unit) {
+ this.unit = unit;
+ }
+
}
// ======================================================================
@@ -190,87 +233,58 @@
}
}
- ValueMatrix valueMatrix = null;
- Column[] arrColumns = null;
+ // Load ColumnAttributes:
+ Map<String, ColumnAttributes> mapColumnAttributes = getColumnAttributes(readRequest);
+
+ if (mapColumnAttributes.isEmpty()) {
+ return Collections.emptyList();
+ }
List<MeasuredValues> listMeasuredValues = new ArrayList<>();
- try {
- // Load ColumnAttributes:
- Map<String, ColumnAttributes> mapColumnAttributes = getColumnAttributes(readRequest);
-
- final Map<String, List<ExternalComponent>> mapExternalComponents;
-
- if (ValuesMode.STORAGE_PRESERVE_EXTCOMPS == readRequest.getValuesMode() && externalComponentExists()) {
-
- EntityType localColumnEntityType = modelManager.getEntityType("LocalColumn");
- EntityType externalComponentEntityType = modelManager.getEntityType("ExternalComponent");
-
- Relation relExternalComponentToLocalColumn = externalComponentEntityType
- .getRelation(localColumnEntityType);
-
- List<Attribute> listExtCompAttributes = externalComponentEntityType.getAttributes();
-
- boolean hasExtCompBitCount = (listExtCompAttributes.stream()
- .filter(a -> ExternalComponentEntity.ATTR_BITCOUNT.equals(a.getName())).count() > 0);
- boolean hasExtCompBitOffset = (listExtCompAttributes.stream()
- .filter(a -> ExternalComponentEntity.ATTR_BITOFFSET.equals(a.getName())).count() > 0);
-
- List<String> ids = new ArrayList<>();
- for (Map.Entry<String, ColumnAttributes> me : mapColumnAttributes.entrySet()) {
- if (me.getValue().getSequenceRepresentation().isExternal()) {
- ids.add(me.getKey());
- }
- }
-
- mapExternalComponents = queryService.createQuery().select(externalComponentEntityType.getAttributes())
- .select(relExternalComponentToLocalColumn.getAttribute())
- .order(relExternalComponentToLocalColumn.getAttribute(),
- externalComponentEntityType.getAttribute(ExternalComponentEntity.ATTR_ORDINALNUMBER))
- .fetch(Filter.and()
- .add(ComparisonOperator.IN_SET.create(relExternalComponentToLocalColumn.getAttribute(),
- ids.toArray(new String[0]))))
- .stream()
- .map(r -> new ImmutablePair<String, ExternalComponent>(
- r.getValue(relExternalComponentToLocalColumn.getAttribute()).extract(),
- new ExternalComponent(
- r.getValue(externalComponentEntityType.getAttribute(
- ExternalComponentEntity.ATTR_TYPESPECIFICATION)).extract(),
- r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_LENGTH)).extract(),
- r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_STARTOFFSET)).extract(),
- r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_BLOCKSIZE)).extract(),
- r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_VALUESPERBLOCK)).extract(),
- r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_VALUEOFFSET)).extract(),
- r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_FILENAMEURL))
- .extractWithDefault(null),
- r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_FLAGSFILENAMEURL))
- .extractWithDefault(null),
- r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_FLAGSSTARTOFFSET))
- .extractWithDefault(null),
- (hasExtCompBitCount
- ? r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_BITCOUNT)).extract()
- : Short.valueOf((short) 0)),
- (hasExtCompBitOffset
- ? r.getValue(externalComponentEntityType
- .getAttribute(ExternalComponentEntity.ATTR_BITOFFSET)).extract()
- : Short.valueOf((short) 0)))))
- .collect(Collectors.groupingBy(Pair::getKey,
- Collectors.mapping(Pair::getValue, Collectors.toList())));
-
- } else {
- mapExternalComponents = new HashMap<>();
+ if (ValuesMode.STORAGE_PRESERVE_EXTCOMPS == readRequest.getValuesMode() && externalComponentExists()) {
+ List<String> processedLcIds = new ArrayList<>();
+ Map<String, List<ExternalComponent>> mapExternalComponents = loadExternalComponents(mapColumnAttributes);
+ LOGGER.debug("Loaded external components for {} localcolumns.", mapExternalComponents.size());
+ // Create MeasuredValues for local columns whose external components have been
+ // loaded above:
+ for (Map.Entry<String, List<ExternalComponent>> entry : mapExternalComponents.entrySet()) {
+ String lcId = entry.getKey();
+ List<ExternalComponent> listExtComps = entry.getValue();
+ listMeasuredValues
+ .add(ODSConverter.fromExternalComponents(listExtComps, mapColumnAttributes.get(lcId)));
+ processedLcIds.add(lcId);
}
- if (mapColumnAttributes.size() > 0) {
+ // implict
+ for (Map.Entry<String, ColumnAttributes> entry : mapColumnAttributes.entrySet()) {
+ String lcId = entry.getKey();
+ ColumnAttributes ca = entry.getValue();
+ if (entry.getValue().getSequenceRepresentation().isImplicit()) {
+
+ ScalarType scalarType = ca.getRawDataType();
+ if (scalarType == ScalarType.UNKNOWN) {
+ scalarType = ca.getDataType();
+ }
+
+ listMeasuredValues.add(scalarType.createMeasuredValues(ca.getName(), ca.getUnit(),
+ ca.getSequenceRepresentation(), ca.getGenerationParameters(), ca.isIndependentColumn(),
+ ca.getAxisType(), ca.getGlobalFlag(), Collections.emptyList()));
+ processedLcIds.add(lcId);
+ }
+ }
+ // remove processed localcolumns
+ processedLcIds.forEach(lcId -> mapColumnAttributes.remove(lcId));
+ }
+
+ if (!mapColumnAttributes.isEmpty()) {
+ // if there are still localcolumns left we load them be valuematrix
+ LOGGER.debug("Loading {} localcolumns by value matrix.", mapColumnAttributes.size());
+
+ ValueMatrix valueMatrix = null;
+ Column[] arrColumns = null;
+
+ try {
valueMatrix = getValueMatrix(readRequest);
List<Pair<Column, ColumnAttributes>> listColumnPairs = getODSColumns(readRequest, valueMatrix,
mapColumnAttributes);
@@ -278,36 +292,100 @@
// Get array of all ODS columns for later release:
arrColumns = listColumnPairs.stream().map(Pair::getLeft).toArray(Column[]::new);
- // Create MeasuredValues for local columns whose external components have been
- // loaded above:
- for (Pair<Column, ColumnAttributes> pair : listColumnPairs) {
- Column column = pair.getLeft();
- ColumnAttributes ca = pair.getRight();
- List<ExternalComponent> listExtComps = mapExternalComponents.get(ca.getId());
- if (listExtComps != null) {
- listMeasuredValues.add(ODSConverter.fromExternalComponents(listExtComps,
- column.getRawDataType(), column.getUnit(), ca));
- }
- }
-
// Create MeasuredValues for the remaining local columns:
- NameValueSeqUnit[] nvsus = valueMatrix.getValue(
- listColumnPairs.stream()
- .filter(p -> !contains(mapExternalComponents.keySet(), p.getRight().getId()))
- .map(p -> p.getLeft()).toArray(Column[]::new),
- readRequest.getStartIndex(), readRequest.getRequestSize());
+ NameValueSeqUnit[] nvsus = valueMatrix.getValue(arrColumns, readRequest.getStartIndex(),
+ readRequest.getRequestSize());
listMeasuredValues.addAll(ODSConverter.fromODSMeasuredValuesSeq(nvsus,
listColumnPairs.stream().map(Pair::getRight).toArray(ColumnAttributes[]::new)));
+ } catch (AoException aoe) {
+ throw new DataAccessException(aoe.reason, aoe);
+ } finally {
+ releaseColumns(arrColumns);
+ releaseValueMatrix(valueMatrix);
}
-
- return listMeasuredValues;
-
- } catch (AoException aoe) {
- throw new DataAccessException(aoe.reason, aoe);
- } finally {
- releaseColumns(arrColumns);
- releaseValueMatrix(valueMatrix);
}
+
+ return listMeasuredValues;
+ }
+
+ /**
+ * Loads External Components for all ColumnAttributes which satisfy
+ * {@link SequenceRepresentation#isExternal()}
+ *
+ * @param mapColumnAttributes
+ * @return a map with the LocalColumn.Id as key and a list of its
+ * ExternalComponents as value
+ */
+ private Map<String, List<ExternalComponent>> loadExternalComponents(
+ Map<String, ColumnAttributes> mapColumnAttributes) {
+ Map<String, List<ExternalComponent>> mapExternalComponents;
+ EntityType localColumnEntityType = modelManager.getEntityType("LocalColumn");
+ EntityType externalComponentEntityType = modelManager.getEntityType("ExternalComponent");
+
+ Relation relExternalComponentToLocalColumn = externalComponentEntityType.getRelation(localColumnEntityType);
+
+ List<Attribute> listExtCompAttributes = externalComponentEntityType.getAttributes();
+
+ boolean hasExtCompBitCount = (listExtCompAttributes.stream()
+ .filter(a -> ExternalComponentEntity.ATTR_BITCOUNT.equals(a.getName())).count() > 0);
+ boolean hasExtCompBitOffset = (listExtCompAttributes.stream()
+ .filter(a -> ExternalComponentEntity.ATTR_BITOFFSET.equals(a.getName())).count() > 0);
+
+ List<String> ids = new ArrayList<>();
+ for (Map.Entry<String, ColumnAttributes> me : mapColumnAttributes.entrySet()) {
+ if (me.getValue().getSequenceRepresentation().isExternal()) {
+ ids.add(me.getKey());
+ }
+ }
+
+ if (ids.isEmpty()) {
+ // no external sequence representations -> no externalcomponents to load
+ return Collections.emptyMap();
+ }
+
+ mapExternalComponents = queryService.createQuery().select(externalComponentEntityType.getAttributes())
+ .select(relExternalComponentToLocalColumn.getAttribute())
+ .order(relExternalComponentToLocalColumn.getAttribute(),
+ externalComponentEntityType.getAttribute(ExternalComponentEntity.ATTR_ORDINALNUMBER))
+ .fetch(Filter.and()
+ .add(ComparisonOperator.IN_SET
+ .create(relExternalComponentToLocalColumn.getAttribute(), ids.toArray(new String[0]))))
+ .stream()
+ .map(r -> new ImmutablePair<String, ExternalComponent>(
+ r.getValue(relExternalComponentToLocalColumn.getAttribute()).extract(),
+ new ExternalComponent(
+ r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_TYPESPECIFICATION)).extract(),
+ r.getValue(
+ externalComponentEntityType.getAttribute(ExternalComponentEntity.ATTR_LENGTH))
+ .extract(),
+ r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_STARTOFFSET)).extract(),
+ r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_BLOCKSIZE)).extract(),
+ r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_VALUESPERBLOCK)).extract(),
+ r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_VALUEOFFSET)).extract(),
+ r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_FILENAMEURL))
+ .extractWithDefault(null),
+ r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_FLAGSFILENAMEURL))
+ .extractWithDefault(null),
+ r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_FLAGSSTARTOFFSET))
+ .extractWithDefault(null),
+ (hasExtCompBitCount
+ ? r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_BITCOUNT)).extract()
+ : Short.valueOf((short) 0)),
+ (hasExtCompBitOffset
+ ? r.getValue(externalComponentEntityType
+ .getAttribute(ExternalComponentEntity.ATTR_BITOFFSET)).extract()
+ : Short.valueOf((short) 0)))))
+ .collect(Collectors.groupingBy(Pair::getKey, Collectors.mapping(Pair::getValue, Collectors.toList())));
+ return mapExternalComponents;
}
private boolean externalComponentExists() {
@@ -404,8 +482,8 @@
Map<String, ColumnAttributes> mapColumnAttributes = new HashMap<>();
EntityType localColumnEntityType = modelManager.getEntityType("LocalColumn");
- Attribute idAttr = localColumnEntityType.getAttribute("Id");
- Attribute nameAttr = localColumnEntityType.getAttribute("Name");
+ Attribute idAttr = localColumnEntityType.getIDAttribute();
+ Attribute nameAttr = localColumnEntityType.getNameAttribute();
Attribute submatrixAttr = localColumnEntityType.getAttribute("SubMatrix");
// Don't query GenerationParameters together with other non-ID attributes as
@@ -413,7 +491,8 @@
Query query1 = queryService.createQuery().select(Lists.newArrayList(idAttr, nameAttr,
localColumnEntityType.getAttribute("SequenceRepresentation"),
localColumnEntityType.getAttribute("IndependentFlag"), localColumnEntityType.getAttribute("GlobalFlag"),
- localColumnEntityType.getAttribute("axistype")));
+ localColumnEntityType.getAttribute("axistype"), localColumnEntityType.getAttribute("RawDatatype"),
+ localColumnEntityType.getAttribute("MeaQuantity")));
// OpenATFX can't handle multiple search conditions, so use just one...
Filter filter = Filter.and().add(
@@ -430,12 +509,14 @@
if (null == setColumnNames || setColumnNames.contains(columnName)) {
String columnId = result.getRecord(localColumnEntityType).getID();
- ColumnAttributes ca = new ColumnAttributes(columnName, columnId,
- (ValuesMode.CALCULATED == readRequest.getValuesMode() ? SequenceRepresentation.EXPLICIT
- : mapValues.get("SequenceRepresentation").extract()),
- new double[0], ((short) mapValues.get("IndependentFlag").extract() != 0),
- mapValues.get("axistype").extract(),
- mapValues.get("GlobalFlag").extractWithDefault(null));
+ SequenceRepresentation seqRep = ValuesMode.CALCULATED == readRequest.getValuesMode()
+ ? SequenceRepresentation.EXPLICIT
+ : mapValues.get("SequenceRepresentation").extract();
+
+ ColumnAttributes ca = new ColumnAttributes(columnName, columnId, seqRep, new double[0],
+ ((short) mapValues.get("IndependentFlag").extract() != 0), mapValues.get("axistype").extract(),
+ mapValues.get("GlobalFlag").extractWithDefault(null), mapValues.get("RawDatatype").extract(),
+ mapValues.get("MeaQuantity").extract());
mapColumnAttributes.put(mapValues.get("Id").extract(), ca);
}
@@ -456,10 +537,59 @@
}
}
+ applyUnitNamesAndDataTypesFallback(mapColumnAttributes.values());
+
return mapColumnAttributes;
}
/**
+ * Loads and sets the unit property of the given ColumnAttributes. This method
+ * does not use Queries with joins as openATFX does not support it. Instead, two
+ * queries are used. (LocalColumn.Id, LocalColumn.MeaQuantity) ->
+ * (MeaQuantity.Id, MeaQuantity.Unit) -> (Unit.Id, Unit.Name)
+ *
+ * @param mapColumnAttributes ColumnAttributes to set the unit.
+ */
+ private void applyUnitNamesAndDataTypesFallback(Collection<ColumnAttributes> columnAttributes) {
+ EntityType meaQuantityEntityType = modelManager.getEntityType(Channel.class);
+
+ Query queryMq = queryService.createQuery().select(meaQuantityEntityType.getIDAttribute(),
+ meaQuantityEntityType.getAttribute("DataType"), meaQuantityEntityType.getAttribute("Unit"));
+ Filter filterMq = Filter.and().add(ComparisonOperator.IN_SET.create(meaQuantityEntityType.getIDAttribute(),
+ columnAttributes.stream().map(ca -> ca.getMeaQuantityId()).toArray(String[]::new)));
+
+ Map<String, String> mqId2unitId = new HashMap<>();
+ Map<String, ScalarType> mqId2datatype = new HashMap<>();
+
+ for (Result result : queryMq.fetch(filterMq)) {
+ Map<String, Value> mapValues = result.getRecord(meaQuantityEntityType).getValues();
+ mqId2unitId.put(result.getRecord(meaQuantityEntityType).getID(), mapValues.get("Unit").extract());
+ mqId2datatype.put(result.getRecord(meaQuantityEntityType).getID(), mapValues.get("DataType").extract());
+ }
+
+ EntityType unitEntityType = modelManager.getEntityType(Unit.class);
+
+ Query queryUnit = queryService.createQuery().select(unitEntityType.getIDAttribute(),
+ unitEntityType.getNameAttribute());
+ Filter filterUnit = Filter.and().add(ComparisonOperator.IN_SET.create(unitEntityType.getIDAttribute(),
+ mqId2unitId.values().stream().distinct().toArray(String[]::new)));
+
+ Map<String, String> mqId2unitName = new HashMap<>();
+
+ for (Result result : queryUnit.fetch(filterUnit)) {
+ Map<String, Value> mapValues = result.getRecord(unitEntityType).getValues();
+
+ mqId2unitName.put(result.getRecord(unitEntityType).getID(), mapValues.get("Name").extract());
+ }
+
+ columnAttributes.forEach(ca -> {
+ String unitId = mqId2unitId.get(ca.getMeaQuantityId());
+ ca.setUnit(mqId2unitName.get(unitId));
+ ca.setDataType(mqId2datatype.get(ca.getMeaQuantityId()));
+ });
+ }
+
+ /**
* Returns the {@link ValueMatrix} CORBA service object associated with given
* {@link ReadRequest}.
*
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
index 9e3a884..4f97478 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/transaction/WriteRequestHandler.java
@@ -131,8 +131,9 @@
values.get(AE_LC_ATTR_AXISTYPE).set(writeRequest.getAxisType());
values.get(AE_LC_ATTR_PARAMETERS).set(writeRequest.getGenerationParameters());
+ ValueType<?> valueType = writeRequest.getRawScalarType().toValueType();
+
if (writeRequest.hasValues()) {
- ValueType<?> valueType = writeRequest.getRawScalarType().toValueType();
String unitName = writeRequest.getChannel().getUnit().getName();
values.put(AE_LC_ATTR_VALUES,
valueType.create(AE_LC_ATTR_VALUES, unitName, true, writeRequest.getValues()));
@@ -152,6 +153,9 @@
IntStream.range(0, genParamD.length)
.forEach(i -> genParamD[i] = ((Number) Array.get(genParamValues, i)).doubleValue());
values.get(AE_LC_ATTR_PARAMETERS).set(genParamD);
+
+ // remove GenerationParameters from Values
+ values.put(AE_LC_ATTR_VALUES, valueType.create(AE_LC_ATTR_VALUES));
}
// flags
@@ -162,9 +166,10 @@
values.get(AE_LC_ATTR_FLAGS).set(flags);
}
} else if (writeRequest.hasExternalComponents()) {
- // No values to write (ext comps are used instead), so remove Values attribute from map:
- values.remove(AE_LC_ATTR_VALUES);
-
+ // No values to write (ext comps are used instead), but we have to set the
+ // Values attribute to empty, based on the rawDataType
+ values.put(AE_LC_ATTR_VALUES, valueType.create(AE_LC_ATTR_VALUES));
+
// Set global flag as specified in the WriteRequest:
values.get(AE_LC_ATTR_GLOBAL_FLAG).set(writeRequest.getGlobalFlag());
@@ -172,15 +177,17 @@
.getEntityType("ExternalComponent");
List<Attribute> listAttrsExtComp = externalComponentEntityType.getAttributes();
- boolean hasMimeType = (listAttrsExtComp.stream().filter(a -> Entity.ATTR_MIMETYPE.equals(a.getName())).count() > 0);
- boolean hasBitCount = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITCOUNT.equals(a.getName())).count() > 0);
- boolean hasBitOffset = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITOFFSET.equals(a.getName())).count() > 0);
+ boolean hasMimeType = (listAttrsExtComp.stream().filter(a -> Entity.ATTR_MIMETYPE.equals(a.getName()))
+ .count() > 0);
+ boolean hasBitCount = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITCOUNT.equals(a.getName()))
+ .count() > 0);
+ boolean hasBitOffset = (listAttrsExtComp.stream().filter(a -> AE_EC_ATTR_BITOFFSET.equals(a.getName()))
+ .count() > 0);
int ordinalNumber = 1;
for (ExternalComponent extComp : writeRequest.getExternalComponents()) {
Core extCompCore = new DefaultCore(externalComponentEntityType);
- ExternalComponentEntity extCompEntity = new ExternalComponentEntity(
- extCompCore);
+ ExternalComponentEntity extCompEntity = new ExternalComponentEntity(extCompCore);
extCompEntity.setName(writeRequest.getChannel().getName());
if (hasMimeType) {
extCompEntity.setMimeType(new MimeType("application/x-asam.aoexternalcomponent"));
@@ -203,7 +210,8 @@
}
core.getChildrenStore().add(extCompEntity);
- extCompCore.getPermanentStore().set(new BaseEntity(core) {});
+ extCompCore.getPermanentStore().set(new BaseEntity(core) {
+ });
}
} else {
throw new IllegalStateException("Given write request neither has measured values nor external components");
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
index 1ecf311..dd023e6 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSConverter.java
@@ -758,8 +758,8 @@
}
if (null == columnAttributes) {
- columnAttributes = new ReadRequestHandler.ColumnAttributes("", null, null, new double[0], false, null,
- null);
+ columnAttributes = new ReadRequestHandler.ColumnAttributes("", null, null, new double[0], false, null, null,
+ null, null);
}
return scalarType.createMeasuredValues(odsMeasuredValues.valName, odsMeasuredValues.unit,
@@ -775,46 +775,18 @@
* @return The converted {@code MeasuredValues} is returned.
* @throws DataAccessException Thrown on conversion errors.
*/
- public static MeasuredValues fromExternalComponents(List<ExternalComponent> externalComponents, DataType dataType,
- String unit, ReadRequestHandler.ColumnAttributes columnAttributes) throws DataAccessException {
- ScalarType scalarType;
-
- if (DataType.DT_STRING == dataType) {
- scalarType = ScalarType.STRING;
- } else if (DataType.DT_DATE == dataType) {
- scalarType = ScalarType.DATE;
- } else if (DataType.DT_BOOLEAN == dataType) {
- scalarType = ScalarType.BOOLEAN;
- } else if (DataType.DT_BYTE == dataType) {
- scalarType = ScalarType.BYTE;
- } else if (DataType.DT_SHORT == dataType) {
- scalarType = ScalarType.SHORT;
- } else if (DataType.DT_LONG == dataType) {
- scalarType = ScalarType.INTEGER;
- } else if (DataType.DT_LONGLONG == dataType) {
- scalarType = ScalarType.LONG;
- } else if (DataType.DT_FLOAT == dataType) {
- scalarType = ScalarType.FLOAT;
- } else if (DataType.DT_DOUBLE == dataType) {
- scalarType = ScalarType.DOUBLE;
- } else if (DataType.DT_BYTESTR == dataType) {
- scalarType = ScalarType.BYTE_STREAM;
- } else if (DataType.DT_COMPLEX == dataType) {
- scalarType = ScalarType.FLOAT_COMPLEX;
- } else if (DataType.DT_DCOMPLEX == dataType) {
- scalarType = ScalarType.DOUBLE_COMPLEX;
- } else if (DataType.DT_EXTERNALREFERENCE == dataType) {
- scalarType = ScalarType.FILE_LINK;
- } else {
- throw new DataAccessException(
- "Conversion for ODS measured points of type '" + dataType.toString() + "' does not exist.");
- }
-
+ public static MeasuredValues fromExternalComponents(List<ExternalComponent> externalComponents,
+ ReadRequestHandler.ColumnAttributes columnAttributes) throws DataAccessException {
if (null == columnAttributes) {
throw new DataAccessException("No ColumnAttributes provided.");
}
- return scalarType.createMeasuredValues(columnAttributes.getName(), unit,
+ ScalarType scalarType = columnAttributes.getRawDataType();
+ if (scalarType == ScalarType.UNKNOWN) {
+ scalarType = columnAttributes.getDataType();
+ }
+
+ return scalarType.createMeasuredValues(columnAttributes.getName(), columnAttributes.getUnit(),
columnAttributes.getSequenceRepresentation(), columnAttributes.getGenerationParameters(),
columnAttributes.isIndependentColumn(), columnAttributes.getAxisType(),
columnAttributes.getGlobalFlag(), externalComponents);
diff --git a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSEnumerations.java b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSEnumerations.java
index 36b686f..a40ad9e 100644
--- a/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSEnumerations.java
+++ b/api/odsadapter/src/main/java/org/eclipse/mdm/api/odsadapter/utils/ODSEnumerations.java
@@ -190,7 +190,7 @@
} else if (constant instanceof ScalarType) {
return toODSScalarType((ScalarType) constant);
} else if (constant instanceof VersionState || constant instanceof Interpolation || constant instanceof AxisType
- || constant instanceof TypeSpecification) {
+ || constant instanceof TypeSpecification || constant instanceof ODSEnum) {
// NOTE: Ordinal numbers map directly to the corresponding ODS
// enumeration constant value.
return constant.ordinal();
diff --git a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/ODSAdapterExtCompTest.java b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/ODSAdapterExtCompTest.java
index 00d7d20..4f4d4bd 100644
--- a/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/ODSAdapterExtCompTest.java
+++ b/api/odsadapter/src/test/java/org/eclipse/mdm/api/odsadapter/ODSAdapterExtCompTest.java
@@ -398,46 +398,26 @@
FileLink fileLinkFlags2 = FileLink.newLocal(fileFlags2.toPath());
wrb = WriteRequest.create(channelGroup, channels.get(3), AxisType.Y_AXIS);
- writeRequests.add(wrb.explicit().externalComponents(
- ScalarType.BYTE,
- Lists.newArrayList((new ExternalComponent()).setTypeSpecification(TypeSpecification.BYTE)
- .setLength(5)
- .setStartOffset(0)
- .setBlocksize(5)
- .setValuesPerBlock(5)
- .setValueOffset(0)
- .setFileLink(fileLinkValues1)
- .setFlagsStartOffset(0L)
- .setFlagsFileLink(fileLinkFlags1),
- (new ExternalComponent()).setTypeSpecification(TypeSpecification.BYTE)
- .setLength(5)
- .setStartOffset(0)
- .setBlocksize(5)
- .setValuesPerBlock(5)
- .setValueOffset(0)
- .setFileLink(fileLinkValues2)
- .setFlagsStartOffset(0L)
- .setFlagsFileLink(fileLinkFlags2)))
+ writeRequests.add(wrb.explicitExternal().externalComponents(ScalarType.BYTE, Lists.newArrayList(
+ (new ExternalComponent()).setTypeSpecification(TypeSpecification.BYTE).setLength(5)
+ .setStartOffset(0).setBlocksize(5).setValuesPerBlock(5).setValueOffset(0)
+ .setFileLink(fileLinkValues1).setFlagsStartOffset(0L).setFlagsFileLink(fileLinkFlags1),
+ (new ExternalComponent()).setTypeSpecification(TypeSpecification.BYTE).setLength(5)
+ .setStartOffset(0).setBlocksize(5).setValuesPerBlock(5).setValueOffset(0)
+ .setFileLink(fileLinkValues2).setFlagsStartOffset(0L).setFlagsFileLink(fileLinkFlags2)))
.build());
wrb = WriteRequest.create(channelGroup, channels.get(4), AxisType.Y_AXIS);
- writeRequests.add(wrb.explicit().externalComponents(
- ScalarType.INTEGER,
- Lists.newArrayList((new ExternalComponent()).setTypeSpecification(TypeSpecification.INTEGER)
- .setLength(5)
- .setStartOffset(5L)
- .setBlocksize(20)
- .setValuesPerBlock(5)
- .setValueOffset(0)
- .setFileLink(fileLinkValues1),
- (new ExternalComponent()).setTypeSpecification(TypeSpecification.INTEGER)
- .setLength(5)
- .setStartOffset(5L)
- .setBlocksize(20)
- .setValuesPerBlock(5)
- .setValueOffset(0)
- .setFileLink(fileLinkValues2)))
- .build());
+ writeRequests
+ .add(wrb.explicitExternal()
+ .externalComponents(ScalarType.INTEGER, Lists.newArrayList(
+ (new ExternalComponent()).setTypeSpecification(TypeSpecification.INTEGER)
+ .setLength(5).setStartOffset(5L).setBlocksize(20).setValuesPerBlock(5)
+ .setValueOffset(0).setFileLink(fileLinkValues1),
+ (new ExternalComponent()).setTypeSpecification(TypeSpecification.INTEGER)
+ .setLength(5).setStartOffset(5L).setBlocksize(20).setValuesPerBlock(5)
+ .setValueOffset(0).setFileLink(fileLinkValues2)))
+ .build());
} catch (IOException exc) {
}
diff --git a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportResource.java b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportResource.java
index 1c93e87..1634346 100644
--- a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportResource.java
+++ b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ExportResource.java
@@ -14,7 +14,6 @@
package org.eclipse.mdm.apicopy.boundary;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -23,6 +22,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -66,17 +66,21 @@
@POST
@Consumes(MediaType.APPLICATION_XML)
- @Produces(MediaType.APPLICATION_XML)
+ @Produces({ MediaType.APPLICATION_XML, "application/zip" })
public Response exportAtfx(ShoppingBasket basket) throws IOException {
final Path exportAtfx = exportService.exportAtfx(basket);
- List<File> files = Files.list(exportAtfx).map(p -> p.toFile()).collect(Collectors.toList());
- if (files.size() == 1) {
+ final List<Path> paths;
+ try (Stream<Path> files = Files.walk(exportAtfx)) {
+ paths = files.filter(p -> p.toFile().isFile()).collect(Collectors.toList());
+ }
+
+ if (paths.size() == 1) {
StreamingOutput output = new StreamingOutput() {
@Override
public void write(OutputStream out) throws IOException {
- try (final InputStream responseStream = new FileInputStream(files.get(0))) {
+ try (final InputStream responseStream = Files.newInputStream(paths.get(0))) {
int length;
byte[] buffer = new byte[1024];
while ((length = responseStream.read(buffer)) != -1) {
@@ -96,16 +100,16 @@
}
};
- return Response.ok(output, MediaType.APPLICATION_XML).build();
+ return Response.ok(output, MediaType.APPLICATION_XML)
+ .header("content-disposition", "attachment; filename = export.atfx").build();
} else {
StreamingOutput out = new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
try (ZipOutputStream out = new ZipOutputStream(output)) {
- List<File> files = Files.list(exportAtfx).map(p -> p.toFile()).collect(Collectors.toList());
- for (File f : files) {
- out.putNextEntry(new ZipEntry(f.getName()));
- try (InputStream in = new FileInputStream(f)) {
+ for (Path p : paths) {
+ out.putNextEntry(new ZipEntry(exportAtfx.relativize(p).toString()));
+ try (InputStream in = Files.newInputStream(p)) {
int read = 0;
byte[] bytes = new byte[1024];
@@ -114,8 +118,6 @@
}
}
}
- } catch (Exception e) {
- e.printStackTrace();
} finally {
if (exportAtfx != null) {
try {
@@ -129,7 +131,8 @@
}
}
};
- return Response.ok(out, "application/zip").build();
+ return Response.ok(out, "application/zip")
+ .header("content-disposition", "attachment; filename = export.zip").build();
}
}
@@ -140,6 +143,8 @@
* @throws IOException
*/
private void deleteDirectory(java.nio.file.Path pathToBeDeleted) throws IOException {
- Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ try (Stream<Path> stream = Files.walk(pathToBeDeleted)) {
+ stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ }
}
}
diff --git a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ImportService.java b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ImportService.java
index d6db379..4a7a944 100644
--- a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ImportService.java
+++ b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/boundary/ImportService.java
@@ -21,6 +21,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import java.util.stream.Stream;
import javax.ejb.Stateless;
import javax.inject.Inject;
@@ -130,7 +131,9 @@
* @throws IOException
*/
private void deleteDirectory(java.nio.file.Path pathToBeDeleted) throws IOException {
- Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ try (Stream<Path> paths = Files.walk(pathToBeDeleted)) {
+ paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ }
}
/**
diff --git a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
index 417dfca..4d933d6 100644
--- a/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
+++ b/nucleus/apicopy/src/main/java/org/eclipse/mdm/apicopy/control/TransferBase.java
@@ -403,15 +403,11 @@
}
}
- if (SequenceRepresentation.EXPLICIT.equals(seqRep) || SequenceRepresentation.EXPLICIT_EXTERNAL.equals(seqRep)) {
+ if (SequenceRepresentation.EXPLICIT.equals(seqRep)) {
builder = wrb.explicit();
-
- if (SequenceRepresentation.EXPLICIT_EXTERNAL.equals(seqRep)) {
-
- return castBuilder(builder, AnyTypeValuesBuilder.class)
- .externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
- .build();
- }
+ } else if (SequenceRepresentation.EXPLICIT_EXTERNAL.equals(seqRep)) {
+ return wrb.explicitExternal().externalComponents(scalarType, measuredValues.getExternalComponents())
+ .independent(independent).build();
} else if (SequenceRepresentation.IMPLICIT_CONSTANT.equals(seqRep)) {
checkGenerationParameters(generationParameters, 1);
@@ -427,38 +423,36 @@
return wrb
.implicitSaw(scalarType, generationParameters[0], generationParameters[1], generationParameters[2])
.build();
- } else if (SequenceRepresentation.RAW_LINEAR.equals(seqRep)
- || SequenceRepresentation.RAW_LINEAR_EXTERNAL.equals(seqRep)) {
+ } else if (SequenceRepresentation.RAW_LINEAR.equals(seqRep)) {
checkGenerationParameters(generationParameters, 2);
builder = wrb.rawLinear(generationParameters[0], generationParameters[1]);
- if (SequenceRepresentation.RAW_LINEAR_EXTERNAL.equals(seqRep)) {
- return castBuilder(builder, AnyTypeValuesBuilder.class)
- .externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
- .build();
- }
- } else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED.equals(seqRep)
- || SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL.equals(seqRep)) {
+ } else if (SequenceRepresentation.RAW_LINEAR_EXTERNAL.equals(seqRep)) {
+ checkGenerationParameters(generationParameters, 2);
+
+ return wrb.rawLinearExternal(generationParameters[0], generationParameters[1])
+ .externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
+ .build();
+ } else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED.equals(seqRep)) {
checkGenerationParameters(generationParameters, 3);
builder = wrb.rawLinearCalibrated(generationParameters[0], generationParameters[1],
generationParameters[2]);
+ } else if (SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL.equals(seqRep)) {
+ checkGenerationParameters(generationParameters, 3);
- if (SequenceRepresentation.RAW_LINEAR_CALIBRATED_EXTERNAL.equals(seqRep)) {
- return castBuilder(builder, AnyTypeValuesBuilder.class)
- .externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
- .build();
- }
- } else if (SequenceRepresentation.RAW_POLYNOMIAL.equals(seqRep)
- || SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL.equals(seqRep)) {
+ return wrb
+ .rawLinearCalibratedExternal(generationParameters[0], generationParameters[1],
+ generationParameters[2])
+ .externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
+ .build();
+ } else if (SequenceRepresentation.RAW_POLYNOMIAL.equals(seqRep)) {
builder = wrb.rawPolynomial(generationParameters);
-
- if (SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL.equals(seqRep)) {
- return castBuilder(builder, AnyTypeValuesBuilder.class)
- .externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
- .build();
- }
+ } else if (SequenceRepresentation.RAW_POLYNOMIAL_EXTERNAL.equals(seqRep)) {
+ return wrb.rawPolynomialExternal(generationParameters)
+ .externalComponents(scalarType, measuredValues.getExternalComponents()).independent(independent)
+ .build();
}
if (scalarType.isString()) {
@@ -629,7 +623,7 @@
}
<T extends NumericalValuesBuilder> T castBuilder(NumericalValuesBuilder builder, Class<T> cls) {
- if (!cls.isInstance(builder)) {
+ if (!builder.getClass().isAssignableFrom(cls)) {
throw new IllegalStateException(String.format(
"Error creating the write values builder, expected class is %s, actual class is %s (likely column data type and sequence representation mismatch)!",
cls.getName(), (null == builder ? "???" : builder.getClass().getName())));
diff --git a/nucleus/apicopy/src/main/resources/emptyAtfx.xml b/nucleus/apicopy/src/main/resources/emptyAtfx.xml
index a6a14e7..a40e264 100644
--- a/nucleus/apicopy/src/main/resources/emptyAtfx.xml
+++ b/nucleus/apicopy/src/main/resources/emptyAtfx.xml
@@ -4338,7 +4338,7 @@
<application_attribute>
<name>FlagsStartOffset</name>
<base_attribute>flags_start_offset</base_attribute>
- <datatype>DT_LONG</datatype>
+ <datatype>DT_LONGLONG</datatype>
<autogenerate>false</autogenerate>
<obligatory>false</obligatory>
<unique>false</unique>
diff --git a/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/FileUtils.java b/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/FileUtils.java
index 19e8542..3494439 100644
--- a/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/FileUtils.java
+++ b/nucleus/apicopy/src/test/java/org/eclipse/mdm/apicopy/FileUtils.java
@@ -22,6 +22,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
+import java.util.stream.Stream;
public class FileUtils {
/**
@@ -49,6 +50,8 @@
* @throws IOException
*/
public static void deleteDirectory(java.nio.file.Path pathToBeDeleted) throws IOException {
- Files.walk(pathToBeDeleted).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ try (Stream<Path> paths = Files.walk(pathToBeDeleted)) {
+ paths.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ }
}
}
diff --git a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
index 3eaf779..f41fb33 100644
--- a/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
+++ b/nucleus/businessobjects/src/main/java/org/eclipse/mdm/businessobjects/service/ContextService.java
@@ -58,6 +58,7 @@
import io.vavr.collection.HashMap;
import io.vavr.collection.List;
import io.vavr.collection.Map;
+import io.vavr.control.Option.Some;
import io.vavr.control.Try;
/**
@@ -400,26 +401,38 @@
return ec;
}
+ @SuppressWarnings("unchecked")
private java.util.Map<ContextType, ContextRoot> updateContextDescribableContext(
ContextDescribable contextDescribable, Map<ContextType, Object> rootMap, ContextType[] contextTypes) {
java.util.Map<ContextType, ContextRoot> existingRootMap = getEntityManager(contextDescribable.getSourceName())
.loadContexts(contextDescribable, contextTypes);
for (ContextType contextType : contextTypes) {
- ContextRoot existingRoot = existingRootMap.computeIfAbsent(contextType,
- ct -> createContextRoot(contextDescribable, ct));
-
- rootMap.get(contextType).forEach(newContextRoot -> updateContextRoot(existingRoot, newContextRoot));
+ boolean hasValues;
+ Object obj = rootMap.get(contextType);
+ if (obj instanceof Some && ((Some) obj).get() instanceof ArrayList && !((Some<ArrayList<?>>) obj).get().isEmpty()) {
+ hasValues = true;
+ } else {
+ hasValues = false;
+ }
+ ContextRoot existingRoot = existingRootMap.computeIfAbsent(contextType,
+ ct -> createContextRoot(contextDescribable, ct, hasValues));
+ rootMap.get(contextType).forEach(newContextRoot -> updateContextRoot(existingRoot, newContextRoot));
}
return existingRootMap;
}
- private ContextRoot createContextRoot(ContextDescribable contextDescribable, ContextType contextType) {
+ private ContextRoot createContextRoot(ContextDescribable contextDescribable, ContextType contextType, boolean hasValues) {
EntityFactory factory = getEntityFactory(contextDescribable.getSourceName());
TemplateRoot templateRoot = findTemplateRoot(contextDescribable, contextType);
- if (contextDescribable instanceof Measurement) {
+ if (templateRoot == null) {
+ if (hasValues) {
+ throw new MDMEntityAccessException("Found measurements for '" + contextType + "' without a Template defined!");
+ }
+ return null;
+ } else if (contextDescribable instanceof Measurement) {
return factory.createContextRoot((Measurement) contextDescribable, templateRoot);
} else if (contextDescribable instanceof TestStep) {
return factory.createContextRoot((TestStep) contextDescribable, templateRoot);
@@ -456,8 +469,7 @@
TemplateTestStep tpl = TemplateTestStep.of(testStep).orElseThrow(
() -> new MDMEntityAccessException("Cannot find TemplateTestStep for TestStep: " + testStep));
- return tpl.getTemplateRoot(contextType).orElseThrow(() -> new MDMEntityAccessException(
- "Cannot find TemplateRoot for ContextType " + contextType + " on Template TestStep " + tpl));
+ return tpl.getTemplateRoot(contextType).orElse(null);
}
public TestStep getTestStep(ContextDescribable contextDescribable) {
diff --git a/nucleus/businessobjects/src/main/resources/org/eclipse/mdm/businessobjects/control/i18n/locale/localization.properties b/nucleus/businessobjects/src/main/resources/org/eclipse/mdm/businessobjects/control/i18n/locale/localization.properties
index 3d78479..8455e99 100644
--- a/nucleus/businessobjects/src/main/resources/org/eclipse/mdm/businessobjects/control/i18n/locale/localization.properties
+++ b/nucleus/businessobjects/src/main/resources/org/eclipse/mdm/businessobjects/control/i18n/locale/localization.properties
@@ -53,6 +53,11 @@
ChannelGroup.MimeType = MimeType
ChannelGroup.SubMatrixNoRows = Rows
+SubMatrix = ChannelGroup
+SubMatrix.Name = Name
+SubMatrix.MimeType = MimeType
+SubMatrix.SubMatrixNoRows = Rows
+
Channel = Channel
Channel.Name = Name
Channel.Description = MimeType
@@ -60,4 +65,13 @@
Channel.Minimum = Minimum
Channel.Maximum = Maximum
Channel.Average = Average
-Channel.Deviation = Deviation
\ No newline at end of file
+Channel.Deviation = Deviation
+
+MeaQuantity = Channel
+MeaQuantity.Name = Name
+MeaQuantity.Description = MimeType
+MeaQuantity.DataType = DataType
+MeaQuantity.Minimum = Minimum
+MeaQuantity.Maximum = Maximum
+MeaQuantity.Average = Average
+MeaQuantity.Deviation = Deviation
\ No newline at end of file
diff --git a/nucleus/webclient/src/main/webapp/src/app/basket/basket.service.ts b/nucleus/webclient/src/main/webapp/src/app/basket/basket.service.ts
index 783a868..d9cc26b 100644
--- a/nucleus/webclient/src/main/webapp/src/app/basket/basket.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/basket/basket.service.ts
@@ -14,7 +14,7 @@
import {Injectable, Output, EventEmitter} from '@angular/core';
-import { Http, Response, Headers, RequestOptions } from '@angular/http';
+import { Http, Response, Headers, RequestOptions, ResponseContentType } from '@angular/http';
import {of as observableOf, Observable} from 'rxjs';
import {defaultIfEmpty, catchError, mergeMap, map} from 'rxjs/operators';
@@ -116,8 +116,7 @@
headers.append('Content-Type', 'application/xml');
return this.getBasketAsXml(basket)
- .flatMap(b => this.http.post(this._prop.getUrl('mdm/export'), b, { headers: headers }))
- .map(r => r.text());
+ .flatMap(b => this.http.post(this._prop.getUrl('mdm/export'), b, { responseType: ResponseContentType.Blob, headers: headers }));
}
getFileExtensions() {
diff --git a/nucleus/webclient/src/main/webapp/src/app/basket/mdm-basket.component.ts b/nucleus/webclient/src/main/webapp/src/app/basket/mdm-basket.component.ts
index 40fa2ba..2ef1da6 100644
--- a/nucleus/webclient/src/main/webapp/src/app/basket/mdm-basket.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/basket/mdm-basket.component.ts
@@ -309,9 +309,13 @@
exportItemsToAtfx(basket: MDMItem[]) {
let downloadContent = new Basket(this.basketName, basket);
- this._basketService.getBasketAsAtfx(downloadContent)
- .map(atfx => new Blob([atfx], { type: 'application/xml' }))
- .subscribe(atfx => this.saveAsFile(atfx, '.atfx'));
+ this._basketService.getBasketAsAtfx(downloadContent).pipe(
+ map(resp => {
+ var contentDisposition = resp.headers.get('content-disposition');
+ var filename = contentDisposition.split(';')[1].split('filename')[1].split('=')[1].trim();
+ return { filename: filename, data: resp.blob() }; })
+ )
+ .subscribe( (r: { filename: string, data: Blob }) => FileSaver.saveAs(r.data, r.filename));
}
saveAsFile(blob: Blob, fileExtension: string, defaultFilename = 'export') {
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/chartviewer.module.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/chartviewer.module.ts
index efd8d58..849fce2 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/chartviewer.module.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/chartviewer.module.ts
@@ -40,6 +40,7 @@
import { XyChartViewerToolbarComponent } from './components/xy-chart-viewer-toolbar/xy-chart-viewer-toolbar.component';
import { XyChartDataSelectionPanelComponent } from './components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component';
import { ConfirmationService } from 'primeng/api';
+import { TooltipModule } from 'primeng/primeng';
@NgModule({
imports: [
@@ -59,6 +60,7 @@
ListboxModule,
PanelModule,
ConfirmDialogModule,
+ TooltipModule
],
declarations: [
ChartViewerNavCardComponent,
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.html b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.html
index fcb98ad..fe12308 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/chartviewer/chart-viewer.component.html
@@ -14,7 +14,7 @@
<p-chart #lineChart type="line" [data]="data" [options]="options"></p-chart>
-<p-accordion>
+<!-- <p-accordion>
<p-accordionTab header="{{'chartviewer.request-options.options' | translate}}">
{{'chartviewer.request-options.from' | translate}} <input type="number" numbersOnly pInputText [(ngModel)]="rangeValues[0]" />
@@ -28,4 +28,4 @@
</div>
<p-button label="{{'chartviewer.request-options.apply' | translate}}" (onClick)="applySettings($event)"></p-button>
</p-accordionTab>
-</p-accordion>
+</p-accordion> -->
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.html b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.html
index 5101f26..3e4a3b4 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.html
@@ -66,10 +66,12 @@
</div>
<!-- Row 3 -->
- <div class="p-col-12" style="text-align: right;">
- <button class="btn btn-mdm" (click)="onApplySettings($event)">
- <span class="fa fa-check"></span>
- {{'chartviewer.request-options.apply' | translate}}
+ <div class="p-col-2 p-offset-8">
+ <button pButton class="btn-mdm" (click)="onResetSettings($event)" style="width: 100%;" icon="fa fa-undo" [label]="'chartviewer.request-options.reset' | translate" >
+ </button>
+ </div>
+ <div class="p-col-2">
+ <button pButton class="btn-mdm" (click)="onApplySettings($event)" style="width: 100%;" icon="fa fa-check" [label]="'chartviewer.request-options.apply' | translate" >
</button>
</div>
</div>
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.ts
index 556cdac..53ee8a2 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/request-options/request-options.component.ts
@@ -34,9 +34,9 @@
private maxRequestedValues = 10000;
public numberOfRows = 0;
- public numberOfChunks = 600;
+ public numberOfChunks: number;
public step: number;
- public previewEnabled = true;
+ public previewEnabled: boolean;
@Input()
public measurement: Measurement;
@@ -48,6 +48,7 @@
private chartService: ChartViewerDataService) { }
ngOnInit() {
+ this.initSettings();
this.reload();
this.emitRequestOptionsChanged();
}
@@ -59,6 +60,11 @@
}
}
+ private initSettings() {
+ this.numberOfChunks = 600;
+ this.previewEnabled = true;
+ }
+
private reload() {
if (this.measurement != undefined && this.measurement.channelGroups.length > 0) {
this.numberOfRows = this.measurement.getFirstChannelGroup().numberOfRows;
@@ -90,6 +96,12 @@
this.emitRequestOptionsChanged();
}
+ public onResetSettings(event: Event) {
+ this.initSettings();
+ this.reload();
+ this.emitRequestOptionsChanged();
+ }
+
private emitRequestOptionsChanged() {
this.onRequestOptionsChanged.emit(
new RequestOptions(this.rangeValues[1] - this.rangeValues[0], this.rangeValues[0] - 1, this.previewEnabled, this.numberOfChunks));
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.css b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.css
new file mode 100644
index 0000000..692ef09
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.css
@@ -0,0 +1,36 @@
+/********************************************************************************
+ * Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+.filter-container {
+ position: relative;
+}
+
+.filter-input-group {
+ line-height: 1;
+ position: absolute;
+ left: 2.3rem;
+ z-index: 99;
+ top: 2.4rem;
+ right: .25rem;
+}
+
+.filter-input-field {
+ width: calc(100% - 2.2rem);
+}
+
+p-listbox ::ng-deep .ui-listbox-header.ui-listbox-header-w-checkbox {
+ height: 2.5rem;
+ display: flex;
+ align-items: center;
+}
\ No newline at end of file
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.html b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.html
index d91ae6e..ffabd1f 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.html
@@ -11,15 +11,26 @@
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************-->
-
<div class="p-grid">
- <div class="p-col-12" style="margin-top: 4px">
+ <div class="p-col-12" style="margin-top: 4px;">
+ <!-- Filter for Listbox. Is overlay via absolute positioning. Enables regex filter, which is not available in prime component -->
+ <div class="filter-container">
+ <span class="filter-input-group">
+ <span class="ui-inputgroup">
+ <input type="text" pInputText [(ngModel)]="channelGroupFilter" [pTooltip]="'chartviewer.xy-chart-data-selection-panel.tooltip-regexp-filter' | translate"
+ [placeholder]="'chartviewer.xy-chart-data-selection-panel.filter' | translate" class="filter-input-field"
+ (keyup)="onFilterChannelGroups($event)"/>
+ <span class="ui-inputgroup-addon">
+ <span class="fa fa-filter"></span>
+ </span>
+ </span>
+ </span>
+ </div>
<p-listbox
- [options]="measurement.channelGroups"
+ [options]="channelGroupOptions"
[(ngModel)]="selectedChannelGroups"
multiple="multiple"
checkbox="checkbox"
- filter="filter"
optionLabel="name"
(onChange)="onSelectedChannelGroupsChanged($event)"
[ngClass]="{'hiddenList': hiddenGroups}"
@@ -36,18 +47,30 @@
</p-listbox>
</div>
<div class="p-col-12">
- <p-listbox [options]="yChannelOptions"
+ <!-- Filter for Listbox. Is overlay via absolute positioning. Enables regex filter, which is not available in prime component -->
+ <div class="filter-container">
+ <span class="filter-input-group">
+ <span class="ui-inputgroup">
+ <input type="text" pInputText [(ngModel)]="yChannelFilter" [pTooltip]="'chartviewer.xy-chart-data-selection-panel.tooltip-regexp-filter' | translate"
+ [placeholder]="'chartviewer.xy-chart-data-selection-panel.filter' | translate" class="filter-input-field"
+ (keyup)="onFilterYChannels($event)"/>
+ <span class="ui-inputgroup-addon">
+ <span class="fa fa-filter"></span>
+ </span>
+ </span>
+ </span>
+ </div>
+ <p-listbox [options]="yChannelOptionsFiltered"
[(ngModel)]="selectedYChannels"
multiple="multiple"
checkbox="checkbox"
- filter="filter"
optionLabel="name"
(onChange)="onSelectedYChannelsChanged($event)"
[ngClass]="{'hiddenList': hiddenYChannels}"
[style]="{'width':'100%'}"
[styleClass]="'ypanel'"
[listStyle]="{'height':'200px'}"
- [showToggleAll]="false">
+ [showToggleAll]="true">
<p-header>
<span class="icon channel"></span>
Y-{{'MeaQuantity' | mdmdatasourcetranslate}}
diff --git a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.ts b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.ts
index 31fe6da..663a013 100644
--- a/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/chartviewer/components/xy-chart-data-selection-panel/xy-chart-data-selection-panel.component.ts
@@ -20,7 +20,6 @@
import { ArrayUtilService } from 'src/app/core/services/array-util.service';
import { MDMNotificationService } from 'src/app/core/mdm-notification.service';
import { TYPE_MEASUREMENT, TYPE_CHANNELGROUP, TYPE_CHANNEL } from '../../model/constants';
-import { tap } from 'rxjs/operators';
import { Node } from '../../../navigator/node';
import {ConfirmationService} from 'primeng/api';
import { TranslateService } from '@ngx-translate/core';
@@ -31,7 +30,7 @@
@Component({
selector: 'mdm5-xy-chart-data-selection-panel',
templateUrl: './xy-chart-data-selection-panel.component.html',
- styleUrls: ['../../chart-viewer.style.css']
+ styleUrls: ['../../chart-viewer.style.css', './xy-chart-data-selection-panel.component.css']
})
export class XyChartDataSelectionPanelComponent implements OnInit, OnDestroy, OnChanges {
@@ -48,6 +47,9 @@
public yChannelOptions: Channel[] = [];
public xChannelOptions: {[key: string]: Channel[]} = {};
+ public channelGroupOptions: ChannelGroup[] = [];
+ public yChannelOptionsFiltered: Channel[] = [];
+
// channel(group) selection for chart
// !! change via setter to update cache for workarround !!
public selectedChannelGroups: ChannelGroup[] = [];
@@ -65,6 +67,10 @@
public hiddenGroups = false;
public hiddenYChannels = false;
+ // Filterstrings
+ public channelGroupFilter: string;
+ public yChannelFilter: string;
+
// for x-channel default value
private independentChannels = new Map<string, Channel>();
@@ -97,6 +103,9 @@
this.selectionChanged = false;
}
}
+ if(changes['measurement']) {
+ this.initChannelGroupOptions();
+ }
}
/**************** Html-template listeners *****************************/
@@ -169,6 +178,18 @@
this.hiddenYChannels = !this.hiddenYChannels;
}
+ public onFilterChannelGroups(event: KeyboardEvent ) {
+ this.initChannelGroupOptions();
+ }
+
+ public onFilterYChannels(event: KeyboardEvent ) {
+ this.filterYChannelOptions();
+ }
+
+
+
+ /**************** Private methods *****************************/
+
private handleDeselectChannelGroup(channelGroup: ChannelGroup) {
// remove rows
if (this.arrayUtil.isNotEmpty(this.selectedChannelRows)) {
@@ -196,11 +217,13 @@
if (node != undefined) {
switch (node.type) {
case TYPE_MEASUREMENT:
+ /** measurement is set via @input and thus new ChannelGroupoption are set set via ngOnChanges */
this.resetChannelData();
this.fireSelectionChanged();
break;
case TYPE_CHANNELGROUP:
this.cleanOldState();
+ this.initChannelGroupOptions();
this.addChannelGroupToSelection(node.id, this.measurement.findChannelGroup(node.id));
break;
case TYPE_CHANNEL:
@@ -315,6 +338,7 @@
this.yChannelOptions = this.yChannelOptions.concat(channels);
}
}
+ this.filterYChannelOptions()
}
/**
@@ -326,6 +350,7 @@
this.setSelectedYChannels([]);
this.xChannelOptions = {};
this.yChannelOptions = [];
+ this.yChannelOptionsFiltered = [];
}
/**
@@ -362,7 +387,7 @@
const index = this.selectedChannelRows.findIndex(row => row.yChannel.id === yChannel.id);
if (index > -1) {
this.selectedChannelRows.splice(index, 1);
- this.selectedChannelRows = [].concat(this.selectedChannelRows);
+ this.selectedChannelRows = [].concat(this.selectedChannelRows).sort((cs1, cs2) => cs1.yChannel.name.localeCompare(cs2.yChannel.name));
} else {
this.addRow(channelGroup, yChannel);
}
@@ -376,6 +401,7 @@
private addRow(channelGroup: ChannelGroup, yChannel: Channel) {
const row = new ChannelSelectionRow(channelGroup, yChannel, this.getDefaultXChannel(channelGroup));
this.selectedChannelRows.push(row);
+ this.selectedChannelRows.sort((cs1, cs2) => cs1.yChannel.name.localeCompare(cs2.yChannel.name))
}
/**
@@ -434,4 +460,29 @@
this.selectedChannelGroups = channelGroups;
this.lastChannelGroupSelection = channelGroups;
}
+
+ private initChannelGroupOptions() {
+ this.channelGroupOptions = this.filter(this.measurement.channelGroups, this.channelGroupFilter);
+ }
+
+ private filterYChannelOptions() {
+ this.yChannelOptionsFiltered = this.filter(this.yChannelOptions, this.yChannelFilter);
+ }
+
+ private filter<T extends {name: string}>(options: T[], filterString: string) {
+ let result: T[];
+ if(filterString != undefined && filterString !== '') {
+ try {
+ const regEx = new RegExp(filterString);
+ result = options.filter(opt => regEx != undefined ? regEx.test(opt.name) : false);
+ } catch {
+ result = options.filter(opt => opt.name.includes(filterString));
+ }
+ } else {
+ result = options;
+ }
+
+ return result.sort((c1, c2) => c1.name.localeCompare(c2.name));
+ }
+
}
diff --git a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
index d64ca38..e1e4f40 100644
--- a/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/details/components/mdm-detail-descriptive-data/mdm-detail-descriptive-data.component.ts
@@ -287,21 +287,26 @@
};
list.push(tmp);
}
-
- if (node.relations != null && node.relations.length > 0) {
- for (let relation of node.relations) {
- if (relation.ids != null && relation.ids.length > 0) {
- for (let id of relation.ids) {
- let nodes = this.getNodes(children, id);
- for (let n of nodes) {
- list.push(this.createTreeNode(n, children));
- }
- }
- }
+
+ let tplCompRel = node.relations.find(r => r.entityType === "TemplateComponent")
+ if (tplCompRel && tplCompRel.ids.length > 0) {
+ let nodes = this.getNodesWithTplCompId(children, tplCompRel.ids[0]);
+ for (let n of nodes) {
+ list.push(this.createTreeNode(n, children));
}
}
+
return list;
}
+
+ /**
+ * Searches for nodes which have a relation to the given TemplateComponent ID.
+ * @param nodes List with nodes to search
+ * @param tplCompId TemplateComponent ID
+ */
+ getNodesWithTplCompId(nodes: Node[], tplCompId: string) {
+ return nodes.filter(n => n.relations[0].parentId == tplCompId);
+ }
/**
* Method to create a tree structure from the flat context entity and attribute map
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/edit-searchFields.component.html b/nucleus/webclient/src/main/webapp/src/app/search/edit-searchFields.component.html
index 15cad3e..32cd04e 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/edit-searchFields.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/search/edit-searchFields.component.html
@@ -57,7 +57,8 @@
typeaheadOptionsLimit="15"
placeholder="{{ 'search.edit-searchfields.enter-search-text' | translate }}"
class="form-control"
- style="margin-bottom: 15px;">
+ style="margin-bottom: 15px;"
+ [title]="'search.edit-searchfields.tooltip-no-wildcards' | translate">
<searchattribute-tree [searchAttributes]="searchAttributes" [environments]="environments"></searchattribute-tree>
</div>
<div class="col-md-8" style="min-height: 50vh; max-height: 77vh; overflow-y:auto;">
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/filter.service.ts b/nucleus/webclient/src/main/webapp/src/app/search/filter.service.ts
index 64bef6b..bbddadd 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/filter.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/filter.service.ts
@@ -24,25 +24,37 @@
export enum Operator {
EQUALS,
+ NOT_EQUALS,
LESS_THAN,
GREATER_THAN,
- LIKE
+ LIKE,
+ GREATER_THAN_OR_EQUAL,
+ LESS_THAN_OR_EQUAL,
+ BETWEEN
}
export namespace OperatorUtil {
export function values() {
- return [ Operator.EQUALS, Operator.LESS_THAN, Operator.GREATER_THAN, Operator.LIKE ];
+ return Object.keys(Operator).filter(k => isNaN(+k)).map(key => Operator[key]);
}
export function toString(operator: Operator) {
switch (operator) {
case Operator.EQUALS:
return '=';
+ case Operator.NOT_EQUALS:
+ return '!=';
case Operator.LESS_THAN:
return '<';
case Operator.GREATER_THAN:
return '>';
case Operator.LIKE:
return 'like';
+ case Operator.GREATER_THAN_OR_EQUAL:
+ return '≥';
+ case Operator.LESS_THAN_OR_EQUAL:
+ return '≤';
+ case Operator.BETWEEN:
+ return 'between';
default:
return undefined;
}
@@ -51,12 +63,20 @@
switch (operator) {
case Operator.EQUALS:
return 'eq';
+ case Operator.NOT_EQUALS:
+ return 'ne';
case Operator.LESS_THAN:
return 'lt';
case Operator.GREATER_THAN:
return 'gt';
case Operator.LIKE:
return 'lk';
+ case Operator.GREATER_THAN_OR_EQUAL:
+ return 'ge';
+ case Operator.LESS_THAN_OR_EQUAL:
+ return 'le';
+ case Operator.BETWEEN:
+ return 'bw';
default:
return undefined;
}
@@ -71,18 +91,20 @@
valueType: string;
@Exclude()
+ isCurrentlyLinked: boolean;
+ @Exclude()
+ isCreatedViaLink: boolean;
+ @Exclude()
sortIndex: number;
- constructor(type: string, attribute: string, operator: Operator, value: string[], valueType?: string) {
+ constructor(type: string, attribute: string, operator: Operator, value: string[], valueType = 'string', isCurrentlyLinked = false, isCreatedViaLink = false) {
this.type = type;
this.attribute = attribute;
this.operator = operator;
this.value = value;
- if (valueType) {
- this.valueType = valueType.toLowerCase();
- } else {
- this.valueType = 'string';
- }
+ this.valueType = valueType.toLowerCase();
+ this.isCreatedViaLink = isCreatedViaLink;
+ this.isCurrentlyLinked = isCurrentlyLinked;
}
}
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.html b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.html
index 5252208..5cd0ddf 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.html
@@ -18,6 +18,40 @@
outline: 0;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(0, 0, 0,.6);
}
+p-togglebutton >>> .ui-button {
+ font-size: 14px;
+ margin: 0 3px;
+ color: #6c757d;
+ background-color: transparent;
+ background-image: none;
+ border-color: #6c757d;
+ height: 100%;
+ width: 2.4rem;
+ margin-top: -2px;
+}
+p-togglebutton ::ng-deep .ui-button-icon-only .ui-button-icon-left {
+ margin-top: -.45em;
+}
+p-togglebutton >>> .ui-button.ui-state-active {
+ /* color: #333333!important; */
+ color: #fff!important;
+ background-color: #a0a0a0;
+}
+
+p-togglebutton >>> .ui-button.ui-state-active >>> .ui-button-icon-left {
+ /* color: #333333!important; */
+ color: #fff!important;
+ background-color: #a0a0a0;
+}
+
+p-togglebutton >>> .ui-button:not(.ui-state-active).ui-state-focus {
+ border-color: #6c757d;
+ background-color: transparent!important;
+}
+
+p-togglebutton >>> .ui-button:not(.ui-state-active).ui-state-focus >>> .ui-button-icon-left {
+ color: #6c757d!important;
+}
/* :host /deep/ .ui-multiselect .ui-multiselect-label {
padding: .25em 2em .25em .25em;
@@ -36,7 +70,20 @@
</div>
<div class="col-sm">
<label for="search-resulttype" class="col-form-label">{{ 'search.mdm-search.lbl-result-type' | translate }}</label>
- <p-dropdown [options]="definitions" optionLabel="label" (onChange)="onResultTypeChane($event)" [style]="{'width': '100%'}"></p-dropdown>
+ <p-dropdown [options]="definitions" (onChange)="onResultTypeChanged($event)" [style]="{'width': '100%'}">
+ <ng-template let-option pTemplate="selectedItem">
+ <div style="display: flex; align-items: center;">
+ <img [src]="option?.type | typeIcon" style="padding-right: .5rem;"/>
+ <span>{{option?.label}}</span>
+ </div>
+ </ng-template>
+ <ng-template let-option pTemplate="item">
+ <div style="display: flex; align-items: center;">
+ <img [src]="option?.type | typeIcon" style="padding-right: .5rem;"/>
+ <span>{{option?.label}}</span>
+ </div>
+ </ng-template>
+ </p-dropdown>
</div>
</div>
<div class="row" style="margin-top: 5px; margin-bottom: 10px;">
@@ -62,6 +109,9 @@
<button type="button" class="btn btn-mdm" (click)="deleteFilter($event)" title="{{ 'search.mdm-search.tooltip-delete-search-filter' | translate }}"><span class="fa fa-times"></span></button>
</div>
<div class="col text-right">
+ <p-toggleButton [(ngModel)]="isSearchLinkedToNavigator" [onIcon]="'fa fa-link'" offIcon="fa fa-unlink" onLabel="" offLabel="" (onChange)="onToggleSearchLinkedToNavigator($event)"
+ [title]="(isSearchLinkedToNavigator ? 'search.mdm-search.tooltip-unlink-search-attributes-from-navigator' : 'search.mdm-search.tooltip-link-search-attributes-to-navigator')| translate">
+ </p-toggleButton>
<button type="button" class="btn btn-mdm" (click)="resetConditions($event)" title="{{ 'search.mdm-search.tooltip-reset-search-conditions' | translate }}" [disabled]=!isAdvancedSearchActive><span class="fa fa-eraser"></span></button>
<span class="fa" style="color: #333 !important; padding-left: 15px;" [ngClass]="{'fa-chevron-down': advancedSearch?.isOpen, 'fa-chevron-right': !advancedSearch?.isOpen}"></span>
</div>
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.ts b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.ts
index ca98675..a3e91cb 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.component.ts
@@ -16,11 +16,11 @@
import {Component, ViewChild, OnInit, OnDestroy} from '@angular/core';
import {SearchService, SearchDefinition, SearchAttribute, SearchLayout} from './search.service';
-import {of as observableOf, combineLatest as observableCombineLatest} from 'rxjs';
+import {of as observableOf, combineLatest as observableCombineLatest, Subscription} from 'rxjs';
import {defaultIfEmpty, mergeMap, tap} from 'rxjs/operators';
-import {FilterService, SearchFilter, Condition} from './filter.service';
+import {FilterService, SearchFilter, Condition, Operator} from './filter.service';
import {NodeService} from '../navigator/node.service';
import {BasketService} from '../basket/basket.service';
import {QueryService, SearchResult } from '../tableview/query.service';
@@ -45,6 +45,7 @@
import { TranslateService } from '@ngx-translate/core';
import { streamTranslate, TRANSLATE } from '../core/mdm-core.module';
+import { NavigatorService } from '@navigator/navigator.service';
@Component({
selector: 'mdm-search',
@@ -89,6 +90,9 @@
{ label: 'Add to shopping basket', icon: 'fa fa-shopping-cart', command: (event) => this.addSelectionToBasket() }
];
+ public isSearchLinkedToNavigator: false;
+ private linkedToNavigatorSub = new Subscription();
+
@ViewChild(ViewComponent)
viewComponent: ViewComponent;
@@ -117,7 +121,8 @@
private localService: LocalizationService,
private notificationService: MDMNotificationService,
private basketService: BasketService,
- private translateService: TranslateService) { }
+ private translateService: TranslateService,
+ private navigatorService: NavigatorService) { }
ngOnInit() {
this.currentFilter = this.filterService.EMPTY_FILTER;
@@ -146,6 +151,7 @@
ngOnDestroy() {
this.saveState();
+ this.linkedToNavigatorSub.unsubscribe();
}
init(envs: Node[], attrs: { [type: string]: { [env: string]: SearchAttribute[] }},
@@ -231,13 +237,12 @@
this.searchFields.splice(index, 1);
}
- onResultTypeChane(e: any) {
- this.selectResultType(e.value);
- }
-
- selectResultType(type: any) {
- this.currentFilter.resultType = type.type;
- this.updateSearchAttributesForCurrentResultType();
+ onResultTypeChanged(event: {value: string, originalEvent: Event}) {
+ const searchDef = this.definitions.find(sd => sd.value === event.value);
+ if (searchDef) {
+ this.currentFilter.resultType = searchDef.type;
+ this.updateSearchAttributesForCurrentResultType();
+ }
}
updateSearchAttributesForCurrentResultType() {
@@ -456,4 +461,45 @@
}
this.lazySelectedRow = this.selectedRow;
}
+
+ public onToggleSearchLinkedToNavigator(event: {checked: boolean, originalEvent: MouseEvent}) {
+ // prevent advanced-search-tab to toggle.
+ event.originalEvent.stopPropagation();
+
+ if(event.checked) {
+ const current = this.navigatorService.getSelectedNode();
+ if(current) {
+ this.addLinkedConditions(current);
+ }
+ this.linkedToNavigatorSub = this.navigatorService.selectedNodeChanged.subscribe(node => this.addLinkedConditions(node));
+ } else {
+ this.linkedToNavigatorSub.unsubscribe();
+ this.unlinkConditions();
+ }
+ }
+
+ private addLinkedConditions(node: Node) {
+ // remove other conditions created via link
+ const oldIndex = this.currentFilter.conditions.findIndex(cond => cond.isCreatedViaLink);
+ if (oldIndex > -1) {
+ this.currentFilter.conditions.splice(oldIndex, 1);
+ }
+ // unlink other conditions (the ones that where not created via link but got linked)
+ this.unlinkConditions();
+ // link the related condition or create linked condition if none exists
+ if (node.type != 'Environment') {
+ const linkedCondition = this.currentFilter.conditions.find(cond => cond.type === node.type && cond.attribute === 'Id');
+ if (linkedCondition) {
+ linkedCondition.value = [node.id];
+ linkedCondition.isCurrentlyLinked = true;
+ } else {
+ this.currentFilter.conditions.push(new Condition(node.type, 'Id', Operator.EQUALS, [node.id], 'string', true, true));
+ }
+ }
+ this.calcCurrentSearch();
+ }
+
+ private unlinkConditions() {
+ this.currentFilter.conditions.forEach(cond => cond.isCurrentlyLinked = false);
+ }
}
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.module.ts b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.module.ts
index 760c623..4473b42 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.module.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/mdm-search.module.ts
@@ -27,19 +27,25 @@
import {FilterService} from './filter.service';
import {SearchattributeTreeModule} from '../searchattribute-tree/searchattribute-tree.module';
-import {AutoCompleteModule} from 'primeng/primeng';
+import {AutoCompleteModule, InputTextModule, ToggleButtonModule} from 'primeng/primeng';
+import {TooltipModule} from 'primeng/tooltip';
+import { TypeIconPipe } from './pipes/type-icon.pipe';
@NgModule({
imports: [
MDMCoreModule,
TableViewModule,
SearchattributeTreeModule,
- AutoCompleteModule
+ AutoCompleteModule,
+ TooltipModule,
+ ToggleButtonModule,
+ InputTextModule
],
declarations: [
MDMSearchComponent,
SearchConditionComponent,
EditSearchFieldsComponent,
+ TypeIconPipe,
],
exports: [
MDMSearchComponent,
@@ -48,7 +54,8 @@
providers: [
SearchService,
FilterService,
- DatePipe
+ DatePipe,
+ TypeIconPipe
],
})
export class MDMSearchModule {
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.spec.ts b/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.spec.ts
new file mode 100644
index 0000000..fb1779a
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.spec.ts
@@ -0,0 +1,22 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+ import { TypeIconPipe } from './type-icon.pipe';
+
+describe('TypeIconPipe', () => {
+ it('create an instance', () => {
+ const pipe = new TypeIconPipe();
+ expect(pipe).toBeTruthy();
+ });
+});
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.ts b/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.ts
new file mode 100644
index 0000000..fd2ae37
--- /dev/null
+++ b/nucleus/webclient/src/main/webapp/src/app/search/pipes/type-icon.pipe.ts
@@ -0,0 +1,65 @@
+/********************************************************************************
+ * Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+import { Pipe, PipeTransform } from '@angular/core';
+import { SearchDefinition } from '../search.service';
+
+type nodeType = 'Environment' | 'Pool' | 'Project' | 'Test' | 'TestStep' | 'Measurement' | 'ChannelGroup' | 'Channel' | 'Quantity' | 'Unit';
+
+@Pipe({
+ name: 'typeIcon'
+})
+export class TypeIconPipe implements PipeTransform {
+
+ transform(value: nodeType | SearchDefinition): any {
+ let type: string;
+ if (value instanceof SearchDefinition) {
+ type = value.type
+ } else {
+ type = value;
+ }
+ let imagePath = 'assets/famfamfam_icons/';
+
+ switch (type as nodeType) {
+ case 'Environment':
+ imagePath += 'database.png';
+ break;
+ case 'Project':
+ imagePath += 'house.png';
+ break;
+ case 'Pool':
+ imagePath += 'paste_plain.png';
+ break;
+ case 'Test':
+ imagePath += 'brick_add.png';
+ break;
+ case 'TestStep':
+ imagePath += 'brick.png';
+ break;
+ case 'Measurement':
+ imagePath += 'chart_curve.png';
+ break;
+ case 'ChannelGroup':
+ imagePath += 'calendar.png';
+ break;
+ case 'Channel':
+ case 'Unit':
+ case 'Quantity':
+ imagePath += 'chart_curve_go.png';
+ break;
+ }
+ return imagePath;
+ }
+
+}
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.css b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.css
index 7cda7f7..dca279d 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.css
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.css
@@ -15,37 +15,32 @@
table.searchdefinition td { vertical-align: middle; padding: 4px 8px; }
-p-autoComplete >>> .ui-inputtext.ui-state-focus {
- border: 1px solid #66afe9 !important;
- outline: 0 !important;
- box-shadow: 0 0 8px rgba(102,175,233,.6) !important;
-}
-
-p-autoComplete >>> .ui-autocomplete.ui-autocomplete-multiple .ui-autocomplete-multiple-container {
- padding: 2.5px 8px !important;
- border: 1px solid #ccc;
- color: #555;
- box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
- width: 100%;
-}
-
-p-autoComplete >>> .ui-autocomplete-multiple-container.ui-inputtext {
+p-autoComplete ::ng-deep .ui-autocomplete-multiple-container.ui-inputtext {
overflow: inherit !important;
}
-p-autoComplete >>> .ui-autocomplete {
+p-autoComplete ::ng-deep ul {
width: 100%;
}
-p-autoComplete >>> .ui-widget:disabled{
- cursor: not-allowed !important;
- background-color: #eee;
- color: #555 !important;
- opacity: 1;
+p-autoComplete ::ng-deep .ui-widget:disabled,
+p-autoComplete ::ng-deep .ui-state-disabled,
+p-autoComplete ::ng-deep input {
+ opacity: .5;
}
-p-autoComplete >>> .ui-state-disabled{
- cursor: not-allowed !important;
- background-color: #eee !important;
- border-color: #e3e3e3 !important;
- opacity: 1;
+
+::ng-deep .ui-tooltip-text {
+ width: 20rem;
+}
+
+.linked-icon {
+ margin-left: 4px;
+ color:gray;
+}
+
+.linked-container {
+ display: inline-grid;
+ grid-template-columns: 1.8rem auto;
+ align-items: center;
+ width: 100%;
}
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.html b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.html
index deccb96..669e46e 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.html
@@ -12,10 +12,10 @@
*
********************************************************************************-->
-<td style="vertical-align: middle">{{condition.type | mdmdatasourcetranslate}}</td> <!-- | translate -->
+<td style="vertical-align: middle"><span>{{condition.type | mdmdatasourcetranslate}}</span></td> <!-- | translate -->
<td style="vertical-align: middle">{{condition.type | mdmdatasourcetranslate: condition.attribute }}</td> <!-- | translate: condition.attribute -->
<td style="vertical-align: middle">
- <div class="btn-group" dropdown style="width: 66px;">
+ <div class="btn-group" dropdown style="width: 6rem;">
<button id="operator" type="button" title="{{ 'search.search-condition.tooltip-select-search-operator' | translate }}" class="btn btn-default btn-sm dropdown-toggle" dropdownToggle aria-haspopup="true" aria-expanded="false" [disabled]="disabled">
{{getOperatorName(condition.operator)}} <span class="caret"></span>
</button>
@@ -27,29 +27,111 @@
</div>
</td>
<td [ngSwitch]="condition.valueType">
- <p-autoComplete *ngSwitchCase="'string'"
- [(ngModel)]="condition.value"
- [suggestions]="displayedSuggestions"
- (completeMethod)="updateSuggestions($event)"
- [multiple]="true"
- [delay]="0"
- [size]="500"
- [scrollHeight]="'50vh'"
- placeholder="Wert"
- [disabled]="disabled"
- (keyup.enter)="onEnter($event)">
- </p-autoComplete>
- <p-calendar *ngSwitchCase="'date'"
- (ngModelChange)="setDateValue($event)"
- [ngModel]="dateValue"
- dateFormat="dd.mm.yy"
- showTime="true"
- hourFormat="24"
- hideOnDateTimeSelect="true"
- [showIcon]="true"
- [disabled]="disabled"
- [ngStyle]="{ 'width': '100%' }">
- </p-calendar>
- <input *ngSwitchDefault type="text" class="form-control input-sm" placeholder="Wert" [value]="condition.value" (input)="setValue($event.target.value)" [disabled]="disabled">
+ <ng-container *ngSwitchCase="'string'">
+ <div [ngClass]="{'linked-container': condition?.isCurrentlyLinked}">
+ <span *ngIf="condition?.isCurrentlyLinked" class="fa fa-link linked-icon" pTooltip="{{ 'search.search-condition.condition-is-linked' | translate }}"></span>
+ <p-autoComplete #multi *ngIf="isHandlingMultipleValues"
+ [(ngModel)]="condition.value"
+ [suggestions]="displayedSuggestions"
+ (completeMethod)="updateSuggestions($event)"
+ [multiple]="true"
+ [delay]="0"
+ [size]="500"
+ [scrollHeight]="'50vh'"
+ placeholder="Wert"
+ [disabled]="disabled"
+ (keyup.enter)="onEnter($event, multi)"
+ (onBlur)="onAutocompleteBlur($event, multi)"
+ [style]="{ 'width': '100%'}"
+ [inputStyle]="{ 'width': '100%' }"
+ pTooltip="{{ 'search.search-condition.multi-value-input' | translate }}" tooltipPosition="top">
+ </p-autoComplete>
+ </div>
+ <p-autoComplete #first *ngIf="!isHandlingMultipleValues"
+ [(ngModel)]="stringValueStart"
+ [suggestions]="displayedSuggestions"
+ (completeMethod)="updateSuggestions($event)"
+ [multiple]="false"
+ [delay]="0"
+ [size]="500"
+ [scrollHeight]="'50vh'"
+ placeholder="Wert"
+ [disabled]="disabled"
+ (keyup.enter)="onEnter($event, first)"
+ (onBlur)="onAutocompleteBlur($event, first)"
+ [style]="{ 'width': isBinaryOperator ? 'calc(50% - 1.5rem)' : '100%'}"
+ [inputStyle]="{ 'width': '100%' }"
+ pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top">
+ </p-autoComplete>
+ <ng-container *ngIf="isBinaryOperator">
+ <span style="width: 3rem; text-align: center; display: inline-block;">{{'search.search-condition.and' | translate}}</span>
+ <p-autoComplete #second
+ [(ngModel)]="stringValueEnd"
+ [suggestions]="displayedSuggestions"
+ (completeMethod)="updateSuggestions($event)"
+ [multiple]="false"
+ [delay]="0"
+ [size]="500"
+ [scrollHeight]="'50vh'"
+ placeholder="Wert"
+ [disabled]="disabled"
+ (keyup.enter)="onEnter($event, second)"
+ (onBlur)="onAutocompleteBlur($event, second)"
+ [style]="{ 'width': 'calc(50% - 1.5rem)'}"
+ [inputStyle]="{ 'width': '100%' }"
+ pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top">
+ </p-autoComplete>
+ </ng-container>
+ </ng-container>
+
+ <ng-container *ngSwitchCase="'date'" >
+ <p-calendar
+ (ngModelChange)="setDateValue($event, 0)"
+ [ngModel]="dateValue"
+ dateFormat="dd.mm.yy"
+ showTime="true"
+ hourFormat="24"
+ hideOnDateTimeSelect="true"
+ [showIcon]="true"
+ [disabled]="disabled"
+ [style]="{ 'width': isBinaryOperator ? 'calc(50% - 1.5rem)' : '100%' }"
+ [inputStyle]="{ 'width': 'calc(100% - 2.2rem)' }"
+ pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top">
+ </p-calendar>
+ <ng-container *ngIf="isBinaryOperator">
+ <span style="width: 3rem; text-align: center; display: inline-block;">{{'search.search-condition.and' | translate}}</span>
+ <p-calendar *ngSwitchCase="'date'"
+ (ngModelChange)="setDateValue($event, 1)"
+ [ngModel]="dateValueEnd"
+ dateFormat="dd.mm.yy"
+ showTime="true"
+ hourFormat="24"
+ hideOnDateTimeSelect="true"
+ [showIcon]="true"
+ [disabled]="disabled"
+ [style]="{ 'width': 'calc(50% - 1.5rem)' }"
+ [inputStyle]="{ 'width': 'calc(100% - 2.2rem)' }"
+ pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top">
+ </p-calendar>
+ </ng-container>
+ </ng-container>
+
+ <ng-container *ngSwitchDefault>
+ <input pInputText type="text" placeholder="Wert"
+ [disabled]="disabled"
+ [(ngModel)]="stringValueStart"
+ (input)="setValue($event)"
+ pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top"
+ [ngStyle]="{ 'width': isBinaryOperator ? 'calc(50% - 1.5rem)' : '100%' }">
+ <ng-container *ngIf="isBinaryOperator">
+ <span style="width: 3rem; text-align: center; display: inline-block;">{{'search.search-condition.and' | translate}}</span>
+ <input pInputText type="text" placeholder="Wert"
+ [disabled]="disabled"
+ [(ngModel)]="stringValueEnd"
+ (input)="setValue($event)"
+ pTooltip="{{ 'search.search-condition.single-value-input' | translate }}" tooltipPosition="top"
+ [ngStyle]="{'width':'calc(50% - 1.5rem)'}">
+ </ng-container>
+ </ng-container>
</td>
<td style="vertical-align: middle"><button id="remove" type="button" class="btn btn-default btn-sm fa fa-times remove" (click)="remove()" [disabled]="disabled"></button></td>
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.ts b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.ts
index 0fabadb..a97fee6 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search-condition.component.ts
@@ -13,13 +13,11 @@
********************************************************************************/
-import {Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, ViewChild, AfterViewInit} from '@angular/core';
+import {Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, OnInit } from '@angular/core';
import {FormGroup} from '@angular/forms';
-import {LocalizationService} from '../localization/localization.service';
import {Condition, Operator, OperatorUtil} from './filter.service';
import {Node} from '../navigator/node';
-import {PropertyService} from '../core/property.service';
import {QueryService} from '../tableview/query.service';
import {AutoComplete} from 'primeng/primeng';
@@ -27,13 +25,17 @@
import {TranslateService} from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
+import { NavigatorService } from '@navigator/navigator.service';
+
@Component({
selector: '[search-condition]',
templateUrl: 'search-condition.component.html',
styleUrls: ['search-condition.component.css'],
})
-export class SearchConditionComponent implements OnChanges, AfterViewInit {
+export class SearchConditionComponent implements OnChanges, OnInit {
+
+ private readonly multiValueOperators = [Operator.LIKE, Operator.EQUALS, Operator.NOT_EQUALS];
@Input() env: string;
@Input() condition: Condition;
@@ -45,34 +47,27 @@
suggestions: string[];
displayedSuggestions: string[] = [];
lastQuery: string;
+
+ // date values for two input fields
dateValue: Date;
+ dateValueEnd: Date;
- @ViewChild(AutoComplete) primeAutoCompleteComponent: AutoComplete;
+ // string values for two input fields
+ stringValueStart: string = '';
+ stringValueEnd: string = '';
- constructor(private localservice: LocalizationService,
- private prop: PropertyService,
- private queryService: QueryService,
+ public isBinaryOperator = false;
+ public isHandlingMultipleValues = false;
+
+ constructor(private queryService: QueryService,
private notificationService: MDMNotificationService,
private translateService: TranslateService,
- private datePipe: DatePipe) { }
+ private datePipe: DatePipe,
+ private navigatorService: NavigatorService) { }
- ngAfterViewInit() {
- if (this.primeAutoCompleteComponent) {
- /* Workaround for missing onBlur handler in primeng version 2.x.
- * We overwrite the existing implementation and additional call our own event handler.
- * In later versions this feature was added https://github.com/primefaces/primeng/issues/2256
- * and this workaround should be removed when using primeng version 4 or later
- */
- this.primeAutoCompleteComponent.onBlur = function() {
- this.primeAutoCompleteComponent.focus = false;
- this.primeAutoCompleteComponent.onModelTouched();
- this.onAutocompleteBlur();
- }.bind(this);
- }
- }
-
- onAutocompleteBlur() {
- this.onEnter(new Event('blur'));
+ ngOnInit() {
+ // set default operator if empty and evaluates operator properties like multiple input, binary eg.
+ this.setOperator(this.condition.operator === undefined ? Operator.EQUALS : this.condition.operator);
}
ngOnChanges(changes: SimpleChanges) {
@@ -80,27 +75,46 @@
this.setAutoCompleteValues();
}
- if (this.condition.valueType === 'date') {
- console.log(this.condition.value[0]);
- if (this.condition.value === undefined || this.condition.value[0] === undefined) {
- this.dateValue = new Date();
- (<Date> this.dateValue).setHours(0, 0, 0, 0);
- } else {
- this.dateValue = new Date(this.condition.value[0]);
+ if (changes['condition']) {
+ // set initial date to today, otherwise primeNg calender breaks on undefined input.
+ if (this.condition.valueType === 'date') {
+ if (this.condition.value === undefined || this.condition.value[0] === undefined) {
+ this.dateValue = new Date();
+ this.dateValue.setHours(0, 0, 0, 0);
+ } else {
+ this.dateValue = new Date(this.condition.value[0]);
+ }
+ this.setDateValue(this.dateValue, 0);
+ if (this.condition.value === undefined || this.condition.value[1] === undefined) {
+ this.dateValueEnd = new Date();
+ this.dateValueEnd.setHours(0, 0, 0, 0);
+ this.dateValueEnd.setDate(this.dateValueEnd.getDate() + 1)
+ } else {
+ this.dateValueEnd = new Date(this.condition.value[1]);
+ }
+ this.setDateValue(this.dateValue, 1);
}
}
}
- onEnter(e: Event) {
- let hl = this.primeAutoCompleteComponent.highlightOption;
- if (hl == undefined && this.lastQuery !== '' || hl != undefined && this.displayedSuggestions.find(s => s === hl) == undefined) {
- if (this.primeAutoCompleteComponent.value[this.primeAutoCompleteComponent.value.length - 1] === hl) {
- this.primeAutoCompleteComponent.value.pop();
+ onAutocompleteBlur(event: Event, htmlElement: AutoComplete) {
+ this.onEnter(undefined, htmlElement);
+ }
+
+ onEnter(e: Event, autoComplete: AutoComplete) {
+ if(this.isHandlingMultipleValues) {
+ let hl = autoComplete.highlightOption;
+ if (hl == undefined && this.lastQuery != undefined && this.lastQuery !== '' || hl != undefined && this.displayedSuggestions.find(s => s === hl) == undefined) {
+ if (autoComplete.value[autoComplete.value.length - 1] === hl) {
+ autoComplete.value.pop();
+ }
+ autoComplete.selectItem(this.lastQuery);
+ this.lastQuery = '';
}
- this.primeAutoCompleteComponent.selectItem(this.lastQuery);
- this.lastQuery = '';
+ autoComplete.highlightOption = undefined;
+ } else {
+ this.condition.value = [this.stringValueStart, this.stringValueEnd];
}
- this.primeAutoCompleteComponent.highlightOption = undefined;
this.displayedSuggestions = [];
}
@@ -133,6 +147,20 @@
setOperator(operator: Operator) {
this.condition.operator = operator;
+ this.isBinaryOperator = operator === Operator.BETWEEN;
+ this.isHandlingMultipleValues = this.multiValueOperators.some(op => op === operator);
+ this.adjustInput();
+ }
+
+ private adjustInput() {
+ if (this.condition.valueType === 'string') {
+ if (!this.isHandlingMultipleValues) {
+ this.stringValueStart = this.condition.value[0];
+ this.stringValueEnd = this.condition.value[1];
+ } else {
+ this.condition.value = this.condition.value.filter(v => v != undefined);
+ }
+ }
}
setValue(value: string) {
@@ -143,9 +171,11 @@
this.condition.value = value;
}
- setDateValue(value: Date) {
- console.log(value);
- this.condition.value = [this.datePipe.transform(value, 'yyyy-MM-dd' + 'T' + 'HH:mm:ss')];
+ setDateValue(value: Date, index: number) {
+ if (this.condition.value == undefined) {
+ this.condition.value = [];
+ }
+ this.condition.value[index] = this.datePipe.transform(value, 'yyyy-MM-dd' + 'T' + 'HH:mm:ss');
}
remove() {
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search.service.spec.ts b/nucleus/webclient/src/main/webapp/src/app/search/search.service.spec.ts
index 77c1c18..d43f278 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search.service.spec.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search.service.spec.ts
@@ -145,7 +145,7 @@
let filter = service.convertEnv('env1', [cond1, cond2, cond3], attributes, 'test');
expect(filter.sourceName).toEqual('env1');
- expect(filter.filter).toEqual('Test.Name ci_lk "PBN*" and Vehicle.Number eq 12 and Vehicle.Created eq "2017-07-17T12:13:14"');
+ expect(filter.filter).toEqual('( Test.Name ci_lk "PBN*" ) and ( Vehicle.Number eq 12 ) and ( Vehicle.Created eq "2017-07-17T12:13:14" )');
expect(filter.searchString).toEqual('test');
})));
diff --git a/nucleus/webclient/src/main/webapp/src/app/search/search.service.ts b/nucleus/webclient/src/main/webapp/src/app/search/search.service.ts
index 1a461e0..e10499e 100644
--- a/nucleus/webclient/src/main/webapp/src/app/search/search.service.ts
+++ b/nucleus/webclient/src/main/webapp/src/app/search/search.service.ts
@@ -25,7 +25,7 @@
import { Preference, PreferenceService, Scope } from '../core/preference.service';
import {HttpErrorHandler} from '../core/http-error-handler';
-import {SearchFilter, Condition, OperatorUtil} from './filter.service';
+import {SearchFilter, Condition, OperatorUtil, Operator} from './filter.service';
import {Query, Filter} from '../tableview/query.service';
import {View} from '../tableview/tableview.service';
@@ -95,7 +95,7 @@
}
getConditions(sourceName: string) {
- return this.map[sourceName].sort((c1, c2) => c1.sortIndex - c2.sortIndex) || [];
+ return (this.map[sourceName] || []).sort((c1, c2) => c1.sortIndex - c2.sortIndex);
}
getSourceName(condition: Condition) {
@@ -253,9 +253,22 @@
convertEnv(env: string, conditions: Condition[], attrs: SearchAttribute[], fullTextQuery: string): Filter {
let filterString = conditions
- .map(c => c.value.map(value => c.type + '.' + c.attribute + ' ' + this.adjustOperator(OperatorUtil.toFilterString(c.operator),
- this.getValueType(c, attrs)) + ' ' + this.quoteValue(value, this.getValueType(c, attrs))).join(' or '))
+ .map(c => {
+ if (c.operator === Operator.BETWEEN) {
+ if (c.value[0] != undefined && c.value[0].length > 0 && c.value[1] != undefined && c.value[1].length > 0) {
+ console.log(c.value)
+ return c.type + '.' + c.attribute + ' ' + OperatorUtil.toFilterString(c.operator)
+ + ' (' +this.quoteValue(c.value[0], this.getValueType(c, attrs)) + ', ' + this.quoteValue(c.value[1], this.getValueType(c, attrs)) +')'
+ } else {
+ return '';
+ }
+ } else {
+ return c.value.map(value => c.type + '.' + c.attribute + ' ' + this.adjustOperator(OperatorUtil.toFilterString(c.operator),
+ this.getValueType(c, attrs)) + ' ' + this.quoteValue(value, this.getValueType(c, attrs))).join(c.operator === Operator.NOT_EQUALS ? ' and ' : ' or ')
+ }
+ })
.filter(c => c.length > 0)
+ .map(c => '( ' + c + ' )')
.join(' and ');
return new Filter(env, filterString, fullTextQuery);
diff --git a/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html b/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html
index 76148ed..4e784bd 100644
--- a/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html
+++ b/nucleus/webclient/src/main/webapp/src/app/tableview/editview.component.html
@@ -61,7 +61,8 @@
typeaheadOptionsLimit="15"
placeholder="{{ 'tableview.editview.enter-search-text' | translate }}"
class="form-control"
- style="margin-bottom: 15px;">
+ style="margin-bottom: 15px;"
+ [title]="'tableview.editview.tooltip-no-wildcards' | translate">
<searchattribute-tree [searchAttributes]="searchAttributes" [environments]="environments"></searchattribute-tree>
</div>
<div class="col-md-8" style="min-height: 50vh; max-height: 72vh; overflow-y:auto;">
diff --git a/nucleus/webclient/src/main/webapp/src/assets/i18n/de.json b/nucleus/webclient/src/main/webapp/src/assets/i18n/de.json
index 0917129..26e24c8 100644
--- a/nucleus/webclient/src/main/webapp/src/assets/i18n/de.json
+++ b/nucleus/webclient/src/main/webapp/src/assets/i18n/de.json
@@ -312,6 +312,7 @@
},
"search": {
"edit-searchfields": {
+ "tooltip-no-wildcards": "Im Suchtext sind keine Wildcards erlaubt.",
"btn-apply-changes": "Änderungen übernehmen",
"enter-search-text": "Suchtext eingeben",
"err-cannot-update-selected-node": "Ausgewählter Knoten kann nicht aktualisiert werden.",
@@ -362,15 +363,21 @@
"tooltip-edit-search-filter": "Suchfilter bearbeiten",
"tooltip-enable-advanced-search": "Erweiterte Suche aktivieren",
"tooltip-global-search-attributes": "Globale Suchattribute sind Attribute, die in allen ausgewählten Datenquellen vorhanden sind. Global Suchattribute werden nur einmal dargestellt und die definierte Bedingung wird in allen Datenquellen angewendet.",
+ "tooltip-link-search-attributes-to-navigator": "Suchattribute an den Navigator koppeln.",
"tooltip-no-name-set": "Name nicht gesetzt!",
"tooltip-reset-search-conditions": "Suchkriterien zurücksetzen",
"tooltip-save-search-filter": "Suchfilter speichern",
- "tooltip-search-text": "Mögliche Operatoren:\n+ steht für einen UND Operator\n| steht für einen ODER Operator\n- negiert einen einzelnen Term\n\" fasst einen Menge von Termen zu einer Phrase zusammen\n* am Ende eines Suchterms beschreibt eine Präfixsuche\n( und ) beschreibt die Operatorpräferenz\n~N nach einem Wort beschreibt den Editierabstand (Unschärfe)\n~N nach einer Phrase beschreibt den Wortabstand\nWenn nach einem der Spezialsymbole gesucht werden soll, müssen diese mit \\ escaped werden.",
- "tooltip-select-sources": "Quellen auswählen"
+ "tooltip-search-text": "Mögliche Operatoren:\n+ steht für einen UND Operator\n| steht für einen ODER Operator\n- negiert einen einzelnen Term\n\" fasst einen Menge von Termen zu einer Phrase zusammen\n* am Ende eines Suchterms beschreibt eine Präfixsuche\n( und ) beschreibt die Operatorpräferenz\n~N nach einem Wort beschreibt den Editierabstand (Unschärfe)\n~N nach einer Phrase beschreibt den Wortabstand\n\nWenn nach einem der Spezialsymbole gesucht werden soll, müssen diese mit \\ escaped werden.",
+ "tooltip-select-sources": "Quellen auswählen",
+ "tooltip-unlink-search-attributes-from-navigator": "Suchattributen vom Navigator entkoppeln."
},
"search-condition": {
+ "and": "und",
+ "condition-is-linked": "Dieses Suchbedingung ist an den Navigator gekoppelt. Die Kopplung kann über den entsprechenden Knopf (gleiches Symbol) in der Kopfzeile 'Erweiterte Suche' gelöst werden.",
"err-cannot-initialize-autocompletion": "Autotvervollständigung kann nicht initialisiert werden.",
- "tooltip-select-search-operator": "Suchoperator auswählen"
+ "tooltip-select-search-operator": "Suchoperator auswählen",
+ "multi-value-input": "Bitte Werte für Suchattribute eingeben. Die Eingabe von mehreren Werten ist möglich. Für die Operatoren 'like' und '=' werden diese logisch mit einem 'ODER' verknüpft. Für den Operator '!=' werden diese logisch mit einem 'UND' verknüpft.\n\nMögliche Wildcards sind '?' für genau ein beliebiges Zeichen und '*' für eine beliebigen Zeichenkette.",
+ "single-value-input": "Bitte einen Wert für Suchattribute eingeben. Die Eingabe von mehreren Werten ist nicht möglich.\n\nMögliche Wildcards sind '?' für genau ein beliebiges Zeichen und '*' für eine beliebigen Zeichenkette."
},
"search-datepicker": {
"placeholder-dateformat": "TT.MM.JJJJ HH:MM",
@@ -391,7 +398,8 @@
"err-cannot-select-node": "Knoten kann nicht ausgewählt werden.",
"lbl-selected-attributes": "Ausgewählte Attribute",
"no-attributes-selected": "Keine Attribute ausgewählt.",
- "title-view-editor": "Ansichtseditor"
+ "title-view-editor": "Ansichtseditor",
+ "tooltip-no-wildcards": "Im Suchtext sind keine Wildcards erlaubt."
},
"tableview": {
"err-cannot-calculate-node": "Knoten konnte nicht berechnet werden.",
@@ -437,12 +445,15 @@
"overall": "Gesamt",
"step-size": "Schrittweite",
"preview-values": "Vorschauwerte",
- "apply": "Übernehmen"
+ "apply": "Übernehmen",
+ "reset": "Zurücksetzen"
},
"chart-viewer-nav-card": {
"load-missing-channels": "{{limit}} Kanäle nachladen ({{offset}}/{{total}})"
},
"xy-chart-data-selection-panel": {
+ "filter": "Filter",
+ "tooltip-regexp-filter": "Reguläre Ausdrücke als Filtereingabe möglich.",
"select-channel-placeholder": "Kanal wählen",
"no-x-channel-dialog-header": "Information",
"no-x-channel-dialog-message": "Die aktuelle Kanalgruppe hat keine Kanäle die als X-Achse bzw. XY-Achse definiert sind. Wenn Sie auch als Y-Achse definierte Kanäle zulassen möchten, deaktivieren Sie bitte den Filter in der Werkzeugleiste."
diff --git a/nucleus/webclient/src/main/webapp/src/assets/i18n/en.json b/nucleus/webclient/src/main/webapp/src/assets/i18n/en.json
index 7f59191..dd42758 100644
--- a/nucleus/webclient/src/main/webapp/src/assets/i18n/en.json
+++ b/nucleus/webclient/src/main/webapp/src/assets/i18n/en.json
@@ -312,6 +312,7 @@
},
"search": {
"edit-searchfields": {
+ "tooltip-no-wildcards": "No wildcards allowed in search text.",
"btn-apply-changes": "Apply changes",
"enter-search-text": "Enter search text",
"err-cannot-update-selected-node": "Cannot update the selected node.",
@@ -362,15 +363,21 @@
"tooltip-edit-search-filter": "Edit search filter",
"tooltip-enable-advanced-search": "Enable advanced search",
"tooltip-global-search-attributes": "Global search attributes are attributes present in all data sources selected. Global search attributes are displayed only once and the condition defined in them is applied to all data sources.",
+ "tooltip-link-search-attributes-to-navigator": "Link search attribtues to the navigator.",
"tooltip-no-name-set": "No name set!",
"tooltip-reset-search-conditions": "Reset search conditions",
"tooltip-save-search-filter": "Save search filter",
- "tooltip-search-text": "Operators available:\n+ represents an AND operator\n| represents an OR operator\n- negates a single term\n\" combines a set of terms into a phrase\n* at the end of a search term describes a prefix search\n( and ) describe the operator preference\n~N after a word describes the edit distance (fuzziness)\n~N after a phrase describes the word distance\nIf one of the special symbols is to be included in a search, they must be escaped with \\.",
- "tooltip-select-sources": "Select sources"
+ "tooltip-search-text": "Operators available:\n+ represents an AND operator\n| represents an OR operator\n- negates a single term\n\" combines a set of terms into a phrase\n* at the end of a search term describes a prefix search\n( and ) describe the operator preference\n~N after a word describes the edit distance (fuzziness)\n~N after a phrase describes the word distance\n\nIf one of the special symbols is to be included in a search, they must be escaped with \\.",
+ "tooltip-select-sources": "Select sources",
+ "tooltip-unlink-search-attributes-from-navigator": "Unlink search attribtues from the navigator."
},
"search-condition": {
+ "and": "and",
+ "condition-is-linked": "This search condition is linked to the navigator. It can be unliked via the related button (same symbol) in the 'Advanced Search' header.",
"err-cannot-initialize-autocompletion": "Cannot initialize autocompletion.",
- "tooltip-select-search-operator": "Select search operator"
+ "tooltip-select-search-operator": "Select search operator",
+ "multi-value-input": "Please enter search values. Multiple input values are allowed. For operators 'like' and '=' the values are connected by a logical 'OR'. However, for operator '!=' the values are connected by a logical 'AND'.\n\nPossible wildcards are '?' for one matching character and '*' for a sequence of matching characters.",
+ "single-value-input": "Please enter a search value. Multiple input values are not allowed.\n\nPossible wildcards are '?' for one matching character and '*' for a sequence of matching characters."
},
"search-datepicker": {
"placeholder-dateformat": "DD.MM.YYYY HH:MM",
@@ -391,7 +398,8 @@
"err-cannot-select-node": "Cannot select node.",
"lbl-selected-attributes": "Selected attributes",
"no-attributes-selected": "No attributes selected.",
- "title-view-editor": "View editor"
+ "title-view-editor": "View editor",
+ "tooltip-no-wildcards": "No wildcards allowed in search text."
},
"tableview": {
"err-cannot-calculate-node": "Cannot calculate node.",
@@ -437,12 +445,15 @@
"overall": "overall",
"step-size": "step size",
"preview-values": "preview values",
- "apply": "Apply"
+ "apply": "Apply",
+ "reset": "Reset"
},
"chart-viewer-nav-card": {
"load-missing-channels": "Load {{limit}} channels ({{offset}}/{{total}})"
},
"xy-chart-data-selection-panel": {
+ "filter": "Filter",
+ "tooltip-regexp-filter": "Regular expressions are enabled for filtering.",
"select-channel-placeholder": "Select Channel",
"no-x-channel-dialog-header": "Information",
"no-x-channel-dialog-message": "The current ChannelGroup has no Channels defined as X-axis or XY-axis. If you want to also allow Channels marked as Y-axis, toggle the corresponding filter in the toolbar."