瀏覽代碼

HPCC-16686 WuDetails using new iterator

Signed-off-by: Shamser Ahmed <shamser.ahmed@lexisnexis.co.uk>
Shamser Ahmed 7 年之前
父節點
當前提交
ac0dfcd652

+ 132 - 1
esp/scm/ws_workunits.ecm

@@ -1931,9 +1931,138 @@ ESPresponse [exceptions_inline, nil_remove] WUGetNumFileToCopyResponse
     [min_ver("1.69")] int64 Total;
 };
 
+// ----------------------------------------------------------------------------------
+// WUDetails service
+//
+// Request related structures
+ESPstruct WUPropertyFilter
+{
+    string Name;                                // Attribute Name to match
+                                                // Match attribute's RawValue
+    [optional] string ExactValue;
+    [optional] string MinValue;
+    [optional] string MaxValue;
+};
+
+// Filters that indicates which nodes are explicitly matched
+// Scopes can be used at the same time as Ids.
+// ScopeTypes cannot be used at the same time as Scopes or Ids.
+// PropertyFilters (attributes or statistics) is applied to all matches
+ESPStruct WUScopeFilter
+{
+    [optional] integer MaxDepth(9999);          // Maximum depth to return matches from (omitted means no limit)
+    [optional] ESParray<string, Scope> Scopes;  // Fully qualified scope (See definition of "Scope" in workunit.hpp)
+    [optional] ESParray<string, id> Ids;        // Return scope with given node id
+    [optional] ESParray<string, ScopeType> ScopeTypes;   // Return scope of a given type
+    [optional] ESParray<ESPstruct WUPropertyFilter, PropertyFilter> PropertyFilters;
+};
+
+// This provides the filter which indicates which nodes are implicitly matched
+// Once a match is found, all nested scopes to a depth of depth are implicitly matched, provided they match the nested filter
+ESPStruct WUNestedFilter
+{
+    [optional] unsigned Depth(9999);            // How many nodes deep relative to matched scope id(s)
+                                                // 0 - implies return only the given scope id
+                                                // 1 - return only the immediate child of the given scope
+                                                // n - return children to 'n' level deep
+    [optional] ESParray<string, ScopeType> ScopeTypes;   // Return scope of a given type
+};
+
+// Additional properties that are returned for a scopeType
+ESPStruct WUExtraProperties
+{
+    string scopeType;                   // A type of scope e.g., activity/edge
+    [optional] ESParray<string, Property> Properties; // a list of properties to return, omitted means none
+};
+
+// If measure and attributes are omitted then all matches are returned
+// Measure and Attributes/ScopeAttributes are mutually exclusive.
+// Measure set to blank or Attributes set to empty will prevent any attributes being returned.
+ESPStruct WUPropertiesToReturn
+{
+    bool AllStatistics(false);
+    bool AllAttributes(false);
+    bool AllHints(false);
+    [optional] uint64 MinVersion;       // Only return properties where the version is later than this version.
+    [optional] string Measure;          // E.g. Time, Num, Size
+    [optional] ESParray<string, Property> Properties; // a list of properties to return
+    [optional] ESParray<ESPstruct WUExtraProperties, Extra> ExtraProperties;
+};
+
+ESPStruct WUScopeOptions
+{
+    bool IncludeMatchedScopesInResults(true);
+    bool IncludeScope(true);
+    bool IncludeId(false);
+    bool IncludeScopeType(false);
+};
+
+// Controls which information is returned for the statistics
+ESPStruct WUPropertyOptions
+{
+    bool IncludeName(true);
+    bool IncludeRawValue(false);
+    bool IncludeFormatted(true);
+    bool IncludeMeasure(true);
+    bool IncludeCreator(false);
+    bool IncludeCreatorType(false);
+};
+
+ESPRequest WUDetailsRequest
+{
+    string WUID;
+    [optional] ESPstruct WUScopeFilter ScopeFilter;               // which scopes are matched
+    [optional] ESPstruct WUNestedFilter NestedFilter;             // what nested scopes are returned
+    [optional] ESPstruct WUPropertiesToReturn PropertiesToReturn; // List of properties to return
+    [optional] string    Filter;                                  // Filter as a string text
+    [optional] ESPstruct WUScopeOptions ScopeOptions;             // Which scope details are returned
+    [optional] ESPstruct WUPropertyOptions PropertyOptions;       // Which attribute details are returned
+};
+
+// ----------------------------------------------------------------------------------
+// Response related structures
+ESPstruct [nil_remove] WUResponseProperty
+{
+    [optional] string Name;         // Name of attribute
+    [optional] string RawValue;     // Value of attribute
+    [optional] string Formatted;    // Formatted value of attribute
+    [optional] string Measure;      // What type is this attribute
+    [optional] string Creator;      // Which engine created it
+    [optional] string CreatorType;  // What type of engine created it.
+};
+
+ESPstruct [nil_remove] WUResponseScope
+{
+    [optional] string ScopeName;        // Fully qualified scope (See definition of "Scope" in workunit.hpp)
+    [optional] string Id;               // Node/Graph id
+    [optional] string ScopeType;        // e.g. Activity, Edge
+    [optional] ESParray<ESPstruct WUResponseProperty, Property> Properties;
+};
+
+
+ESPResponse [exceptions_inline] WUDetailsResponse
+{
+    uint64 MaxVersion;                              // largest version for any matches
+    string WUID;                                    // all    [optional] ESParray<string, Attribute> Attributes; // a list of attributes to returnows wildcarded requests to be interpreted.
+    ESParray<ESPstruct WUResponseScope, Scope> Scopes;
+};
+
+// ----------------------------------------------------------------------------------
+ESPRequest WUDetailsMetaRequest
+{
+};
+
+ESPResponse [exceptions_inline] WUDetailsMetaResponse
+{
+    ESParray<string, Statistic> Statistics;
+    ESParray<string, Attribute> Attributes;
+    ESParray<string, ScopeType> ScopeTypes;
+    ESParray<string, Measure> Measures;
+};
+// ----------------------------------------------------------------------------------
 ESPservice [
     auth_feature("DEFERRED"), //This declares that the method logic handles feature level authorization
-    version("1.70"), default_client_version("1.70"),
+    version("1.71"), default_client_version("1.71"),
     noforms,exceptions_inline("./smc_xslt/exceptions.xslt"),use_method_name] WsWorkunits
 {
     ESPmethod [cache_seconds(60), resp_xsl_default("/esp/xslt/workunits.xslt")]     WUQuery(WUQueryRequest, WUQueryResponse);
@@ -2014,6 +2143,8 @@ ESPservice [
     ESPmethod [cache_seconds(60), min_ver("1.57")] WUListArchiveFiles(WUListArchiveFilesRequest, WUListArchiveFilesResponse);
     ESPmethod [cache_seconds(60), min_ver("1.57")] WUGetArchiveFile(WUGetArchiveFileRequest, WUGetArchiveFileResponse);
     ESPmethod [cache_seconds(60), min_ver("1.61")] WUGetNumFileToCopy(WUGetNumFileToCopyRequest, WUGetNumFileToCopyResponse);
+    ESPmethod [min_ver("1.71")] WUDetails(WUDetailsRequest, WUDetailsResponse);
+    ESPmethod [min_ver("1.71")] WUDetailsMeta(WUDetailsMetaRequest, WUDetailsMetaResponse);
 };
 
 

+ 1 - 0
esp/services/ws_workunits/CMakeLists.txt

@@ -41,6 +41,7 @@ set (    SRCS
          ws_workunitsHelpers.hpp
          ws_workunitsAuditLogs.cpp
          ws_workunitsQuerySets.cpp
+         ws_wudetails.cpp
     )
 
 include_directories (

+ 73 - 0
esp/services/ws_workunits/ws_workunitsService.cpp

@@ -44,6 +44,7 @@
 #include "thorxmlwrite.hpp"
 #include "fvdatasource.hpp"
 #include "fvresultset.ipp"
+#include "ws_wudetails.hpp"
 
 #include "package.h"
 
@@ -4209,6 +4210,78 @@ bool CWsWorkunitsEx::onGVCAjaxGraph(IEspContext &context, IEspGVCAjaxGraphReques
     return true;
 }
 
+bool CWsWorkunitsEx::onWUDetails(IEspContext &context, IEspWUDetailsRequest &req, IEspWUDetailsResponse &resp)
+{
+    try
+    {
+        StringBuffer wuid = req.getWUID();
+        WsWuHelpers::checkAndTrimWorkunit("WUDetails", wuid);
+
+        PROGLOG("WUDetails: %s", wuid.str());
+
+        Owned<IWorkUnitFactory> factory = getWorkUnitFactory(context.querySecManager(), context.queryUser());
+        Owned<IConstWorkUnit> cw = factory->openWorkUnit(wuid.str());
+        if(!cw)
+            throw MakeStringException(ECLWATCH_CANNOT_OPEN_WORKUNIT,"Cannot open workunit %s.",wuid.str());
+        ensureWsWorkunitAccess(context, *cw, SecAccess_Read);
+
+        WUDetails wuDetails(cw, wuid);
+        wuDetails.processRequest(req, resp);
+    }
+    catch(IException* e)
+    {
+        FORWARDEXCEPTION(context, e,  ECLWATCH_INTERNAL_ERROR);
+    }
+    return true;
+}
+
+bool CWsWorkunitsEx::onWUDetailsMeta(IEspContext &context, IEspWUDetailsMetaRequest &req, IEspWUDetailsMetaResponse &resp)
+{
+    try
+    {
+        StringArray statistics;
+        for (unsigned i=StatisticKind::StKindAll+1; i<StatisticKind::StMax;++i)
+        {
+            const char * s = queryStatisticName((StatisticKind)i);
+            if (s && *s)
+                statistics.append(s);
+        }
+        resp.setStatistics(statistics);
+
+        StringArray attributes;
+        for (unsigned i=WuAttr::WAAll+1; i<WuAttr::WAMax; ++i)
+        {
+            const char * s = queryWuAttributeName((WuAttr)i);
+            if (s && *s)
+                attributes.append(s);
+        }
+        resp.setAttributes(attributes);
+
+        StringArray scopeTypes;
+        for (unsigned i=StatisticScopeType::SSTall+1; i<StatisticScopeType::SSTmax; ++i)
+        {
+            const char * s = queryScopeTypeName((StatisticScopeType)i);
+            if (s && *s)
+                scopeTypes.append(s);
+        }
+        resp.setScopeTypes(scopeTypes);
+
+        StringArray measures;
+        for (unsigned i=StatisticMeasure::SMeasureAll+1; i<StatisticMeasure::SMeasureMax; ++i)
+        {
+            const char *s = queryMeasureName((StatisticMeasure)i);
+            if (s && *s)
+                measures.append(s);
+        }
+        resp.setMeasures(measures);
+    }
+    catch(IException* e)
+    {
+        FORWARDEXCEPTION(context, e,  ECLWATCH_INTERNAL_ERROR);
+    }
+    return true;
+}
+
 bool CWsWorkunitsEx::onWUGraphInfo(IEspContext &context,IEspWUGraphInfoRequest &req, IEspWUGraphInfoResponse &resp)
 {
     try

+ 2 - 0
esp/services/ws_workunits/ws_workunitsService.hpp

@@ -258,6 +258,8 @@ public:
 
     bool onWUCDebug(IEspContext &context, IEspWUDebugRequest &req, IEspWUDebugResponse &resp);
     bool onWUDeployWorkunit(IEspContext &context, IEspWUDeployWorkunitRequest & req, IEspWUDeployWorkunitResponse & resp);
+    bool onWUDetails(IEspContext &context, IEspWUDetailsRequest &req, IEspWUDetailsResponse &resp);
+    bool onWUDetailsMeta(IEspContext &context, IEspWUDetailsMetaRequest &req, IEspWUDetailsMetaResponse &resp);
 
     void setPort(unsigned short _port){port=_port;}
 

+ 426 - 0
esp/services/ws_workunits/ws_wudetails.cpp

@@ -0,0 +1,426 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2017 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#include <set>
+#include "ws_workunitsService.hpp"
+#include "ws_wudetails.hpp"
+#include "jlib.hpp"
+#include "workunit.hpp"
+#include "jset.hpp"
+#include "jstatcodes.h"
+
+class WUDetailsVisitor : public IWuScopeVisitor
+{
+public:
+    WUDetailsVisitor(IConstWUPropertyOptions & _propertyOptions, IConstWUPropertiesToReturn & _propertiesToReturn);
+    virtual ~WUDetailsVisitor(){};
+    virtual void noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & extra) override;
+    virtual void noteAttribute(WuAttr attr, const char * value) override;
+    virtual void noteHint(const char * kind, const char * value) override;
+
+    void resetScope();
+    void noteScopeType(const StatisticScopeType _sst);
+    IArrayOf<IEspWUResponseProperty> & getResponseProperties() { return EspWUResponseProperties;}
+    unsigned __int64 getMaxTimestamp() const { return maxTimestamp;}
+
+private:
+    static const unsigned StatisticFilterMaskSize = StatisticKind::StMax;
+    static const unsigned AttributeFilterMaskSize = WuAttr::WAMax-WANone;
+
+    bool includeName = false;
+    bool includeRawValue = false;
+    bool includeFormatted = false;
+    bool includeMeasure = false;
+    bool includeCreator = false;
+    bool includeCreatorType = false;
+
+    bool extraStatisticsRequested = false;
+    std::set<StatisticKind> extraStatistics[SSTmax];
+    bool extraAttributesRequested = false;
+    std::set<WuAttr> extraAttributes[SSTmax];
+
+    bool returnStatisticListSpecified = false;
+    Owned<IBitSet> returnStatisticList;
+    bool returnAttributeSpecified = false;
+    Owned<IBitSet> returnAttributeList;
+
+    const std::set<StatisticKind> * additionalStatsForCurScope = nullptr;
+    const std::set<WuAttr> * additionalAttribsForCurScope = nullptr;
+    unsigned __int64 maxTimestamp = 0;
+    IArrayOf<IEspWUResponseProperty> EspWUResponseProperties;
+    StatisticScopeType currentStatisticScopeType = SSTnone;
+
+    void buildAttribListToReturn(IConstWUPropertiesToReturn & propertiesToReturn);
+};
+
+WUDetailsVisitor::WUDetailsVisitor(IConstWUPropertyOptions & propertyOptions, IConstWUPropertiesToReturn & propertiesToReturn)
+{
+    includeName = propertyOptions.getIncludeName();
+    includeRawValue = propertyOptions.getIncludeRawValue();
+    includeFormatted = propertyOptions.getIncludeFormatted();
+    includeMeasure = propertyOptions.getIncludeMeasure();
+    includeCreator = propertyOptions.getIncludeCreator();
+    includeCreatorType = propertyOptions.getIncludeCreatorType();
+
+    buildAttribListToReturn(propertiesToReturn);
+}
+
+void WUDetailsVisitor::noteStatistic(StatisticKind kind, unsigned __int64 value, IConstWUStatistic & extra)
+{
+    if (extraStatisticsRequested)
+    {
+        // If the statistic is not in the standard return statistic list,
+        // then check if it's in the additional property list
+        if (returnStatisticListSpecified && !returnStatisticList->test(kind))
+        {
+            if (additionalStatsForCurScope==nullptr ||
+                additionalStatsForCurScope->find(kind)==additionalStatsForCurScope->end())
+            {
+                return;
+            }
+        }
+    }
+
+    Owned<IEspWUResponseProperty> EspWUResponseProperty = createWUResponseProperty("","");
+
+    if (includeName)
+        EspWUResponseProperty->setName(queryStatisticName(kind));
+    if (includeRawValue)
+    {
+        StringBuffer rawValue;
+        rawValue.append(value);
+        EspWUResponseProperty->setRawValue(rawValue);
+    }
+    SCMStringBuffer tmpStr;
+    if (includeFormatted)
+        EspWUResponseProperty->setFormatted(extra.getFormattedValue(tmpStr).str());
+    if (includeMeasure && extra.getMeasure()!=SMeasureNone)
+        EspWUResponseProperty->setMeasure(queryMeasureName(extra.getMeasure()));
+    if (includeCreator)
+        EspWUResponseProperty->setCreator(extra.getCreator(tmpStr).str());
+    if (includeCreatorType && extra.getCreatorType()!=SCTnone)
+        EspWUResponseProperty->setCreatorType(queryCreatorTypeName(extra.getCreatorType()));
+
+    EspWUResponseProperties.append(*EspWUResponseProperty.getClear());
+    if (extra.getTimestamp()>maxTimestamp)
+        maxTimestamp = extra.getTimestamp();
+}
+
+void WUDetailsVisitor::noteAttribute(WuAttr attr, const char * value)
+{
+    if (extraAttributesRequested)
+    {
+        // If the Attribute is not in the standard return statistic list,
+        // then check if it's in the additional attribute list
+        if ((returnAttributeSpecified && !returnAttributeList->test(attr-WANone)))
+        {
+            if (additionalAttribsForCurScope==nullptr ||
+                additionalAttribsForCurScope->find(attr)==additionalAttribsForCurScope->end())
+            {
+                return;
+            }
+        }
+    }
+
+    Owned<IEspWUResponseProperty> EspWUResponseProperty = createWUResponseProperty("","");
+    EspWUResponseProperty->setName(queryWuAttributeName(attr));
+    if (includeFormatted)
+        EspWUResponseProperty->setFormatted(value);
+    if (includeRawValue)
+        EspWUResponseProperty->setRawValue(value);
+
+    EspWUResponseProperties.append(*EspWUResponseProperty.getClear());
+}
+
+void WUDetailsVisitor::noteHint(const char * kind, const char * value)
+{
+    Owned<IEspWUResponseProperty> EspWUResponseProperty = createWUResponseProperty("","");
+
+    StringBuffer hint("hint:");
+    hint.append(kind);
+    EspWUResponseProperty->setName(hint);
+    if (includeFormatted)
+        EspWUResponseProperty->setFormatted(value);
+    if (includeRawValue)
+        EspWUResponseProperty->setRawValue(value);
+
+    EspWUResponseProperties.append(*EspWUResponseProperty.getClear());
+}
+
+void WUDetailsVisitor::buildAttribListToReturn(IConstWUPropertiesToReturn & propertiesToReturn)
+{
+    const bool allStatistics = propertiesToReturn.getAllStatistics();
+    const bool allAttributes = propertiesToReturn.getAllAttributes();
+    if (allStatistics && allAttributes)
+        return;
+
+    IArrayOf<IConstWUExtraProperties> & extraProperties = propertiesToReturn.getExtraProperties();
+    ForEachItemIn(idx, extraProperties)
+    {
+        IConstWUExtraProperties & cur = extraProperties.item(idx);
+        const char * scopeTypeWithAdditionalProps = cur.getScopeType();
+        if (!scopeTypeWithAdditionalProps || !*scopeTypeWithAdditionalProps)
+            continue;
+
+        StatisticScopeType sst = queryScopeType(scopeTypeWithAdditionalProps,SSTnone);
+        if(sst==SSTnone)
+            throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid ScopeType (%s) in ExtraProperties",scopeTypeWithAdditionalProps);
+
+        const StringArray & props = cur.getProperties();
+        ForEachItemIn(idx2, props)
+        {
+            StatisticKind sk = queryStatisticKind(props[idx2], StKindNone);
+            if (sk==StKindAll)
+                throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid property name (%s) in ExtraProperties",props[idx2]);
+            if (sk!=StKindNone)
+            {
+                if (!allStatistics)
+                {
+                    extraStatistics[sst].insert(sk);
+                    extraStatisticsRequested = true;
+                }
+            }
+            else
+            {
+                const WuAttr wa = queryWuAttribute(props[idx2], WAMax);
+                if (wa==WAMax)
+                    throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid property name (%s) in ExtraProperties",props[idx2]);
+                if (!allAttributes)
+                {
+                    extraAttributes[sst].insert(wa);
+                    extraAttributesRequested = true;
+                }
+            }
+        }
+    }
+
+    // If additional stats or attributes specified for scope type,
+    // then noteStatistic & noteAttribute will need to work out what to return
+    if (extraStatisticsRequested || extraAttributesRequested)
+    {
+        StringArray & propertiesToReturnList = propertiesToReturn.getProperties();
+        ForEachItemIn(idx,propertiesToReturnList)
+        {
+            const char * attributeName = propertiesToReturnList[idx];
+            if (!attributeName || *attributeName==0) continue;
+
+            const StatisticKind sk = queryStatisticKind(attributeName, StKindNone);
+            if (sk==StKindAll)
+                throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid Attribute name in AttributeToReturn(%s)", attributeName);
+            if (sk!=StKindNone)
+            {
+                if (!returnStatisticListSpecified)
+                {
+                    returnStatisticList.set(createBitSet(StatisticFilterMaskSize));
+                    returnStatisticListSpecified=true;
+                }
+                returnStatisticList->set(sk, true);
+            }
+            else
+            {
+              const WuAttr wa = queryWuAttribute(attributeName, WAMax);
+              if (wa!=WAMax)
+              {
+                  if (!returnAttributeSpecified)
+                  {
+                      returnAttributeList.set(createBitSet(AttributeFilterMaskSize));
+                      returnAttributeSpecified=true;
+                  }
+                  returnAttributeList->set(wa-WANone,true);
+              }
+              else
+                  throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid Attribute name in AttributeToReturn(%s)", attributeName);
+            }
+        }
+    }
+}
+
+void WUDetailsVisitor::resetScope()
+{
+    currentStatisticScopeType = SSTnone;
+    additionalStatsForCurScope = nullptr;
+    additionalAttribsForCurScope = nullptr;
+    EspWUResponseProperties.clear();
+}
+
+void WUDetailsVisitor::noteScopeType(const StatisticScopeType _sst)
+{
+    currentStatisticScopeType = _sst;
+    additionalStatsForCurScope = & extraStatistics[currentStatisticScopeType];
+    additionalAttribsForCurScope = & extraAttributes[currentStatisticScopeType];
+}
+
+WUDetails::WUDetails(IConstWorkUnit *_workunit, const char *_wuid)
+: workunit(_workunit), wuid(_wuid)
+{
+}
+
+void WUDetails::processRequest(IEspWUDetailsRequest &req, IEspWUDetailsResponse &resp)
+{
+    IConstWUScopeOptions & scopeOptions =  req.getScopeOptions();
+    const bool includeScope = scopeOptions.getIncludeScope();
+    const bool includeScopeType = scopeOptions.getIncludeScopeType();
+    const bool includeId = scopeOptions.getIncludeId();
+
+    buildWuScopeFilter(req.getScopeFilter(), req.getNestedFilter(), req.getPropertiesToReturn(),
+                       req.getFilter(), scopeOptions);
+
+    IArrayOf<IEspWUResponseScope> respScopes;
+    WUDetailsVisitor wuDetailsVisitor(req.getPropertyOptions(), req.getPropertiesToReturn());
+    Owned<IConstWUScopeIterator> iter = &workunit->getScopeIterator(wuScopeFilter);
+    ForEach(*iter)
+    {
+        StatisticScopeType scopeType = iter->getScopeType();
+        const char * scope = iter->queryScope();
+        assertex(scope);
+
+        wuDetailsVisitor.noteScopeType(scopeType);
+        iter->playProperties(PTall, wuDetailsVisitor);
+
+        Owned<IEspWUResponseScope> respScope = createWUResponseScope("","");
+        if (includeScope)
+            respScope->setScopeName(scope);
+        if (includeScopeType)
+            respScope->setScopeType(queryScopeTypeName(scopeType));
+        if (includeId)
+            respScope->setId(queryScopeTail(scope));
+
+        IArrayOf<IEspWUResponseProperty> & properties = wuDetailsVisitor.getResponseProperties();
+        if (!properties.empty())
+            respScope->setProperties(properties);
+        respScopes.append(*respScope.getClear());
+
+        wuDetailsVisitor.resetScope();
+    }
+    StringBuffer maxVersion;
+    maxVersion.append(wuDetailsVisitor.getMaxTimestamp());
+
+    resp.setWUID(wuid.str());
+    resp.setMaxVersion(maxVersion.str());
+    resp.setScopes(respScopes);
+}
+
+void WUDetails::buildWuScopeFilter(IConstWUScopeFilter & requestScopeFilter, IConstWUNestedFilter & nestedFilter,
+                                   IConstWUPropertiesToReturn & propertiesToReturn, const char * filter,
+                                   IConstWUScopeOptions & scopeOptions)
+{
+    wuScopeFilter.addFilter(filter);
+    wuScopeFilter.setDepth(0, requestScopeFilter.getMaxDepth());
+    wuScopeFilter.setMeasure(propertiesToReturn.getMeasure());
+    wuScopeFilter.setIncludeNesting(nestedFilter.getDepth());
+    wuScopeFilter.setIncludeMatch(scopeOptions.getIncludeMatchedScopesInResults());
+
+    StringArray & scopes = requestScopeFilter.getScopes();
+    ForEachItemIn(idx1,scopes)
+        if (*scopes.item(idx1))
+            wuScopeFilter.addScope(scopes.item(idx1));
+
+    StringArray & ids = requestScopeFilter.getIds();
+    ForEachItemIn(idx2,ids)
+        if (*ids.item(idx2))
+            wuScopeFilter.addId(ids.item(idx2));
+
+    StringArray & scopeTypes = requestScopeFilter.getScopeTypes();
+    ForEachItemIn(idx3,scopeTypes)
+        if (*scopeTypes.item(idx3))
+            wuScopeFilter.addScopeType(scopeTypes.item(idx3));
+
+    buildPropertyFilter(requestScopeFilter.getPropertyFilters());
+
+    StringArray & nestedScopeTypes = nestedFilter.getScopeTypes();
+    ForEachItemIn(idx4,nestedScopeTypes)
+        if (*nestedScopeTypes.item(idx4))
+            wuScopeFilter.setIncludeScopeType(nestedScopeTypes.item(idx4));
+
+    const char * minVersion = propertiesToReturn.getMinVersion();
+    if (minVersion && *minVersion)
+    {
+        StringBuffer sMinVersion("version[");
+        sMinVersion.append(propertiesToReturn.getMinVersion()).append("]");
+        wuScopeFilter.addFilter(sMinVersion);
+    }
+
+    StringArray & properties = propertiesToReturn.getProperties();
+    ForEachItemIn(idx5,properties)
+    {
+        if (properties.item(idx5) && *properties.item(idx5))
+        {
+            wuScopeFilter.addOutput(properties.item(idx5));
+        }
+    }
+
+    IArrayOf<IConstWUExtraProperties> & extraProperties= propertiesToReturn.getExtraProperties();
+    ForEachItemIn(idx6, extraProperties)
+    {
+        IConstWUExtraProperties & cur = extraProperties.item(idx6);
+        const char * scopeTypeWithExtraProps = cur.getScopeType();
+        if (!scopeTypeWithExtraProps || !*scopeTypeWithExtraProps) continue;
+
+        StringArray & properties = cur.getProperties();
+        ForEachItemIn(idx7, properties)
+        {
+            wuScopeFilter.addOutput(properties.item(idx7));
+        }
+    }
+
+    if (propertiesToReturn.getAllStatistics())
+        wuScopeFilter.addOutputProperties(PTstatistics);
+    if (propertiesToReturn.getAllAttributes())
+        wuScopeFilter.addOutputProperties(PTattributes);
+    if (propertiesToReturn.getAllHints())
+        wuScopeFilter.addOutputProperties(PThints);
+
+    wuScopeFilter.finishedFilter();
+}
+
+void WUDetails::buildPropertyFilter(IArrayOf<IConstWUPropertyFilter> & reqPropertyFilter)
+{
+    ForEachItemIn(idx,reqPropertyFilter)
+    {
+        IConstWUPropertyFilter & attribFilterItem = reqPropertyFilter.item(idx);
+        const char * propertyName = attribFilterItem.getName();
+        if ( !propertyName || *propertyName==0 ) continue;
+
+        const char *exactValue = attribFilterItem.getExactValue();
+        const char *minValue = attribFilterItem.getMinValue();
+        const char *maxValue = attribFilterItem.getMaxValue();
+        const bool hasExactValue = *exactValue!=0;
+        const bool hasMinValue = *minValue!=0;
+        const bool hasMaxValue = *maxValue!=0;
+
+        if (hasExactValue && (hasMinValue||hasMaxValue))
+            throw MakeStringException(ECLWATCH_INVALID_INPUT,
+                                      "Invalid Property Filter ('%s') - ExactValue may not be used with MinValue or MaxValue",
+                                      propertyName);
+        const StatisticKind sk = queryStatisticKind(propertyName, StKindNone);
+        if (sk==StKindAll || sk==StKindNone)
+            throw MakeStringException(ECLWATCH_INVALID_INPUT, "Invalid Property Name ('%s') in Property Filter", propertyName);
+        if (hasExactValue)
+        {
+            stat_type exactVal = atoi64(exactValue);
+            wuScopeFilter.addRequiredStat(sk,exactVal,exactVal);
+        }
+        else if (hasMinValue||hasMaxValue)
+        {
+            stat_type minVal = atoi64(minValue);
+            stat_type maxVal = atoi64(maxValue);
+            wuScopeFilter.addRequiredStat(sk,minVal,maxVal);
+        }
+        else
+            wuScopeFilter.addRequiredStat(sk);
+    }
+}

+ 43 - 0
esp/services/ws_workunits/ws_wudetails.hpp

@@ -0,0 +1,43 @@
+/*##############################################################################
+
+    HPCC SYSTEMS software Copyright (C) 2017 HPCC Systems®.
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+############################################################################## */
+
+#ifndef _ESPWIZ_ws_wudetails_HPP__
+#define _ESPWIZ_ws_wudetails_HPP__
+
+#include "workunit.hpp"
+#include "jstatcodes.h"
+
+
+class WUDetails
+{
+public:
+    WUDetails(IConstWorkUnit *_workunit, const char *_wuid);
+    void processRequest(IEspWUDetailsRequest &req, IEspWUDetailsResponse &resp);
+
+private:
+    Linked<IConstWorkUnit> workunit;
+    StringBuffer wuid;
+
+    WuScopeFilter wuScopeFilter;
+    void buildWuScopeFilter(IConstWUScopeFilter & requestScopeFilter, IConstWUNestedFilter & nestedFilter,
+                            IConstWUPropertiesToReturn & propertiesToReturn, const char * filter,
+                            IConstWUScopeOptions & scopeOptions);
+    void buildPropertyFilter(IArrayOf<IConstWUPropertyFilter> & reqPropertyFilter);
+};
+
+
+#endif

+ 14 - 0
system/jlib/jset.cpp

@@ -375,6 +375,7 @@ class CBitSetThreadSafe : public CBitSetBase<CBitSetArrayHelper>
             }
         }
     }
