diff --git a/common/dllserver/thorplugin.cpp b/common/dllserver/thorplugin.cpp index 53dccaf4d71..ab58f3913b3 100644 --- a/common/dllserver/thorplugin.cpp +++ b/common/dllserver/thorplugin.cpp @@ -559,6 +559,8 @@ const StringArray &HelperDll::queryManifestFiles(const char *type, const char *w OwnedIFileIO o = f->open(IFOcreate); assertex(o.get() != nullptr); o->write(0, len, data); + o->close(); + list->append(extractName); if (doTrace(traceJava) && streq(type, "jar")) DBGLOG("Extracted jar resource %u size %u to %s in %u ms", id, len, extractName.str(), msTick() - start); diff --git a/dali/base/dacoven.cpp b/dali/base/dacoven.cpp index 081f5f2d73c..8aa60877944 100644 --- a/dali/base/dacoven.cpp +++ b/dali/base/dacoven.cpp @@ -563,12 +563,14 @@ class CCovenServer: public CCovenBase Owned f = createIFile(storename.get()); Owned io = f->open(IFOcreate); io->write(0, xml.length(), xml.str()); + io->close(); io.clear(); if (!backupname.isEmpty()) { try { f.setown(createIFile(backupname.get())); io.setown(f->open(IFOcreate)); io->write(0, xml.length(), xml.str()); + io->close(); io.clear(); } catch (IException *e) { diff --git a/dali/base/dasds.cpp b/dali/base/dasds.cpp index 916fd762463..03b55e0a434 100644 --- a/dali/base/dasds.cpp +++ b/dali/base/dasds.cpp @@ -1022,6 +1022,7 @@ void writeDelta(StringBuffer &xml, IFile &iFile, const char *msg="", unsigned re sprintf(strNum, "%016" I64F "X", fLen); memcpy(headerPtr + deltaHeaderSizeOff, strNum, 16); iFileIO->write(0, strlen(deltaHeader), headerPtr); + iFileIO->close(); } catch (IException *e) { @@ -1212,6 +1213,7 @@ class CDeltaWriter : implements IThreaded Owned iFile = createIFile(rL.str()); Owned fileIO = iFile->open(IFOcreate); fileIO->write(0, length, data); + fileIO->close(); } catch (IException *e) { @@ -5286,6 +5288,7 @@ class CStoreHelper : implements IStoreHelper, public CInterface iFileIO->write(0, sizeof(unsigned), crc); if (storeInfo) storeInfo->cache.set(filename.str()); + iFileIO->close(); } void updateStoreInfo(const char *base, const char *location, unsigned edition, unsigned *crc, CStoreInfo *storeInfo=NULL) @@ -5397,6 +5400,7 @@ class CStoreHelper : implements IStoreHelper, public CInterface OwnedIFileIO detachIPIO = detachIPIFile->open(IFOcreate); detachIPIO->write(0, sizeof(storeHelper.mySessId), &storeHelper.mySessId); + detachIPIO->close(); detachIPIO.clear(); detachIPIFile->rename(activeDetachIPStr.str()); // check often do not wait any longer than necessary diff --git a/dali/datest/datest.cpp b/dali/datest/datest.cpp index 88b23181d3e..84db25fe817 100644 --- a/dali/datest/datest.cpp +++ b/dali/datest/datest.cpp @@ -2927,7 +2927,7 @@ NULL #else out = fileno(stdout); #endif - Owned stdOutFileIO = createIFileIO(out,IFOwrite); + Owned stdOutFileIO = createIFileIO(nullptr,out,IFOwrite); if (testParams.ordinality()) { newFileName = testParams.item(0); diff --git a/dali/dfu/dfurun.cpp b/dali/dfu/dfurun.cpp index 160c51cf07e..3b470723a66 100644 --- a/dali/dfu/dfurun.cpp +++ b/dali/dfu/dfurun.cpp @@ -540,23 +540,31 @@ class CDFUengine: public CInterface, implements IDFUengine return result; } - void ensureFilePermissions(const char * planeName, const char * fileName, SecAccessFlags perm, bool write) + void ensureFilePermissions(const char * planeName, const char * fileName, SecAccessFlags perm, IUserDescriptor *user, bool write) { if ((write && !HASWRITEPERMISSION(perm)) || (!write && !HASREADPERMISSION(perm))) { + StringBuffer context; + StringBuffer username; + if (user) + user->getUserName(username); + else + username.append("Null user"); + context.appendf("user: '%s', assigned access %s (%d)", username.str(), getSecAccessFlagName(perm), perm); + if (!isEmptyString(planeName)) { CDfsLogicalFileName dlfn; dlfn.setPlaneExternal(planeName, fileName); if (write) - throw makeStringExceptionV(DFSERR_CreateAccessDenied, "Create permission denied for file scope: %s on DropZone: %s", dlfn.get(), planeName); + throw makeStringExceptionV(DFSERR_CreateAccessDenied, "Create permission denied for file scope: %s on DropZone: %s, %s", dlfn.get(), planeName, context.str()); else - throw makeStringExceptionV(DFSERR_LookupAccessDenied, "Lookup permission denied for file scope: %s on DropZone: %s", dlfn.get(), planeName); + throw makeStringExceptionV(DFSERR_LookupAccessDenied, "Lookup permission denied for file scope: %s on DropZone: %s, %s", dlfn.get(), planeName, context.str()); } if (write) - throw makeStringExceptionV(DFSERR_CreateAccessDenied, "Create permission denied for physical file(s): %s", fileName); + throw makeStringExceptionV(DFSERR_CreateAccessDenied, "Create permission denied for physical file(s): %s, %s", fileName, context.str()); else - throw makeStringExceptionV(DFSERR_LookupAccessDenied, "Lookup permission denied for physical file(s): %s", fileName); + throw makeStringExceptionV(DFSERR_LookupAccessDenied, "Lookup permission denied for physical file(s): %s, %s", fileName, context.str()); } } @@ -618,12 +626,13 @@ class CDFUengine: public CInterface, implements IDFUengine void checkPhysicalFilePermissions(IFileDescriptor *fd,IUserDescriptor *user,bool write) { unsigned auditflags = (DALI_LDAP_AUDIT_REPORT|DALI_LDAP_READ_WANTED); + logNullUser(user);//stack trace if NULL user if (write) auditflags |= DALI_LDAP_WRITE_WANTED; SecAccessFlags perm = queryDistributedFileDirectory().getFDescPermissions(fd,user,auditflags); StringBuffer name; - ensureFilePermissions(nullptr,getFDescName(fd,name),perm,write); + ensureFilePermissions(nullptr,getFDescName(fd,name),perm,user,write); } void checkForeignFilePermissions(IConstDFUfileSpec *fSpec,IFileDescriptor *fd,IUserDescriptor *user) @@ -667,6 +676,7 @@ class CDFUengine: public CInterface, implements IDFUengine void checkPlaneFilePermissions(IFileDescriptor *fd,IUserDescriptor *user,bool write) { + logNullUser(user);//stack trace if NULL user //This function checks the scope permissions for a file or files that reside in a single directory on a single plane. //The IFileDescriptor is used to discover the plane and directory. //If the plane is not present, it implies that it is a bare-metal system and useDropZoneRestriction is off, and there @@ -697,7 +707,8 @@ class CDFUengine: public CInterface, implements IDFUengine { if (getGlobalConfigSP()->getPropBool("expert/@failOverToLegacyPhysicalPerms",!isContainerized())) perm = queryDistributedFileDirectory().getFDescPermissions(fd,user,auditflags); - ensureFilePermissions(planeName,relativePath,perm,write); + + ensureFilePermissions(planeName,relativePath,perm,user,write); } } else diff --git a/dali/ft/filecopy.cpp b/dali/ft/filecopy.cpp index 6834135bffa..ad88915c46c 100644 --- a/dali/ft/filecopy.cpp +++ b/dali/ft/filecopy.cpp @@ -1023,6 +1023,7 @@ void FileSprayer::beforeTransfer() io->write(lastOutputOffset-sizeof(null), sizeof(null), &null); } } + io->close(); } } } diff --git a/dali/ft/ftbase.ipp b/dali/ft/ftbase.ipp index 2c6ea512d30..70c472a612a 100644 --- a/dali/ft/ftbase.ipp +++ b/dali/ft/ftbase.ipp @@ -134,6 +134,10 @@ public: virtual offset_t tell() override; virtual size32_t write(size32_t len, const void * data) override; virtual unsigned __int64 getStatistic(StatisticKind kind) override { return stream->getStatistic(kind); } + virtual void close() override + { + stream->close(); + } unsigned getCRC() { return crc; } void setCRC(unsigned long _crc) { crc = _crc; } diff --git a/dali/ft/fttransform.cpp b/dali/ft/fttransform.cpp index 4e8ca2fdec8..db88821696e 100644 --- a/dali/ft/fttransform.cpp +++ b/dali/ft/fttransform.cpp @@ -790,6 +790,8 @@ bool TransferServer::pull() assertex(curProgress.status != OutputProgress::StatusRenamed); if (curProgress.status != OutputProgress::StatusCopied) { + if (out) + out->close(); out.setown(createIOStream(outio)); out->seek(progressOffset, IFSbegin); wrapOutInCRC(curProgress.outputCRC); @@ -885,6 +887,8 @@ bool TransferServer::pull() } } + if (out) + out->close(); out.setown(createIOStream(outio)); out->seek(0, IFSbegin); wrapOutInCRC(0); @@ -903,7 +907,10 @@ bool TransferServer::pull() } crcOut.clear(); + if (out) + out->close(); out.clear(); + //Once the transfers have completed, rename the files, and sync file times //if replicating... if (!isSafeMode) @@ -997,6 +1004,7 @@ bool TransferServer::push() } outio.setown(createCompressedFileWriter(outio, false, 0, true, compressor, COMPRESS_METHOD_LZ4)); } + out.setown(createIOStream(outio)); if (!compressOutput) out->seek(curPartition.outputOffset + curProgress.outputLength, IFSbegin); @@ -1011,6 +1019,7 @@ bool TransferServer::push() sendProgress(curProgress); } crcOut.clear(); + out->close(); out.clear(); } } diff --git a/dali/server/daserver.cpp b/dali/server/daserver.cpp index cdd2f21a1e9..f0004457432 100644 --- a/dali/server/daserver.cpp +++ b/dali/server/daserver.cpp @@ -612,6 +612,7 @@ int main(int argc, const char* argv[]) backupCheck.append("bakchk.").append((unsigned)GetCurrentProcessId()); OwnedIFile iFileDataDir = createIFile(backupCheck.str()); OwnedIFileIO iFileIO = iFileDataDir->open(IFOcreate); + iFileIO->close(); iFileIO.clear(); try { diff --git a/ecl/ecl-bundle/ecl-bundle.cpp b/ecl/ecl-bundle/ecl-bundle.cpp index 1393de7f04b..b91a316752c 100644 --- a/ecl/ecl-bundle/ecl-bundle.cpp +++ b/ecl/ecl-bundle/ecl-bundle.cpp @@ -750,6 +750,7 @@ class CBundleInfoSet : public CInterfaceOf VStringBuffer redirect("IMPORT %s.%s.%s.%s as _%s; EXPORT %s := _%s;", VERSION_SUBDIR, name, version, name, name, name, name); Owned rfile = redirector->open(IFOcreate); rfile->write(0, redirect.length(), redirect.str()); + rfile->close(); bundle->setActive(true); } virtual void setActive(const char *version) diff --git a/ecl/eclcc/eclcc.cpp b/ecl/eclcc/eclcc.cpp index 2898dedc7b0..3f73dbc466b 100644 --- a/ecl/eclcc/eclcc.cpp +++ b/ecl/eclcc/eclcc.cpp @@ -1990,8 +1990,11 @@ void EclCC::outputXmlToOutputFile(EclCompileInstance & instance, IPropertyTree * { //Work around windows problem writing 64K to stdout if not redirected/piped Owned stream = createIOStream(ifileio.get()); - Owned buffered = createBufferedIOStream(stream,0x8000); - saveXML(*buffered, xml); + { + Owned buffered = createBufferedIOStream(stream,0x8000); + saveXML(*buffered, xml); + } + ifileio->close(); } } @@ -2044,7 +2047,10 @@ void EclCC::generateOutput(EclCompileInstance & instance) OwnedIFileIO ifileio = createArchiveOutputFile(instance); if (ifileio) + { ifileio->write(0, filenames.length(), filenames.str()); + ifileio->close(); + } } else { diff --git a/ecl/eclccserver/eclccserver.cpp b/ecl/eclccserver/eclccserver.cpp index 3542dab6b25..175fedfd2d6 100644 --- a/ecl/eclccserver/eclccserver.cpp +++ b/ecl/eclccserver/eclccserver.cpp @@ -559,6 +559,7 @@ class EclccCompileThread : implements IPooledThread, implements IErrorReporter, { Owned dstIO = dstfile->open(IFOwrite); dstIO->write(0, output.length(), output.str()); + dstIO->close(); IArrayOf errors; extractErrorsFromCppLog(errors, output.str(), numFailed != 0); diff --git a/ecl/eclcmd/queries/ecl-queries.cpp b/ecl/eclcmd/queries/ecl-queries.cpp index 19501a9016f..b3e192ccfe5 100644 --- a/ecl/eclcmd/queries/ecl-queries.cpp +++ b/ecl/eclcmd/queries/ecl-queries.cpp @@ -1382,7 +1382,10 @@ class EclCmdQueriesExport : public EclCmdCommon fprintf(stdout, "\nWriting to file %s\n", file->queryFilename()); if (io.get()) + { io->write(0, strlen(s), s); + io->close(); + } else fprintf(stderr, "\nFailed to create file %s\n", file->queryFilename()); } diff --git a/ecl/hql/hqlcache.cpp b/ecl/hql/hqlcache.cpp index 1389f456b8c..5f7b056c3f9 100644 --- a/ecl/hql/hqlcache.cpp +++ b/ecl/hql/hqlcache.cpp @@ -492,6 +492,7 @@ static void extractFile(const char * path, const char * moduleName, const char * Owned io = file->open(IFOcreate); if (text) io->write(0, strlen(text), text); + io->close(); io.clear(); if (ts) { diff --git a/ecl/hql/hqlopt.cpp b/ecl/hql/hqlopt.cpp index 001acae84ae..bd4f5adcf98 100644 --- a/ecl/hql/hqlopt.cpp +++ b/ecl/hql/hqlopt.cpp @@ -4285,6 +4285,22 @@ IHqlExpression * CTreeOptimizer::doCreateTransformed(IHqlExpression * transforme args.replace(*createConstant(true), 2); } + //This test currently duplicates the test in the enclosing if. This is in preparation for applying the logic + //to other joins later by removing the outer test. + if (transformed->hasAttribute(lookupAtom)) + { + //LOOKUP join implicitly dedups the right hand side by the hard-match-condition (unless MANY is specified). + //Technically this is not equivalent to KEEP(1) - since that is only applied after any soft-match condition + //However in this case the match condition is true (or row-invarient in very obscure cases), + //so they are identical and this transformation is valid. + if (!transformed->hasAttribute(manyAtom)) + { + //There should never be a KEEP attribute if MANY not specified - but remove in case. + removeAttribute(args, keepAtom); + args.append(*createAttribute(keepAtom, createConstant(1))); + } + removeAttribute(args, lookupAtom); + } args.append(*createAttribute(allAtom)); } if (doTrace(traceOptimizations)) diff --git a/ecl/hqlcpp/hqlecl.cpp b/ecl/hqlcpp/hqlecl.cpp index e4d028541d0..5e462750104 100644 --- a/ecl/hqlcpp/hqlecl.cpp +++ b/ecl/hqlcpp/hqlecl.cpp @@ -311,7 +311,7 @@ void HqlDllGenerator::expandCode(StringBuffer & filename, const char * codeTempl addDirectoryPrefix(fullname, targetDir).append(filename); Owned out = createIFile(fullname.str()); - Owned expander = createTemplateExpander(out, codeTemplate); + Owned expander = createTemplateExpander(codeTemplate); Owned writer = createCppWriter(*code, compiler); Owned props = createProperties(true); @@ -325,7 +325,7 @@ void HqlDllGenerator::expandCode(StringBuffer & filename, const char * codeTempl props->setProp("headerName", headerName.str()); props->setProp("outputName", fullname.str()); - expander->generate(*writer, pass, props); + expander->generate(*writer, out, pass, props); totalGeneratedSize += out->size(); diff --git a/ecl/hqlcpp/hqlwcpp.cpp b/ecl/hqlcpp/hqlwcpp.cpp index 0adcc71d34c..ef59a67735c 100644 --- a/ecl/hqlcpp/hqlwcpp.cpp +++ b/ecl/hqlcpp/hqlwcpp.cpp @@ -103,9 +103,14 @@ CppWriterTemplate::CppWriterTemplate(const char * codeTemplate) loadTemplate(codeTemplate); } -void CppWriterTemplate::generate(ISectionWriter & writer, unsigned pass, IProperties * properties) +void CppWriterTemplate::generate(ISectionWriter & writer, IFile * outputFile, unsigned pass, IProperties * properties) { - writer.setOutput(out, outStream); + Owned io = outputFile->open(IFOcreate); + if (!io) + throwError1(HQLERR_CouldNotCreateOutputX, outputFile->queryFilename()); + + outStream.setown(createIOStream(io)); + writer.setOutput(outputFile, outStream); const char * finger = text; bool output = true; @@ -149,6 +154,8 @@ void CppWriterTemplate::generate(ISectionWriter & writer, unsigned pass, IProper outputQuoted(writer, end-finger, finger); writer.setOutput(NULL, NULL); + outStream.clear(); + io->close(); } void CppWriterTemplate::loadTemplate(const char * codeTemplate) @@ -2271,11 +2278,9 @@ void HqlCppSectionWriter::generateSection(unsigned delta, IAtom * section, unsig //--------------------------------------------------------------------------- -ITemplateExpander * createTemplateExpander(IFile * output, const char * codeTemplate) +ITemplateExpander * createTemplateExpander(const char * codeTemplate) { - Owned expander = new CppWriterTemplate(codeTemplate); - expander->setOutput(output); - return expander.getClear(); + return new CppWriterTemplate(codeTemplate); } ISectionWriter * createCppWriter(IHqlCppInstance & _instance, CompilerType compiler) diff --git a/ecl/hqlcpp/hqlwcpp.hpp b/ecl/hqlcpp/hqlwcpp.hpp index 535c41374b8..e8001cc1de3 100644 --- a/ecl/hqlcpp/hqlwcpp.hpp +++ b/ecl/hqlcpp/hqlwcpp.hpp @@ -35,7 +35,7 @@ interface HQLCPP_API ISectionWriter : public IInterface interface HQLCPP_API ITemplateExpander : public IInterface { public: - virtual void generate(ISectionWriter & writer, unsigned pass, IProperties * properties = NULL) = 0; + virtual void generate(ISectionWriter & writer, IFile * _output, unsigned pass, IProperties * properties = NULL) = 0; }; extern HQLCPP_API StringBuffer & generateExprCpp(StringBuffer & out, IHqlExpression * expr, CompilerType compiler); @@ -43,7 +43,7 @@ extern HQLCPP_API StringBuffer & generateTypeCpp(StringBuffer & out, ITypeInfo * bool generateFunctionPrototype(StringBuffer & out, IHqlExpression * funcdef, CompilerType compiler); void generateFunctionReturnType(StringBuffer & prefix, StringBuffer & params, ITypeInfo * retType, IHqlExpression * attrs, CompilerType compiler); -extern HQLCPP_API ITemplateExpander * createTemplateExpander(IFile * output, const char * codeTemplate); +extern HQLCPP_API ITemplateExpander * createTemplateExpander(const char * codeTemplate); extern HQLCPP_API ISectionWriter * createCppWriter(IHqlCppInstance & _instance, CompilerType compiler); extern bool isTypePassedByAddress(ITypeInfo * type); diff --git a/ecl/hqlcpp/hqlwcpp.ipp b/ecl/hqlcpp/hqlwcpp.ipp index 8074016b25f..613c5da19fd 100644 --- a/ecl/hqlcpp/hqlwcpp.ipp +++ b/ecl/hqlcpp/hqlwcpp.ipp @@ -23,7 +23,7 @@ public: CppWriterTemplate(const char * codeTemplate); IMPLEMENT_IINTERFACE - virtual void generate(ISectionWriter & writer, unsigned pass, IProperties * properties = NULL); + virtual void generate(ISectionWriter & writer, IFile * _output, unsigned pass, IProperties * properties = NULL); void outputQuoted(ISectionWriter & writer, size32_t len, const char * str) { @@ -31,17 +31,6 @@ public: outStream->write(len, str); } - void setOutput(IFile * _output) - { - Owned io = _output->open(IFOcreate); - if (!io) - throwError1(HQLERR_CouldNotCreateOutputX, _output->queryFilename()); - - out.set(_output); - outStream.setown(createIOStream(io)); - } - - private: void loadTemplate(const char * codeTemplate); enum TplSectionType { TplEmbed, TplExpand, TplCondition, TplEndCondition }; @@ -58,7 +47,6 @@ protected: const char * text; unsigned len; CIArray sections; - Owned out; Owned outStream; }; diff --git a/ecl/hthor/hthor.cpp b/ecl/hthor/hthor.cpp index 166cea2c0a7..e00fb39bcb7 100644 --- a/ecl/hthor/hthor.cpp +++ b/ecl/hthor/hthor.cpp @@ -673,6 +673,7 @@ void CHThorDiskWriteActivity::close() { io->flush(); numDiskWrites = io->getStatistic(StNumDiskWrites); + io->close(); io.clear(); } if(clusterHandler) @@ -1295,6 +1296,7 @@ void CHThorIndexWriteActivity::execute() offsetBranches = builder->getOffsetBranches(); out->flush(); out.clear(); + io->close(); } if(clusterHandler) diff --git a/esp/bindings/SOAP/Platform/soapbind.cpp b/esp/bindings/SOAP/Platform/soapbind.cpp index b0e181a92bb..8e1b2ab9735 100644 --- a/esp/bindings/SOAP/Platform/soapbind.cpp +++ b/esp/bindings/SOAP/Platform/soapbind.cpp @@ -258,7 +258,7 @@ int CHttpSoapBinding::HandleSoapRequest(CHttpRequest* request, CHttpResponse* re response->sendBasicChallenge(m_challenge_realm.str(), false); else if (status == SOAP_AUTHENTICATION_ERROR) { - throw MakeStringExceptionDirect(401,"Unauthorized Access"); + throw makeStringExceptionV(401, "Unauthorized Access: %s - user=%s", ctx->queryServiceName(nullptr), nullText(ctx->queryUserId())); } else response->setStatus(HTTP_STATUS_OK); diff --git a/esp/src/package-lock.json b/esp/src/package-lock.json index ecb1390c775..3391efbb526 100644 --- a/esp/src/package-lock.json +++ b/esp/src/package-lock.json @@ -51,6 +51,7 @@ "react-hook-form": "7.51.2", "react-hot-toast": "2.4.1", "react-reflex": "4.2.6", + "react-singleton-hook": "3.4.0", "react-sizeme": "3.0.2", "universal-router": "9.2.0", "xstyle": "0.3.3" @@ -9777,6 +9778,23 @@ "react-dom": ">0.13.0" } }, + "node_modules/react-singleton-hook": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/react-singleton-hook/-/react-singleton-hook-3.4.0.tgz", + "integrity": "sha512-eQEpyacGAaRejmWUizUdNNQFn5AO0iaKRSl1jxgC0FQadVY/I1WFuPrYiutglPzO9s8yEbIh95UXVJQel4d7HQ==", + "license": "MIT", + "peerDependencies": { + "react": "15 - 18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-sizeme": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/react-sizeme/-/react-sizeme-3.0.2.tgz", diff --git a/esp/src/package.json b/esp/src/package.json index e9d7224d6f4..de3511485ec 100644 --- a/esp/src/package.json +++ b/esp/src/package.json @@ -77,6 +77,7 @@ "react-hook-form": "7.51.2", "react-hot-toast": "2.4.1", "react-reflex": "4.2.6", + "react-singleton-hook": "3.4.0", "react-sizeme": "3.0.2", "universal-router": "9.2.0", "xstyle": "0.3.3" diff --git a/esp/src/src-react/components/Files.tsx b/esp/src/src-react/components/Files.tsx index 6cc33214cbb..95cf41ee7b6 100644 --- a/esp/src/src-react/components/Files.tsx +++ b/esp/src/src-react/components/Files.tsx @@ -70,10 +70,12 @@ function formatQuery(_filter): { [id: string]: any } { delete filter.NotInSuperFiles; delete filter.Indexes; if (filter.StartDate) { + if (filter.StartDate.indexOf("Z") < 0) { filter.StartDate += ":00.000Z"; } filter.StartDate = new Date(filter.StartDate).toISOString(); } if (filter.EndDate) { - filter.EndDate = new Date(filter.StartDate).toISOString(); + if (filter.EndDate.indexOf("Z") < 0) { filter.EndDate += ":00.000Z"; } + filter.EndDate = new Date(filter.EndDate).toISOString(); } return filter; } diff --git a/esp/src/src-react/components/Metrics.tsx b/esp/src/src-react/components/Metrics.tsx index 40413d012c7..95d16528912 100644 --- a/esp/src/src-react/components/Metrics.tsx +++ b/esp/src/src-react/components/Metrics.tsx @@ -11,7 +11,7 @@ import { scopedLogger } from "@hpcc-js/util"; import nlsHPCC from "src/nlsHPCC"; import { WUTimelineNoFetch } from "src/Timings"; import * as Utility from "src/Utility"; -import { FetchStatus, useMetricsOptions, useWUQueryMetrics, MetricsOptions as MetricsOptionsT } from "../hooks/metrics"; +import { FetchStatus, useMetricsViews, useWUQueryMetrics } from "../hooks/metrics"; import { HolyGrail } from "../layouts/HolyGrail"; import { AutosizeComponent, AutosizeHpccJSComponent } from "../layouts/HpccJSAdapter"; import { DockPanel, DockPanelItem, ResetableDockPanel } from "../layouts/DockPanel"; @@ -89,10 +89,10 @@ class TableEx extends Table { } _rawDataMap: { [id: number]: string } = {}; - metrics(metrics: any[], options: MetricsOptionsT, scopeFilter: string, matchCase: boolean): this { + metrics(metrics: any[], scopeTypes: string[], properties: string[], scopeFilter: string, matchCase: boolean): this { this .columns(["##"]) // Reset hash to force recalculation of default widths - .columns(["##", nlsHPCC.Type, "StdDevs", nlsHPCC.Scope, ...options.properties, "__StdDevs"]) + .columns(["##", nlsHPCC.Type, "StdDevs", nlsHPCC.Scope, ...properties, "__StdDevs"]) .columnFormats([ new ColumnFormatEx() .column("StdDevs") @@ -106,18 +106,18 @@ class TableEx extends Table { .data(metrics .filter(m => this.scopeFilterFunc(m, scopeFilter, matchCase)) .filter(row => { - return options.scopeTypes.indexOf(row.type) >= 0; + return scopeTypes.indexOf(row.type) >= 0; }).map((row, idx) => { if (idx === 0) { this._rawDataMap = { 0: "##", 1: "type", 2: "__StdDevs", 3: "name" }; - options.properties.forEach((p, idx2) => { + properties.forEach((p, idx2) => { this._rawDataMap[4 + idx2] = p; }); } row.__hpcc_id = row.name; - return [idx, row.type, row.__StdDevs === 0 ? undefined : row.__StdDevs, row.name, ...options.properties.map(p => { + return [idx, row.type, row.__StdDevs === 0 ? undefined : row.__StdDevs, row.name, ...properties.map(p => { return row.__groupedProps[p]?.Value ?? row.__groupedProps[p]?.Max ?? row.__groupedProps[p]?.Avg ?? @@ -157,6 +157,7 @@ class TableEx extends Table { } type SelectedMetricsSource = "" | "scopesTable" | "scopesSqlTable" | "metricGraphWidget" | "hotspot" | "reset"; +const TIMELINE_FIXEDHEIGHT = 152; interface MetricsProps { wuid: string; @@ -178,10 +179,9 @@ export const Metrics: React.FunctionComponent = ({ const [selectedMetrics, setSelectedMetrics] = React.useState([]); const [selectedMetricsPtr, setSelectedMetricsPtr] = React.useState(-1); const [metrics, columns, _activities, _properties, _measures, _scopeTypes, fetchStatus, refresh] = useWUQueryMetrics(wuid, querySet, queryId); + const { viewIds, viewId, setViewId, view, updateView } = useMetricsViews(); const [showMetricOptions, setShowMetricOptions] = React.useState(false); - const [options, setOptions, saveOptions] = useMetricsOptions(); const [dockpanel, setDockpanel] = React.useState(); - const [showTimeline, setShowTimeline] = React.useState(true); const [trackSelection, setTrackSelection] = React.useState(true); const [fullscreen, setFullscreen] = React.useState(false); const [hotspots, setHotspots] = React.useState(""); @@ -240,11 +240,14 @@ export const Metrics: React.FunctionComponent = ({ }, [parentUrl, timeline]); React.useEffect(() => { - timeline - .scopes(metrics) - .lazyRender() - ; - }, [metrics, timeline]); + if (view.showTimeline) { + timeline + .scopes(metrics) + .height(TIMELINE_FIXEDHEIGHT) + .lazyRender() + ; + } + }, [metrics, timeline, view.showTimeline]); // Scopes Table --- const onChangeScopeFilter = React.useCallback((event: React.FormEvent, newValue?: string) => { @@ -258,7 +261,7 @@ export const Metrics: React.FunctionComponent = ({ const scopesTable = useConst(() => new TableEx() .multiSelect(true) - .metrics([], options, scopeFilter, matchCase) + .metrics([], view.scopeTypes, view.properties, scopeFilter, matchCase) .sortable(true) ); @@ -274,13 +277,13 @@ export const Metrics: React.FunctionComponent = ({ React.useEffect(() => { scopesTable - .metrics(metrics, options, scopeFilter, matchCase) + .metrics(metrics, view.scopeTypes, view.properties, scopeFilter, matchCase) .lazyRender() ; - }, [matchCase, metrics, options, scopeFilter, scopesTable]); + }, [matchCase, metrics, scopeFilter, scopesTable, view.properties, view.scopeTypes]); const updateScopesTable = React.useCallback((selection: IScope[]) => { - if (scopesTable?.renderCount() > 0) { + if (scopesTable?.renderCount() > 0 && selectedMetricsSource !== "scopesTable") { scopesTable.selection([]); if (selection.length) { const selRows = scopesTable.data().filter(row => { @@ -291,7 +294,7 @@ export const Metrics: React.FunctionComponent = ({ }); } } - }, [scopesTable]); + }, [scopesTable, selectedMetricsSource]); // Graph --- const metricGraph = useConst(() => new MetricGraph()); @@ -490,9 +493,9 @@ export const Metrics: React.FunctionComponent = ({ }, [crossTabTable]); React.useEffect(() => { - const dot = metricGraph.graphTpl(selectedLineage ? [selectedLineage] : [], options); + const dot = metricGraph.graphTpl(selectedLineage ? [selectedLineage] : [], view); setDot(dot); - }, [metricGraph, options, selectedLineage]); + }, [metricGraph, view, selectedLineage]); React.useEffect(() => { let cancelled = false; @@ -530,13 +533,14 @@ export const Metrics: React.FunctionComponent = ({ React.useEffect(() => { // Update layout prior to unmount --- - if (dockpanel && options && saveOptions && setOptions) { + if (dockpanel && updateView) { return () => { - setOptions({ ...options, layout: dockpanel.getLayout() }); - saveOptions(); + if (dockpanel && updateView) { + updateView({ layout: dockpanel.getLayout() }); + } }; } - }, [dockpanel, options, saveOptions, setOptions]); + }, [dockpanel, updateView]); // Command Bar --- const buttons = React.useMemo((): ICommandBarItemProps[] => [ @@ -556,19 +560,30 @@ export const Metrics: React.FunctionComponent = ({ }, { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => }, { - key: "timeline", text: nlsHPCC.Timeline, canCheck: true, checked: showTimeline, iconProps: { iconName: "TimelineProgress" }, + key: "views", text: viewId, iconProps: { iconName: "View" }, + subMenuProps: { + items: viewIds.map(v => ({ + key: v, text: v, onClick: () => { + updateView({ layout: dockpanel.getLayout() }); + setViewId(v); + } + })) + }, + }, + { + key: "timeline", text: nlsHPCC.Timeline, canCheck: true, checked: view.showTimeline, iconProps: { iconName: "TimelineProgress" }, onClick: () => { - setShowTimeline(!showTimeline); + updateView({ showTimeline: !view.showTimeline }, true); } }, { key: "options", text: nlsHPCC.Options, iconProps: { iconName: "Settings" }, onClick: () => { - setOptions({ ...options, layout: dockpanel.layout() }); + updateView({ layout: dockpanel.getLayout() }); setShowMetricOptions(true); } } - ], [dockpanel, hotspots, onHotspot, options, refresh, setOptions, showTimeline, timeline]); + ], [dockpanel, hotspots, onHotspot, refresh, setViewId, timeline, updateView, view.showTimeline, viewId, viewIds]); const formatColumns = React.useMemo((): Utility.ColumnMap => { const copyColumns: Utility.ColumnMap = {}; @@ -610,7 +625,9 @@ export const Metrics: React.FunctionComponent = ({ } }] } - }, { + }, + { key: "divider_2", itemType: ContextualMenuItemType.Divider, onRender: () => }, + { key: "fullscreen", title: nlsHPCC.MaximizeRestore, iconProps: { iconName: fullscreen ? "ChromeRestore" : "FullScreen" }, onClick: () => setFullscreen(!fullscreen) } @@ -618,23 +635,18 @@ export const Metrics: React.FunctionComponent = ({ const setShowMetricOptionsHook = React.useCallback((show: boolean) => { setShowMetricOptions(show); - scopesTable - .metrics(metrics, options, scopeFilter, matchCase) - .render(() => { - updateScopesTable(selectedMetrics); - }) - ; + }, []); - }, [matchCase, metrics, options, scopeFilter, scopesTable, selectedMetrics, updateScopesTable]); + console.log("View ID", viewId, view.scopeTypes); return -