+
 public:
     CBitSetThreadSafe()
     {
@@ -476,6 +477,14 @@ public:
             memset(mem, 0, bitSetUnits*sizeof(bits_t));
         fixedMemory = true;
     }
+    CBitSet(unsigned initialBits)
+    {
+        size32_t memRequired = getBitSetMemoryRequirement(initialBits);
+        mb.appendBytes(0, memRequired);
+        mem = (bits_t *)mb.bufferBase();
+        bitSetUnits = memRequired/sizeof(bits_t);
+        fixedMemory = false;
+    }
     CBitSet(MemoryBuffer &buffer)
     {
         deserialize(buffer);
@@ -496,6 +505,11 @@ extern jlib_decl IBitSet *createBitSet(unsigned maxBits, const void *mem, bool r
     return new CBitSet(maxBits, mem, reset);
 }
 
+extern jlib_decl IBitSet *createBitSet(unsigned initialBits)
+{
+    return new CBitSet(initialBits);
+}
+
 extern jlib_decl IBitSet *createBitSet()
 {
     return new CBitSet();

+ 2 - 1
system/jlib/jset.hpp

@@ -155,7 +155,8 @@ extern jlib_decl IBitSet *deserializeThreadSafeBitSet(MemoryBuffer &mb);
  * IOW, e.g. bits 0-sizeof(bits_t) must be set from only 1 thread at a time.
  */
 extern jlib_decl IBitSet *createBitSet(size32_t memSize, const void *mem, bool reset=true);
-// This form allows the size of the bit set to be dynamic. No guarantees about threading.
+// These forms allows the size of the bit set to be dynamic. No guarantees about threading.
+extern jlib_decl IBitSet *createBitSet(unsigned initialBits);
 extern jlib_decl IBitSet *createBitSet();
 extern jlib_decl IBitSet *deserializeBitSet(MemoryBuffer &mb);
 // returns number of bytes required to represent numBits in memory