diff --git a/ApplicationExeCode/CMakeLists.txt b/ApplicationExeCode/CMakeLists.txt index 8b9f4a5955..28ba766559 100644 --- a/ApplicationExeCode/CMakeLists.txt +++ b/ApplicationExeCode/CMakeLists.txt @@ -302,12 +302,7 @@ set(UNITY_EXCLUDE_FILES # forever is used as variable name, and this symbol is defined by Qt and # used in precompiled headers ${ResInsight_SOURCE_DIR}/ThirdParty/gtest/gtest-all.cc - qrc_cafAnimControl.cpp - qrc_ResInsight.cpp - qrc_cafCommandFeatures.cpp - # Exclude files including opm-common - ProjectDataModel/RimVfpTableExtractor.cpp - ProjectDataModel/RimVfpPlot.cpp + qrc_cafAnimControl.cpp qrc_ResInsight.cpp qrc_cafCommandFeatures.cpp ) if(RESINSIGHT_ENABLE_UNITY_BUILD) diff --git a/ApplicationExeCode/Resources/ResInsight.qrc b/ApplicationExeCode/Resources/ResInsight.qrc index 7c2e9a61bd..cb62facd2e 100644 --- a/ApplicationExeCode/Resources/ResInsight.qrc +++ b/ApplicationExeCode/Resources/ResInsight.qrc @@ -287,6 +287,7 @@ regression-curve.svg padlock.svg warning.svg + cloud-and-server.svg fs_CellFace.glsl diff --git a/ApplicationExeCode/Resources/cloud-and-server.svg b/ApplicationExeCode/Resources/cloud-and-server.svg new file mode 100644 index 0000000000..67c6a4f24a --- /dev/null +++ b/ApplicationExeCode/Resources/cloud-and-server.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ApplicationLibCode/Application/CMakeLists_files.cmake b/ApplicationLibCode/Application/CMakeLists_files.cmake index 55bb5fa2e1..8dc2c7f0aa 100644 --- a/ApplicationLibCode/Application/CMakeLists_files.cmake +++ b/ApplicationLibCode/Application/CMakeLists_files.cmake @@ -11,6 +11,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesGrid.h ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSystem.h ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesOsdu.h + ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSumo.h ${CMAKE_CURRENT_LIST_DIR}/RiaPorosityModel.h ${CMAKE_CURRENT_LIST_DIR}/RiaSummaryCurveDefinition.h ${CMAKE_CURRENT_LIST_DIR}/RiaCurveSetDefinition.h @@ -54,6 +55,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesGrid.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSystem.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesOsdu.cpp + ${CMAKE_CURRENT_LIST_DIR}/RiaPreferencesSumo.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaPorosityModel.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaSummaryCurveDefinition.cpp ${CMAKE_CURRENT_LIST_DIR}/RiaCurveSetDefinition.cpp diff --git a/ApplicationLibCode/Application/RiaApplication.cpp b/ApplicationLibCode/Application/RiaApplication.cpp index aeae3a2659..70a26aaf0a 100644 --- a/ApplicationLibCode/Application/RiaApplication.cpp +++ b/ApplicationLibCode/Application/RiaApplication.cpp @@ -17,7 +17,10 @@ ///////////////////////////////////////////////////////////////////////////////// #include "RiaApplication.h" +#include "Cloud/RiaSumoConnector.h" +#include "Cloud/RiaSumoDefines.h" #include "OsduImportCommands/RiaOsduConnector.h" + #include "RiaArgumentParser.h" #include "RiaBaseDefs.h" #include "RiaFilePathTools.h" @@ -26,6 +29,7 @@ #include "RiaImportEclipseCaseTools.h" #include "RiaLogging.h" #include "RiaPreferences.h" +#include "RiaPreferencesSumo.h" #include "RiaPreferencesSystem.h" #include "RiaProjectModifier.h" #include "RiaSocketServer.h" @@ -1710,3 +1714,25 @@ RiaOsduConnector* RiaApplication::makeOsduConnector() m_osduConnector = new RiaOsduConnector( RiuMainWindow::instance(), server, dataPartitionId, authority, scopes, clientId ); return m_osduConnector; } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiaSumoConnector* RiaApplication::makeSumoConnector() +{ + if ( !m_sumoConnector ) + { + auto sumoPrefs = preferences()->sumoPreferences(); + const QString server = sumoPrefs->server(); + const QString authority = sumoPrefs->authority(); + const QString scopes = sumoPrefs->scopes(); + const QString clientId = sumoPrefs->clientId(); + + m_sumoConnector = new RiaSumoConnector( RiuMainWindow::instance(), server, authority, scopes, clientId ); + + m_sumoConnector->setTokenDataFilePath( RiaSumoDefines::tokenPath() ); + m_sumoConnector->importTokenFromFile(); + } + + return m_sumoConnector; +} diff --git a/ApplicationLibCode/Application/RiaApplication.h b/ApplicationLibCode/Application/RiaApplication.h index b98be5cd3e..c9e45cb537 100644 --- a/ApplicationLibCode/Application/RiaApplication.h +++ b/ApplicationLibCode/Application/RiaApplication.h @@ -69,6 +69,7 @@ class RiuPlotMainWindow; class RiuRecentFileActionProvider; class RiaArgumentParser; class RiaOsduConnector; +class RiaSumoConnector; namespace caf { @@ -202,6 +203,7 @@ public: virtual void showFormattedTextInMessageBoxOrConsole( const QString& errMsg ) = 0; RiaOsduConnector* makeOsduConnector(); + RiaSumoConnector* makeSumoConnector(); protected: // Protected implementation specific overrides @@ -259,4 +261,5 @@ protected: private: static RiaApplication* s_riaApplication; QPointer m_osduConnector; + QPointer m_sumoConnector; }; diff --git a/ApplicationLibCode/Application/RiaPreferences.cpp b/ApplicationLibCode/Application/RiaPreferences.cpp index ba163931a0..131551e6f1 100644 --- a/ApplicationLibCode/Application/RiaPreferences.cpp +++ b/ApplicationLibCode/Application/RiaPreferences.cpp @@ -27,6 +27,7 @@ #include "RiaPreferencesGeoMech.h" #include "RiaPreferencesGrid.h" #include "RiaPreferencesSummary.h" +#include "RiaPreferencesSumo.h" #include "RiaPreferencesSystem.h" #include "RiaQDateTimeTools.h" #include "RiaValidRegExpValidator.h" @@ -146,9 +147,6 @@ RiaPreferences::RiaPreferences() CAF_PDM_InitField( &m_storeBackupOfProjectFile, "storeBackupOfProjectFile", true, "Store Backup of Project Files" ); - CAF_PDM_InitField( &ssihubAddress, "ssihubAddress", QString( "http://" ), "SSIHUB Address" ); - ssihubAddress.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::TOP ); - CAF_PDM_InitFieldNoDefault( &m_defaultMeshModeType, "defaultMeshModeType", "Show Grid Lines" ); CAF_PDM_InitField( &defaultGridLineColors, "defaultGridLineColors", RiaColorTables::defaultGridLineColor(), "Mesh Color" ); CAF_PDM_InitField( &defaultFaultGridLineColors, @@ -273,6 +271,9 @@ RiaPreferences::RiaPreferences() CAF_PDM_InitFieldNoDefault( &m_osduPreferences, "osduPreferences", "osduPreferences" ); m_osduPreferences = new RiaPreferencesOsdu; + + CAF_PDM_InitFieldNoDefault( &m_sumoPreferences, "sumoPreferences", "sumoPreferences" ); + m_sumoPreferences = new RiaPreferencesSumo; } //-------------------------------------------------------------------------------------------------- @@ -370,8 +371,15 @@ void RiaPreferences::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& viewsGroup->add( &m_showInfoBox ); viewsGroup->add( &m_showGridBox, { .newRow = false, .totalColumnSpan = 1 } ); + caf::PdmUiGroup* loggingGroup = uiOrdering.addNewGroup( "Logging and Backup" ); + loggingGroup->add( &m_storeBackupOfProjectFile ); + loggingGroup->add( &m_loggerFilename ); + loggingGroup->add( &m_loggerFlushInterval ); + loggingGroup->add( &m_loggerTrapSignalAndFlush ); + m_loggerTrapSignalAndFlush.uiCapability()->setUiReadOnly( !m_loggerFilename().first ); + m_loggerFlushInterval.uiCapability()->setUiReadOnly( !m_loggerFilename().first ); + caf::PdmUiGroup* otherGroup = uiOrdering.addNewGroup( "Other" ); - otherGroup->add( &ssihubAddress ); otherGroup->add( &holoLensDisableCertificateVerification ); otherGroup->add( &m_useUndoRedo ); } @@ -466,17 +474,11 @@ void RiaPreferences::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& otherGroup->add( &m_gridCalculationExpressionFolder ); otherGroup->add( &m_summaryCalculationExpressionFolder ); - caf::PdmUiGroup* loggingGroup = uiOrdering.addNewGroup( "Logging and Backup" ); - loggingGroup->add( &m_storeBackupOfProjectFile ); - loggingGroup->add( &m_loggerFilename ); - loggingGroup->add( &m_loggerFlushInterval ); - loggingGroup->add( &m_loggerTrapSignalAndFlush ); - m_loggerTrapSignalAndFlush.uiCapability()->setUiReadOnly( !m_loggerFilename().first ); - m_loggerFlushInterval.uiCapability()->setUiReadOnly( !m_loggerFilename().first ); - } - else if ( uiConfigName == RiaPreferences::tabNameOsdu() ) - { - m_osduPreferences()->uiOrdering( uiConfigName, uiOrdering ); + caf::PdmUiGroup* osduGroup = uiOrdering.addNewGroup( "OSDU" ); + m_osduPreferences()->uiOrdering( uiConfigName, *osduGroup ); + + caf::PdmUiGroup* sumoGroup = uiOrdering.addNewGroup( "SUMO" ); + m_sumoPreferences()->uiOrdering( uiConfigName, *sumoGroup ); } else if ( RiaApplication::enableDevelopmentFeatures() && uiConfigName == RiaPreferences::tabNameSystem() ) { @@ -605,14 +607,6 @@ QString RiaPreferences::tabNameSystem() return "System"; } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -QString RiaPreferences::tabNameOsdu() -{ - return "Osdu"; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -653,7 +647,6 @@ QStringList RiaPreferences::tabNames() names << tabNameGeomech(); #endif names << tabNameImportExport(); - names << tabNameOsdu(); if ( RiaApplication::enableDevelopmentFeatures() ) { @@ -1028,6 +1021,14 @@ RiaPreferencesOsdu* RiaPreferences::osduPreferences() const return m_osduPreferences(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiaPreferencesSumo* RiaPreferences::sumoPreferences() const +{ + return m_sumoPreferences(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/Application/RiaPreferences.h b/ApplicationLibCode/Application/RiaPreferences.h index 89d8964805..c56cccf38c 100644 --- a/ApplicationLibCode/Application/RiaPreferences.h +++ b/ApplicationLibCode/Application/RiaPreferences.h @@ -46,6 +46,7 @@ class RiaPreferencesGeoMech; class RiaPreferencesSystem; class RiaPreferencesOsdu; class RiaPreferencesGrid; +class RiaPreferencesSumo; //-------------------------------------------------------------------------------------------------- /// @@ -129,6 +130,7 @@ public: RiaPreferencesSummary* summaryPreferences() const; RiaPreferencesSystem* systemPreferences() const; RiaPreferencesOsdu* osduPreferences() const; + RiaPreferencesSumo* sumoPreferences() const; RiaPreferencesGrid* gridPreferences() const; public: @@ -139,8 +141,6 @@ public: caf::PdmField scriptEditorExecutable; caf::PdmField showPythonDebugInfo; - caf::PdmField ssihubAddress; - caf::PdmField defaultGridLineColors; caf::PdmField defaultFaultGridLineColors; caf::PdmField defaultViewerBackgroundColor; @@ -171,7 +171,6 @@ private: static QString tabNamePlotting(); static QString tabNameScripting(); static QString tabNameSystem(); - static QString tabNameOsdu(); static QString tabNameImportExport(); static double defaultMarginSize( QPageSize::PageSizeId pageSizeId ); @@ -238,6 +237,7 @@ private: // Osdu settings caf::PdmChildField m_osduPreferences; + caf::PdmChildField m_sumoPreferences; // 3d view caf::PdmField> m_defaultMeshModeType; diff --git a/ApplicationLibCode/Application/RiaPreferencesSumo.cpp b/ApplicationLibCode/Application/RiaPreferencesSumo.cpp new file mode 100644 index 0000000000..0ad7f66e76 --- /dev/null +++ b/ApplicationLibCode/Application/RiaPreferencesSumo.cpp @@ -0,0 +1,75 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RiaApplication.h" + +#include "RiaPreferences.h" +#include "RiaPreferencesSumo.h" + +CAF_PDM_SOURCE_INIT( RiaPreferencesSumo, "RiaPreferencesSumo" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiaPreferencesSumo::RiaPreferencesSumo() +{ + CAF_PDM_InitFieldNoDefault( &m_server, "server", "Server" ); + CAF_PDM_InitFieldNoDefault( &m_authority, "authority", "Authority" ); + CAF_PDM_InitFieldNoDefault( &m_scopes, "scopes", "Scopes" ); + CAF_PDM_InitFieldNoDefault( &m_clientId, "clientId", "Client Id" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiaPreferencesSumo* RiaPreferencesSumo::current() +{ + return RiaApplication::instance()->preferences()->sumoPreferences(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaPreferencesSumo::server() const +{ + return m_server; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaPreferencesSumo::authority() const +{ + return m_authority; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaPreferencesSumo::scopes() const +{ + return m_scopes; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaPreferencesSumo::clientId() const +{ + return m_clientId; +} diff --git a/ApplicationLibCode/Application/RiaPreferencesSumo.h b/ApplicationLibCode/Application/RiaPreferencesSumo.h new file mode 100644 index 0000000000..a66c2bef67 --- /dev/null +++ b/ApplicationLibCode/Application/RiaPreferencesSumo.h @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafPdmField.h" +#include "cafPdmObject.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +class RiaPreferencesSumo : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + RiaPreferencesSumo(); + + static RiaPreferencesSumo* current(); + + QString server() const; + QString authority() const; + QString scopes() const; + QString clientId() const; + +private: + caf::PdmField m_server; + caf::PdmField m_authority; + caf::PdmField m_scopes; + caf::PdmField m_clientId; +}; diff --git a/ApplicationLibCode/Application/Tools/Cloud/CMakeLists_files.cmake b/ApplicationLibCode/Application/Tools/Cloud/CMakeLists_files.cmake new file mode 100644 index 0000000000..6fe5273a78 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/Cloud/CMakeLists_files.cmake @@ -0,0 +1,17 @@ +set(SOURCE_GROUP_HEADER_FILES + ${CMAKE_CURRENT_LIST_DIR}/RiaSumoConnector.h + ${CMAKE_CURRENT_LIST_DIR}/RiaSumoDefines.h + ${CMAKE_CURRENT_LIST_DIR}/RiaConnectorTools.h +) + +set(SOURCE_GROUP_SOURCE_FILES + ${CMAKE_CURRENT_LIST_DIR}/RiaSumoConnector.cpp + ${CMAKE_CURRENT_LIST_DIR}/RiaSumoDefines.cpp + ${CMAKE_CURRENT_LIST_DIR}/RiaConnectorTools.cpp +) + +list(APPEND QT_MOC_HEADERS ${CMAKE_CURRENT_LIST_DIR}/RiaSumoConnector.h) + +list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) + +list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES}) diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.cpp b/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.cpp new file mode 100644 index 0000000000..c7872bc1b5 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.cpp @@ -0,0 +1,80 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RiaConnectorTools.h" + +#include +#include +#include +#include +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaConnectorTools::tokenDataAsJson( QOAuth2AuthorizationCodeFlow* authCodeFlow ) +{ + QJsonObject obj; + obj.insert( "token", authCodeFlow->token() ); + obj.insert( "refreshToken", authCodeFlow->refreshToken() ); + + QJsonDocument doc( obj ); + return doc.toJson( QJsonDocument::Indented ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaConnectorTools::initializeTokenDataFromJson( QOAuth2AuthorizationCodeFlow* authCodeFlow, const QString& tokenDataJson ) +{ + QJsonDocument doc = QJsonDocument::fromJson( tokenDataJson.toUtf8() ); + QJsonObject obj = doc.object(); + + authCodeFlow->setToken( obj["token"].toString() ); + authCodeFlow->setRefreshToken( obj["refreshToken"].toString() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaConnectorTools::writeTokenData( const QString& filePath, const QString& tokenDataJson ) +{ + QFile file( filePath ); + if ( file.open( QIODevice::WriteOnly ) ) + { + QTextStream stream( &file ); + stream << tokenDataJson; + file.close(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaConnectorTools::readTokenData( const QString& filePath ) +{ + QFile file( filePath ); + if ( file.open( QIODevice::ReadOnly ) ) + { + QTextStream stream( &file ); + QString result = stream.readAll(); + file.close(); + return result; + } + return {}; +} diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.h b/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.h new file mode 100644 index 0000000000..5acb2f4f8e --- /dev/null +++ b/ApplicationLibCode/Application/Tools/Cloud/RiaConnectorTools.h @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +class QOAuth2AuthorizationCodeFlow; + +namespace RiaConnectorTools +{ +QString tokenDataAsJson( QOAuth2AuthorizationCodeFlow* authCodeFlow ); +void initializeTokenDataFromJson( QOAuth2AuthorizationCodeFlow* authCodeFlow, const QString& tokenDataJson ); +void writeTokenData( const QString& filePath, const QString& tokenDataJson ); +QString readTokenData( const QString& filePath ); +} // namespace RiaConnectorTools diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.cpp b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.cpp new file mode 100644 index 0000000000..9e7feda679 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.cpp @@ -0,0 +1,1110 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RiaSumoConnector.h" + +#include "RiaConnectorTools.h" +#include "RiaFileDownloader.h" +#include "RiaLogging.h" +#include "RiaOsduDefines.h" + +#include "OsduImportCommands/RiaOsduOAuthHttpServerReplyHandler.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiaSumoConnector::RiaSumoConnector( QObject* parent, const QString& server, const QString& authority, const QString& scopes, const QString& clientId ) + : QObject( parent ) + , m_server( server ) + , m_authority( authority ) + , m_scopes( scopes ) + , m_clientId( clientId ) +{ + m_authCodeFlow = new QOAuth2AuthorizationCodeFlow( this ); + m_networkAccessManager = new QNetworkAccessManager( this ); + m_authCodeFlow->setNetworkAccessManager( m_networkAccessManager ); + + RiaLogging::debug( "SSL BUILD VERSION: " + QSslSocket::sslLibraryBuildVersionString() ); + RiaLogging::debug( "SSL VERSION STRING: " + QSslSocket::sslLibraryVersionString() ); + + // NB: Make sure the port is not in use by another application + const unsigned int port = 53527; + + connect( m_authCodeFlow, + &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, + []( QUrl url ) + { + RiaLogging::info( "Authorize with url: " + url.toString() ); + QUrlQuery query( url ); + url.setQuery( query ); + QDesktopServices::openUrl( url ); + } ); + + QString authUrl = constructAuthUrl( m_authority ); + m_authCodeFlow->setAuthorizationUrl( QUrl( authUrl ) ); + + QString tokenUrl = constructTokenUrl( m_authority ); + m_authCodeFlow->setAccessTokenUrl( QUrl( tokenUrl ) ); + + // App key + m_authCodeFlow->setClientIdentifier( m_clientId ); + m_authCodeFlow->setScope( m_scopes ); + + auto replyHandler = new RiaOsduOAuthHttpServerReplyHandler( port, this ); + m_authCodeFlow->setReplyHandler( replyHandler ); + + connect( m_authCodeFlow, SIGNAL( granted() ), this, SLOT( accessGranted() ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::accessGranted() +{ + m_token = m_authCodeFlow->token(); + + QString tokenDataJson = RiaConnectorTools::tokenDataAsJson( m_authCodeFlow ); + RiaConnectorTools::writeTokenData( m_tokenDataFilePath, tokenDataJson ); + + emit tokenReady( m_token ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestFailed( const QAbstractOAuth::Error error ) +{ + RiaLogging::error( "Request failed: " ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::parquetDownloadComplete( const QString& blobId, const QByteArray& contents, const QString& url ) +{ + SumoRedirect obj; + obj.objectId = blobId; + obj.contents = contents; + obj.url = url; + + m_redirectInfo.push_back( obj ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestToken() +{ + RiaLogging::debug( "Requesting token." ); + m_authCodeFlow->grant(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiaSumoConnector::~RiaSumoConnector() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoConnector::token() const +{ + return m_token; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::importTokenFromFile() +{ + auto tokenDataJson = RiaConnectorTools::readTokenData( m_tokenDataFilePath ); + if ( !tokenDataJson.isEmpty() ) + { + RiaConnectorTools::initializeTokenDataFromJson( m_authCodeFlow, tokenDataJson ); + m_token = m_authCodeFlow->token(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::setTokenDataFilePath( const QString& filePath ) +{ + m_tokenDataFilePath = filePath; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestCasesForField( const QString& fieldName ) +{ + m_cases.clear(); + + requestTokenBlocking(); + + QNetworkRequest m_networkRequest; + m_networkRequest.setUrl( QUrl( constructSearchUrl( m_server ) ) ); + + addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() ); + + QString payloadTemplate = R"( +{ + "query": { + "bool": { + "filter": [ + {"term":{"class.keyword":"case"}}, + {"term":{"access.asset.name.keyword":"%1"}} + ] + } + }, + "sort": [ + {"tracklog.datetime":{"order":"desc"}} + ], + "track_total_hits":true, + "size":100, + "from":0 +} +)"; + + QString payload = payloadTemplate.arg( fieldName ); + auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() ); + + connect( reply, + &QNetworkReply::finished, + [this, reply]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + parseCases( reply ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestCasesForFieldBlocking( const QString& fieldName ) +{ + QEventLoop loop; + connect( this, SIGNAL( casesFinished() ), &loop, SLOT( quit() ) ); + QTimer timer; + + requestCasesForField( fieldName ); + + timer.setSingleShot( true ); + timer.start( RiaSumoDefines::requestTimeoutMillis() ); + loop.exec(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestAssets() +{ + requestTokenBlocking(); + + QNetworkRequest m_networkRequest; + m_networkRequest.setUrl( QUrl( m_server + "/api/v1/userpermissions" ) ); + + addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() ); + + auto reply = m_networkAccessManager->get( m_networkRequest ); + + connect( reply, + &QNetworkReply::finished, + [this, reply]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + parseAssets( reply ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestAssetsBlocking() +{ + QEventLoop loop; + connect( this, SIGNAL( assetsFinished() ), &loop, SLOT( quit() ) ); + QTimer timer; + + requestAssets(); + + timer.setSingleShot( true ); + timer.start( RiaSumoDefines::requestTimeoutMillis() ); + loop.exec(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestEnsembleByCasesId( const SumoCaseId& caseId ) +{ + QString payloadTemplate = R"( + +{ + "query": { + "bool": { + "filter": [ + {"term":{"_sumo.parent_object.keyword":"%1"}} + ] + } + }, + "aggs": { + "aggs_columns": { + "terms": { "field": "fmu.iteration.name.keyword", "size": 5 } + } + }, + "track_total_hits":true, + "size":20, + "from":0, + "_source": false +} + +)"; + + QNetworkRequest m_networkRequest; + m_networkRequest.setUrl( QUrl( m_server + "/api/v1/search" ) ); + + addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() ); + + auto payload = payloadTemplate.arg( caseId.get() ); + auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() ); + + connect( reply, + &QNetworkReply::finished, + [this, reply, caseId]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + parseEnsembleNames( reply, caseId ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestEnsembleByCasesIdBlocking( const SumoCaseId& caseId ) +{ + QEventLoop loop; + connect( this, SIGNAL( ensembleNamesFinished() ), &loop, SLOT( quit() ) ); + QTimer timer; + + requestEnsembleByCasesId( caseId ); + + timer.setSingleShot( true ); + timer.start( RiaSumoDefines::requestTimeoutMillis() ); + loop.exec(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestVectorNamesForEnsemble( const SumoCaseId& caseId, const QString& ensembleName ) +{ + QString payloadTemplate = R"( +{ + "track_total_hits": true, + "query": { "bool": { + "must": [ + {"term": {"class": "table"}}, + {"term": {"_sumo.parent_object.keyword": "%1"}}, + {"term": {"fmu.iteration.name.keyword": "%2"}}, + {"term": {"fmu.context.stage.keyword": "iteration"}}, + {"term": {"fmu.aggregation.operation.keyword": "collection"}}, + {"term": {"data.tagname.keyword": "summary"}}, + {"term": {"data.content.keyword": "timeseries"}} + ]} + }, + "aggs": { + "smry_tables": { + "terms": { + "field": "data.name.keyword" + }, + "aggs": { + "smry_columns": { + "terms": { + "field": "data.spec.columns.keyword", + "size": 65535 + } + } + } + } + }, + "_source": false, + "size": 0 +})"; + + QNetworkRequest m_networkRequest; + m_networkRequest.setUrl( QUrl( m_server + "/api/v1/search" ) ); + + addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() ); + + auto payload = payloadTemplate.arg( caseId.get() ).arg( ensembleName ); + auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() ); + + connect( reply, + &QNetworkReply::finished, + [this, reply, ensembleName, caseId]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + parseVectorNames( reply, caseId, ensembleName ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestVectorNamesForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName ) +{ + QEventLoop loop; + connect( this, SIGNAL( vectorNamesFinished() ), &loop, SLOT( quit() ) ); + QTimer timer; + + requestVectorNamesForEnsemble( caseId, ensembleName ); + + timer.setSingleShot( true ); + timer.start( RiaSumoDefines::requestTimeoutMillis() ); + loop.exec(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestRealizationIdsForEnsemble( const SumoCaseId& caseId, const QString& ensembleName ) +{ + QString payloadTemplate = R"( +{ + "track_total_hits": true, + "query": { + "bool": { + "must": [ + {"term": {"class": "table"}}, + {"term": {"_sumo.parent_object.keyword": "%1"}}, + {"term": {"fmu.iteration.name.keyword": "%2"}}, + {"term": {"fmu.context.stage.keyword": "iteration"}}, + {"term": {"fmu.aggregation.operation.keyword": "collection"}}, + {"term": {"data.tagname.keyword": "summary"}}, + {"term": {"data.content.keyword": "timeseries"}} + ]} + }, + "aggs": { + "realization-ids": { + "terms": { + "field": "fmu.aggregation.realization_ids", + "size":1000 + } + } + }, + "_source": false, + "size":0 +} +)"; + m_realizationIds.clear(); + + QNetworkRequest m_networkRequest; + m_networkRequest.setUrl( QUrl( m_server + "/api/v1/search" ) ); + + addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() ); + + auto payload = payloadTemplate.arg( caseId.get() ).arg( ensembleName ); + auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() ); + + connect( reply, + &QNetworkReply::finished, + [this, reply, ensembleName, caseId]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + parseRealizationNumbers( reply, caseId, ensembleName ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestRealizationIdsForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName ) +{ + QEventLoop loop; + connect( this, SIGNAL( realizationIdsFinished() ), &loop, SLOT( quit() ) ); + QTimer timer; + + requestRealizationIdsForEnsemble( caseId, ensembleName ); + + timer.setSingleShot( true ); + timer.start( RiaSumoDefines::requestTimeoutMillis() ); + loop.exec(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestBlobIdForEnsemble( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName ) +{ + QString payloadTemplate = R"( +{ + "track_total_hits": true, + "query": { "bool": { + "must": [ + {"term": {"class": "table"}}, + {"term": {"_sumo.parent_object.keyword": "%1"}}, + {"term": {"fmu.iteration.name.keyword": "%2"}}, + {"term": {"fmu.context.stage.keyword": "iteration"}}, + {"term": {"fmu.aggregation.operation.keyword": "collection"}}, + {"term": {"data.tagname.keyword": "summary"}}, + {"term": {"data.spec.columns.keyword": "%3"}} + ]} + }, + "fields": [ + "data.name", + "_sumo.blob_name" + ], + "_source": true, + "size": 1 +} +)"; + + QNetworkRequest m_networkRequest; + m_networkRequest.setUrl( QUrl( m_server + "/api/v1/search" ) ); + + addStandardHeader( m_networkRequest, m_token, RiaDefines::contentTypeJson() ); + + auto payload = payloadTemplate.arg( caseId.get() ).arg( ensembleName ).arg( vectorName ); + auto reply = m_networkAccessManager->post( m_networkRequest, payload.toUtf8() ); + + connect( reply, + &QNetworkReply::finished, + [this, reply, ensembleName, caseId, vectorName]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + parseBlobIds( reply, caseId, ensembleName, vectorName ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestBlobIdForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName ) +{ + QEventLoop loop; + connect( this, SIGNAL( blobIdFinished() ), &loop, SLOT( quit() ) ); + QTimer timer; + + requestBlobIdForEnsemble( caseId, ensembleName, vectorName ); + + timer.setSingleShot( true ); + timer.start( RiaSumoDefines::requestTimeoutMillis() ); + loop.exec(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestBlobDownload( const QString& blobId ) +{ + QString url = constructDownloadUrl( m_server, blobId ); + + QNetworkRequest networkRequest; + networkRequest.setUrl( url ); + + // Other redirection policies are NoLessSafeRedirectPolicy, SameOriginRedirectPolicy, UserVerifiedRedirectPolicy. They were tested, but + // did not work. Use ManualRedirectPolicy instead, and inspect the reply for the redirection target. + networkRequest.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy ); + + addStandardHeader( networkRequest, m_token, RiaDefines::contentTypeJson() ); + + auto reply = m_networkAccessManager->get( networkRequest ); + + connect( reply, + &QNetworkReply::finished, + [this, reply, blobId, url]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + auto contents = reply->readAll(); + + QVariant redirectUrl = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ); + if ( redirectUrl.isValid() ) + { + requestBlobByRedirectUri( blobId, redirectUrl.toString() ); + } + else + { + QString errorMessage = "Not able to parse and interpret valid redirect Url"; + RiaLogging::error( errorMessage ); + } + } + else + { + QString errorMessage = "Download failed: " + url + " failed." + reply->errorString(); + RiaLogging::error( errorMessage ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestBlobByRedirectUri( const QString& blobId, const QString& redirectUri ) +{ + QNetworkRequest networkRequest; + networkRequest.setUrl( redirectUri ); + + auto reply = m_networkAccessManager->get( networkRequest ); + + connect( reply, + &QNetworkReply::finished, + [this, reply, blobId, redirectUri]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + auto contents = reply->readAll(); + + QString msg = "Received data from : " + redirectUri; + RiaLogging::info( msg ); + + parquetDownloadComplete( blobId, contents, redirectUri ); + + emit parquetDownloadFinished( contents, redirectUri ); + } + else + { + QString errorMessage = "Download failed: " + redirectUri + " failed." + reply->errorString(); + RiaLogging::error( errorMessage ); + + emit parquetDownloadFinished( {}, redirectUri ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QByteArray RiaSumoConnector::requestParquetDataBlocking( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName ) +{ + requestBlobIdForEnsembleBlocking( caseId, ensembleName, vectorName ); + + if ( m_blobUrl.empty() ) return {}; + + auto blobId = m_blobUrl.back(); + + QEventLoop loop; + connect( this, SIGNAL( parquetDownloadFinished( const QByteArray&, const QString& ) ), &loop, SLOT( quit() ) ); + QTimer timer; + + requestBlobDownload( blobId ); + + timer.setSingleShot( true ); + timer.start( RiaSumoDefines::requestTimeoutMillis() ); + loop.exec(); + + for ( const auto& blobData : m_redirectInfo ) + { + if ( blobData.objectId == blobId ) + { + return blobData.contents; + } + } + + return {}; +} + +//-------------------------------------------------------------------------------------------------- +// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoConnector::constructSearchUrl( const QString& server ) +{ + return server + "/api/v1/search"; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoConnector::constructDownloadUrl( const QString& server, const QString& blobId ) +{ + return server + "/api/v1/objects('" + blobId + "')/blob"; + // https: // main-sumo-prod.radix.equinor.com/api/v1/objects('76d6d11f-2278-3fe2-f12f-77142ad163c6')/blob +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoConnector::constructAuthUrl( const QString& authority ) +{ + return authority + "/oauth2/v2.0/authorize"; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoConnector::constructTokenUrl( const QString& authority ) +{ + return authority + "/oauth2/v2.0/token"; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QNetworkReply* RiaSumoConnector::makeRequest( const std::map& parameters, const QString& server, const QString& token ) +{ + QNetworkRequest m_networkRequest; + m_networkRequest.setUrl( QUrl( constructSearchUrl( server ) ) ); + + addStandardHeader( m_networkRequest, token, RiaDefines::contentTypeJson() ); + + QJsonObject obj; + for ( auto [key, value] : parameters ) + { + obj.insert( key, value ); + } + + QJsonDocument doc( obj ); + QString strJson( doc.toJson( QJsonDocument::Compact ) ); + + auto reply = m_networkAccessManager->post( m_networkRequest, strJson.toUtf8() ); + return reply; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::parseAssets( QNetworkReply* reply ) +{ + QByteArray result = reply->readAll(); + reply->deleteLater(); + + if ( reply->error() == QNetworkReply::NoError ) + { + QJsonDocument doc = QJsonDocument::fromJson( result ); + QJsonObject jsonObj = doc.object(); + + m_assets.clear(); + + for ( auto key : jsonObj.keys() ) + { + QString id; + QString kind; + QString fieldName = key; + m_assets.push_back( SumoAsset{ SumoAssetId( id ), kind, fieldName } ); + } + + for ( auto a : m_assets ) + { + RiaLogging::info( QString( "Asset: %1" ).arg( a.name ) ); + } + } + emit assetsFinished(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::parseEnsembleNames( QNetworkReply* reply, const SumoCaseId& caseId ) +{ + QByteArray result = reply->readAll(); + reply->deleteLater(); + + if ( reply->error() == QNetworkReply::NoError ) + { + m_ensembleNames.clear(); + + QJsonDocument doc = QJsonDocument::fromJson( result ); + QJsonObject jsonObj = doc.object(); + auto keys_1 = jsonObj.keys(); + + auto aggregationsObject = jsonObj["aggregations"].toObject(); + + QJsonObject aggregationColumnsObject = aggregationsObject["aggs_columns"].toObject(); + auto keys_2 = aggregationColumnsObject.keys(); + + QJsonArray bucketsArray = aggregationColumnsObject["buckets"].toArray(); + foreach ( const QJsonValue& bucket, bucketsArray ) + { + QJsonObject bucketObj = bucket.toObject(); + auto keys_3 = bucketObj.keys(); + + auto ensembleName = bucketObj["key"].toString(); + m_ensembleNames.push_back( { caseId, ensembleName } ); + } + } + + emit ensembleNamesFinished(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::parseCases( QNetworkReply* reply ) +{ + QByteArray result = reply->readAll(); + reply->deleteLater(); + + if ( reply->error() == QNetworkReply::NoError ) + { + QJsonDocument doc = QJsonDocument::fromJson( result ); + QJsonObject jsonObj = doc.object(); + QJsonObject rootHits = jsonObj["hits"].toObject(); + + QJsonArray hitsObjects = rootHits["hits"].toArray(); + + m_cases.clear(); + + foreach ( const QJsonValue& value, hitsObjects ) + { + QJsonObject resultObj = value.toObject(); + auto keys_1 = resultObj.keys(); + + QJsonObject sourceObj = resultObj["_source"].toObject(); + auto sourceKeys = sourceObj.keys(); + + QJsonObject fmuObj = sourceObj["fmu"].toObject(); + auto fmuObjKeys = fmuObj.keys(); + + QJsonObject fmuCase = fmuObj["case"].toObject(); + auto fmuCaseKeys = fmuCase.keys(); + + QString id = resultObj["_id"].toString(); + QString kind = ""; + QString fieldName = fmuCase["name"].toString(); + m_cases.push_back( SumoCase{ SumoCaseId( id ), kind, fieldName } ); + } + + emit casesFinished(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::parseVectorNames( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName ) +{ + QByteArray result = reply->readAll(); + reply->deleteLater(); + + m_vectorNames.clear(); + + if ( reply->error() == QNetworkReply::NoError ) + { + QJsonDocument doc = QJsonDocument::fromJson( result ); + QJsonObject jsonObj = doc.object(); + + QJsonArray tableHits = jsonObj["aggregations"].toObject()["smry_tables"].toObject()["buckets"].toArray(); + for ( const auto& tableHit : tableHits ) + { + QJsonArray columnHits = tableHit.toObject()["smry_columns"].toObject()["buckets"].toArray(); + for ( const auto& columnHit : columnHits ) + { + m_vectorNames.push_back( columnHit.toObject()["key"].toString() ); + } + } + } + + emit vectorNamesFinished(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::parseRealizationNumbers( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName ) +{ + QByteArray result = reply->readAll(); + reply->deleteLater(); + + if ( reply->error() == QNetworkReply::NoError ) + { + QJsonDocument doc = QJsonDocument::fromJson( result ); + QJsonObject jsonObj = doc.object(); + + QJsonArray hits = jsonObj["aggregations"].toObject()["realization-ids"].toObject()["buckets"].toArray(); + for ( const auto& hit : hits ) + { + QJsonObject resultObj = hit.toObject(); + auto keys_1 = resultObj.keys(); + + auto val = resultObj.value( "key" ); + auto intValue = val.toInt(); + + auto realizationId = QString::number( intValue ); + m_realizationIds.push_back( realizationId ); + } + } + + emit realizationIdsFinished(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::parseBlobIds( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName ) +{ + QByteArray result = reply->readAll(); + reply->deleteLater(); + + m_blobUrl.clear(); + + if ( reply->error() == QNetworkReply::NoError ) + { + QJsonDocument doc = QJsonDocument::fromJson( result ); + QJsonObject jsonObj = doc.object(); + + QJsonObject rootHits = jsonObj["hits"].toObject(); + QJsonArray hitsObjects = rootHits["hits"].toArray(); + + foreach ( const QJsonValue& value, hitsObjects ) + { + QJsonObject resultObj = value.toObject(); + auto keys_1 = resultObj.keys(); + + QJsonObject sourceObj = resultObj["_source"].toObject(); + auto sourceKeys = sourceObj.keys(); + + QJsonObject fmuObj = sourceObj["_sumo"].toObject(); + auto fmuObjKeys = fmuObj.keys(); + + auto blobName = fmuObj["blob_name"].toString(); + m_blobUrl.push_back( blobName ); + } + } + + emit blobIdFinished(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::saveFile( QNetworkReply* reply, const QString& fileId ) +{ + QByteArray result = reply->readAll(); + reply->deleteLater(); + + if ( reply->error() == QNetworkReply::NoError ) + { + QEventLoop loop; + + QJsonDocument doc = QJsonDocument::fromJson( result ); + QJsonObject jsonObj = doc.object(); + + QString signedUrl = jsonObj["SignedUrl"].toString(); + + RiaFileDownloader* downloader = new RiaFileDownloader; + QUrl url( signedUrl ); + QString filePath = "/tmp/" + generateRandomString( 30 ) + ".txt"; + + QString formattedJsonString = doc.toJson( QJsonDocument::Indented ); + + RiaLogging::info( QString( "File download: %1 => %2" ).arg( signedUrl ).arg( filePath ) ); + connect( this, SIGNAL( fileDownloadFinished( const QString&, const QString& ) ), &loop, SLOT( quit() ) ); + connect( downloader, + &RiaFileDownloader::done, + [this, fileId, filePath]() + { + RiaLogging::info( QString( "Download complete %1 => %2" ).arg( fileId ).arg( filePath ) ); + emit( fileDownloadFinished( fileId, filePath ) ); + } ); + RiaLogging::info( "Starting download" ); + downloader->downloadFile( url, filePath ); + + downloader->deleteLater(); + loop.exec(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::addStandardHeader( QNetworkRequest& networkRequest, const QString& token, const QString& contentType ) +{ + networkRequest.setHeader( QNetworkRequest::ContentTypeHeader, contentType ); + networkRequest.setRawHeader( "Authorization", "Bearer " + token.toUtf8() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoConnector::requestTokenBlocking() +{ + if ( !m_token.isEmpty() ) return m_token; + + QTimer timer; + timer.setSingleShot( true ); + QEventLoop loop; + connect( this, SIGNAL( tokenReady( const QString& ) ), &loop, SLOT( quit() ) ); + connect( &timer, SIGNAL( timeout() ), &loop, SLOT( quit() ) ); + requestToken(); + timer.start( RiaSumoDefines::requestTimeoutMillis() ); + loop.exec(); + return m_token; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QNetworkReply* RiaSumoConnector::makeDownloadRequest( const QString& url, const QString& token, const QString& contentType ) +{ + QNetworkRequest m_networkRequest; + m_networkRequest.setUrl( QUrl( url ) ); + + addStandardHeader( m_networkRequest, token, contentType ); + + auto reply = m_networkAccessManager->get( m_networkRequest ); + return reply; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSumoConnector::requestParquetData( const QString& url, const QString& token ) +{ + RiaLogging::info( "Requesting download of parquet from: " + url ); + + auto reply = makeDownloadRequest( url, token, RiaDefines::contentTypeJson() ); + connect( reply, + &QNetworkReply::finished, + [this, reply, url]() + { + if ( reply->error() == QNetworkReply::NoError ) + { + QByteArray contents = reply->readAll(); + RiaLogging::info( QString( "Download succeeded: %1 bytes." ).arg( contents.length() ) ); + emit parquetDownloadFinished( contents, "" ); + } + else + { + QString errorMessage = "Download failed: " + url + " failed." + reply->errorString(); + RiaLogging::error( errorMessage ); + emit parquetDownloadFinished( QByteArray(), errorMessage ); + } + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoConnector::generateRandomString( int randomStringLength ) +{ + const QString possibleCharacters( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ); + QString randomString; + for ( int i = 0; i < randomStringLength; ++i ) + { + quint32 value = QRandomGenerator::global()->generate(); + int index = value % possibleCharacters.length(); + QChar nextChar = possibleCharacters.at( index ); + randomString.append( nextChar ); + } + return randomString; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoConnector::server() const +{ + return m_server; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RiaSumoConnector::assets() const +{ + return m_assets; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RiaSumoConnector::cases() const +{ + return m_cases; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RiaSumoConnector::ensembleNamesForCase( const SumoCaseId& caseId ) const +{ + std::vector ensembleNames; + for ( const auto& ensemble : m_ensembleNames ) + { + if ( ensemble.caseId == caseId ) + { + ensembleNames.push_back( ensemble.name ); + } + } + return ensembleNames; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RiaSumoConnector::vectorNames() const +{ + return m_vectorNames; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RiaSumoConnector::realizationIds() const +{ + return m_realizationIds; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RiaSumoConnector::blobUrls() const +{ + return m_blobUrl; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RiaSumoConnector::blobContents() const +{ + return m_redirectInfo; +} diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.h b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.h new file mode 100644 index 0000000000..3e8fb62a83 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoConnector.h @@ -0,0 +1,182 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RiaSumoDefines.h" + +#include +#include +#include + +#include + +using SumoObjectId = QString; + +struct SumoAsset +{ + SumoAssetId assetId; + + QString kind; + QString name; +}; + +struct SumoCase +{ + SumoCaseId caseId; + + QString kind; + QString name; +}; + +struct SumoRedirect +{ + SumoObjectId objectId; + QString blobName; + QString url; + QString redirectBaseUri; + QString redirectAuth; + QByteArray contents; +}; + +struct SumoEnsemble +{ + SumoCaseId caseId; + QString name; +}; + +//================================================================================================== +/// +//================================================================================================== +class RiaSumoConnector : public QObject +{ + Q_OBJECT +public: + RiaSumoConnector( QObject* parent, const QString& server, const QString& authority, const QString& scopes, const QString& clientId ); + ~RiaSumoConnector() override; + + QString token() const; + + void importTokenFromFile(); + void setTokenDataFilePath( const QString& filePath ); + + void requestAssets(); + void requestAssetsBlocking(); + + void requestCasesForField( const QString& fieldName ); + void requestCasesForFieldBlocking( const QString& fieldName ); + + void requestEnsembleByCasesId( const SumoCaseId& caseId ); + void requestEnsembleByCasesIdBlocking( const SumoCaseId& caseId ); + + void requestVectorNamesForEnsemble( const SumoCaseId& caseId, const QString& ensembleName ); + void requestVectorNamesForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName ); + + void requestRealizationIdsForEnsemble( const SumoCaseId& caseId, const QString& ensembleName ); + void requestRealizationIdsForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName ); + + void requestBlobIdForEnsemble( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName ); + void requestBlobIdForEnsembleBlocking( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName ); + + void requestBlobDownload( const QString& blobId ); + void requestBlobByRedirectUri( const QString& blobId, const QString& redirectUri ); + + QByteArray requestParquetDataBlocking( const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName ); + + QString server() const; + + std::vector assets() const; + std::vector cases() const; + std::vector ensembleNamesForCase( const SumoCaseId& caseId ) const; + std::vector vectorNames() const; + std::vector realizationIds() const; + std::vector blobUrls() const; + std::vector blobContents() const; + +public slots: + void requestToken(); + + void parseAssets( QNetworkReply* reply ); + void parseEnsembleNames( QNetworkReply* reply, const SumoCaseId& caseId ); + void parseCases( QNetworkReply* reply ); + void parseVectorNames( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName ); + void parseRealizationNumbers( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName ); + void parseBlobIds( QNetworkReply* reply, const SumoCaseId& caseId, const QString& ensembleName, const QString& vectorName ); + + void saveFile( QNetworkReply* reply, const QString& fileId ); + + void accessGranted(); + void requestFailed( const QAbstractOAuth::Error error ); + void parquetDownloadComplete( const QString& blobId, const QByteArray&, const QString& url ); + +signals: + void fileDownloadFinished( const QString& fileId, const QString& filePath ); + void casesFinished(); + void wellsFinished(); + void wellboresFinished( const QString& wellId ); + void wellboreTrajectoryFinished( const QString& wellboreId ); + void tokenReady( const QString& token ); + void parquetDownloadFinished( const QByteArray& contents, const QString& url ); + void ensembleNamesFinished(); + void vectorNamesFinished(); + void blobIdFinished(); + void assetsFinished(); + void realizationIdsFinished(); + +private: + void addStandardHeader( QNetworkRequest& networkRequest, const QString& token, const QString& contentType ); + + QString requestTokenBlocking(); + + QNetworkReply* makeRequest( const std::map& parameters, const QString& server, const QString& token ); + QNetworkReply* makeDownloadRequest( const QString& url, const QString& token, const QString& contentType ); + void requestParquetData( const QString& url, const QString& token ); + + static QString generateRandomString( int length = 20 ); + static QString constructSearchUrl( const QString& server ); + static QString constructDownloadUrl( const QString& server, const QString& blobId ); + static QString constructAuthUrl( const QString& authority ); + static QString constructTokenUrl( const QString& authority ); + +private: + QOAuth2AuthorizationCodeFlow* m_authCodeFlow; + QNetworkAccessManager* m_networkAccessManager; + + const QString m_server; + const QString m_authority; + const QString m_scopes; + const QString m_clientId; + + QString m_token; + + std::vector m_assets; + std::vector m_cases; + std::vector m_vectorNames; + std::vector m_realizationIds; + std::vector m_ensembleNames; + + std::vector m_blobUrl; + + QString m_redirect; + + std::vector m_redirectInfo; + + QByteArray m_parquetData; + + QString m_tokenDataFilePath; +}; diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaSumoDefines.cpp b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoDefines.cpp new file mode 100644 index 0000000000..c9502c7ab3 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoDefines.cpp @@ -0,0 +1,38 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RiaSumoDefines.h" + +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiaSumoDefines::tokenPath() +{ + QString homePath = QDir::homePath(); + return homePath + "/.resinsight/sumo_token.json"; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +int RiaSumoDefines::requestTimeoutMillis() +{ + return 500; +} diff --git a/ApplicationLibCode/Application/Tools/Cloud/RiaSumoDefines.h b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoDefines.h new file mode 100644 index 0000000000..2bb97f0ea4 --- /dev/null +++ b/ApplicationLibCode/Application/Tools/Cloud/RiaSumoDefines.h @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "nonstd/type.hpp" + +#include + +using SumoAssetId = nonstd::ordered; +using SumoCaseId = nonstd::ordered; + +namespace RiaSumoDefines +{ +QString tokenPath(); +int requestTimeoutMillis(); +}; // namespace RiaSumoDefines diff --git a/ApplicationLibCode/Application/Tools/RiaSummaryTools.cpp b/ApplicationLibCode/Application/Tools/RiaSummaryTools.cpp index 5af3086149..9bc190d04d 100644 --- a/ApplicationLibCode/Application/Tools/RiaSummaryTools.cpp +++ b/ApplicationLibCode/Application/Tools/RiaSummaryTools.cpp @@ -393,3 +393,23 @@ void RiaSummaryTools::reloadSummaryCase( RimSummaryCase* summaryCase ) multiPlot->updatePlotTitles(); } } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaSummaryTools::reloadSummaryEnsemble( RimSummaryCaseCollection* ensemble ) +{ + RimSummaryMultiPlotCollection* summaryPlotColl = RiaSummaryTools::summaryMultiPlotCollection(); + for ( RimSummaryMultiPlot* multiPlot : summaryPlotColl->multiPlots() ) + { + for ( RimSummaryPlot* summaryPlot : multiPlot->summaryPlots() ) + { + summaryPlot->loadDataAndUpdate(); + + // Consider to make the zoom optional + summaryPlot->zoomAll(); + } + + multiPlot->updatePlotTitles(); + } +} diff --git a/ApplicationLibCode/Application/Tools/RiaSummaryTools.h b/ApplicationLibCode/Application/Tools/RiaSummaryTools.h index e232162407..1e1f19cb30 100644 --- a/ApplicationLibCode/Application/Tools/RiaSummaryTools.h +++ b/ApplicationLibCode/Application/Tools/RiaSummaryTools.h @@ -86,6 +86,7 @@ public: static void copyCurveAxisData( RimSummaryCurve& curve, const RimSummaryCurve& otherCurve ); static void reloadSummaryCase( RimSummaryCase* summaryCase ); + static void reloadSummaryEnsemble( RimSummaryCaseCollection* ensemble ); private: static void updateRequiredCalculatedCurves( RimSummaryCase* sourceSummaryCase ); diff --git a/ApplicationLibCode/CMakeLists.txt b/ApplicationLibCode/CMakeLists.txt index 2d476be4d7..dfdf6c6127 100644 --- a/ApplicationLibCode/CMakeLists.txt +++ b/ApplicationLibCode/CMakeLists.txt @@ -93,6 +93,7 @@ list( Application/CMakeLists_files.cmake Application/Tools/CMakeLists_files.cmake Application/Tools/WellPathTools/CMakeLists_files.cmake + Application/Tools/Cloud/CMakeLists_files.cmake ReservoirDataModel/CMakeLists_files.cmake ReservoirDataModel/CMakeLists_filesNotToUnitTest.cmake ReservoirDataModel/Completions/CMakeLists_files.cmake @@ -415,6 +416,7 @@ set(UNITY_EXCLUDE_FILES FileInterface/RifOsduWellPathReader.cpp FileInterface/RifOsduWellLogReader.cpp FileInterface/RifByteArrayArrowRandomAccessFile.cpp + FileInterface/RifArrowTools.cpp ) if(RESINSIGHT_ENABLE_UNITY_BUILD) diff --git a/ApplicationLibCode/Commands/ApplicationCommands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/ApplicationCommands/CMakeLists_files.cmake index 6d33bbcc4b..f75d1b73a8 100644 --- a/ApplicationLibCode/Commands/ApplicationCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/ApplicationCommands/CMakeLists_files.cmake @@ -20,6 +20,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RicShowPlotDataCtxFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicOpenInTextEditorFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicShowMemoryReportFeature.h + ${CMAKE_CURRENT_LIST_DIR}/RicSumoDataFeature.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -44,13 +45,16 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicShowPlotDataCtxFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicOpenInTextEditorFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicShowMemoryReportFeature.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicSumoDataFeature.cpp ) list(APPEND COMMAND_CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) list(APPEND COMMAND_CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES}) -set(COMMAND_QT_MOC_HEADERS ${COMMAND_QT_MOC_HEADERS}) +list(APPEND COMMAND_QT_MOC_HEADERS + ${CMAKE_CURRENT_LIST_DIR}/RicSumoDataFeature.h +) source_group( "CommandFeature\\Application" diff --git a/ApplicationLibCode/Commands/ApplicationCommands/RicEditPreferencesFeature.cpp b/ApplicationLibCode/Commands/ApplicationCommands/RicEditPreferencesFeature.cpp index 6a8345f472..678f4e90ba 100644 --- a/ApplicationLibCode/Commands/ApplicationCommands/RicEditPreferencesFeature.cpp +++ b/ApplicationLibCode/Commands/ApplicationCommands/RicEditPreferencesFeature.cpp @@ -64,7 +64,6 @@ void RicEditPreferencesFeature::onActionTriggered( bool isChecked ) std::unique_ptr oldPreferences = clonePreferences( app->preferences() ); RiuPropertyViewTabWidget propertyDialog( nullptr, app->preferences(), "Preferences", tabNames ); - propertyDialog.setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred ); auto pushButton = propertyDialog.dialogButtonBox()->addButton( "Help", QDialogButtonBox::HelpRole ); connect( pushButton, &QPushButton::clicked, this, &RicEditPreferencesFeature::showHelp ); diff --git a/ApplicationLibCode/Commands/ApplicationCommands/RicSumoDataFeature.cpp b/ApplicationLibCode/Commands/ApplicationCommands/RicSumoDataFeature.cpp new file mode 100644 index 0000000000..e1c8e67203 --- /dev/null +++ b/ApplicationLibCode/Commands/ApplicationCommands/RicSumoDataFeature.cpp @@ -0,0 +1,288 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RicSumoDataFeature.h" + +#include "RiaApplication.h" +#include "RiaGuiApplication.h" +#include "RiaLogging.h" +#include "RiaPreferences.h" +#include "RiaPreferencesSystem.h" + +#include "RifArrowTools.h" + +#include "RiuMainWindow.h" + +#include +#include + +CAF_CMD_SOURCE_INIT( RicSumoDataFeature, "RicSumoDataFeature" ); + +SimpleDialog::SimpleDialog( QWidget* parent ) + : QDialog( parent ) +{ + setWindowTitle( "Simple Dialog" ); + + QVBoxLayout* layout = new QVBoxLayout( this ); + + label = new QLabel( "This is a simple dialog.", this ); + layout->addWidget( label ); + + authButton = new QPushButton( "Authenticate", this ); + connect( authButton, &QPushButton::clicked, this, &SimpleDialog::onAuthClicked ); + layout->addWidget( authButton ); + + assetsButton = new QPushButton( "Asset Names", this ); + connect( assetsButton, &QPushButton::clicked, this, &SimpleDialog::onAssetsClicked ); + layout->addWidget( assetsButton ); + + casesButton = new QPushButton( "Cases", this ); + connect( casesButton, &QPushButton::clicked, this, &SimpleDialog::onCasesClicked ); + layout->addWidget( casesButton ); + + vectorNamesButton = new QPushButton( "Vector Names", this ); + connect( vectorNamesButton, &QPushButton::clicked, this, &SimpleDialog::onVectorNamesClicked ); + layout->addWidget( vectorNamesButton ); + + blobIdButton = new QPushButton( "Blob Id", this ); + connect( blobIdButton, &QPushButton::clicked, this, &SimpleDialog::onFindBlobIdClicked ); + layout->addWidget( blobIdButton ); + + parquetDownloadButton = new QPushButton( "Parquet", this ); + connect( parquetDownloadButton, &QPushButton::clicked, this, &SimpleDialog::onParquetClicked ); + layout->addWidget( parquetDownloadButton ); + + showContentParquetButton = new QPushButton( "Show Content Parquet", this ); + connect( showContentParquetButton, &QPushButton::clicked, this, &SimpleDialog::onShowContentParquetClicked ); + layout->addWidget( showContentParquetButton ); + + realizationIdsButton = new QPushButton( "Realizations", this ); + connect( realizationIdsButton, &QPushButton::clicked, this, &SimpleDialog::onRealizationsClicked ); + layout->addWidget( realizationIdsButton ); + + okButton = new QPushButton( "OK", this ); + connect( okButton, &QPushButton::clicked, this, &SimpleDialog::onOkClicked ); + layout->addWidget( okButton ); + + cancelButton = new QPushButton( "Cancel", this ); + connect( cancelButton, &QPushButton::clicked, this, &SimpleDialog::onCancelClicked ); + layout->addWidget( cancelButton ); + + setLayout( layout ); + + m_sumoConnector = RiaApplication::instance()->makeSumoConnector(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +SimpleDialog::~SimpleDialog() +{ + if ( m_sumoConnector ) + { + m_sumoConnector->deleteLater(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::createConnection() +{ + m_sumoConnector = RiaApplication::instance()->makeSumoConnector(); + connect( m_sumoConnector, &RiaSumoConnector::tokenReady, this, &SimpleDialog::onTokenReady ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onAuthClicked() +{ + createConnection(); + m_sumoConnector->requestToken(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onAssetsClicked() +{ + if ( !isTokenValid() ) return; + + m_sumoConnector->requestAssets(); + m_sumoConnector->assets(); + + label->setText( "Requesting fields (see log for response" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onCasesClicked() +{ + if ( !isTokenValid() ) return; + + QString fieldName = "Drogon"; + m_sumoConnector->requestCasesForField( fieldName ); + + label->setText( "Requesting cases (see log for response" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onVectorNamesClicked() +{ + if ( !isTokenValid() ) return; + + SumoCaseId caseId( "5b783aab-ce10-4b78-b129-baf8d8ce4baa" ); + QString iteration = "iter-0"; + + m_sumoConnector->requestVectorNamesForEnsemble( caseId, iteration ); + + label->setText( "Requesting vector names (see log for response" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onFindBlobIdClicked() +{ + if ( !isTokenValid() ) return; + + SumoCaseId caseId( "5b783aab-ce10-4b78-b129-baf8d8ce4baa" ); + QString iteration = "iter-0"; + QString vectorName = "FOPT"; + + m_sumoConnector->requestBlobIdForEnsemble( caseId, iteration, vectorName ); + + label->setText( "Requesting blob ID for vector name (see log for response" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onParquetClicked() +{ + if ( !isTokenValid() ) return; + + if ( m_sumoConnector->blobUrls().empty() ) + { + onFindBlobIdClicked(); + } + + if ( !m_sumoConnector->blobUrls().empty() ) + { + m_sumoConnector->requestBlobDownload( m_sumoConnector->blobUrls().back() ); + + label->setText( "Requesting blob ID for vector name (see log for response" ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onShowContentParquetClicked() +{ + if ( m_sumoConnector->blobContents().empty() ) return; + + auto blob = m_sumoConnector->blobContents().back(); + + auto content = blob.contents; + + // TODO: show content using parquet reader + auto tableText = RifArrowTools::readFirstRowsOfTable( content ); + RiaLogging::info( tableText ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onRealizationsClicked() +{ + if ( !isTokenValid() ) return; + + SumoCaseId caseId( "485041ce-ad72-48a3-ac8c-484c0ed95cf8" ); + QString iteration = "iter-0"; + + m_sumoConnector->requestRealizationIdsForEnsembleBlocking( caseId, iteration ); + + auto ids = m_sumoConnector->realizationIds(); + for ( const auto& id : ids ) + { + RiaLogging::info( id ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool SimpleDialog::isTokenValid() +{ + if ( !m_sumoConnector ) + { + createConnection(); + } + + if ( m_sumoConnector->token().isEmpty() ) + { + m_sumoConnector->requestToken(); + } + + return !m_sumoConnector->token().isEmpty(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void SimpleDialog::onTokenReady( const QString& token ) +{ + RiaLogging::info( "Token ready: " + token ); +} + +void SimpleDialog::onOkClicked() +{ + qDebug( "OK button clicked" ); + accept(); +} + +void SimpleDialog::onCancelClicked() +{ + qDebug( "Cancel button clicked" ); + reject(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicSumoDataFeature::onActionTriggered( bool isChecked ) +{ + if ( !m_dialog ) + { + m_dialog = new SimpleDialog( RiaGuiApplication::instance()->mainWindow() ); + } + m_dialog->show(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicSumoDataFeature::setupActionLook( QAction* actionToSetup ) +{ + actionToSetup->setText( "SUMO" ); +} diff --git a/ApplicationLibCode/Commands/ApplicationCommands/RicSumoDataFeature.h b/ApplicationLibCode/Commands/ApplicationCommands/RicSumoDataFeature.h new file mode 100644 index 0000000000..7dffcd58e5 --- /dev/null +++ b/ApplicationLibCode/Commands/ApplicationCommands/RicSumoDataFeature.h @@ -0,0 +1,85 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2024 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafCmdFeature.h" + +#include "Cloud/RiaSumoConnector.h" + +#include +#include +#include +#include +#include + +class SimpleDialog : public QDialog +{ + Q_OBJECT + +public: + SimpleDialog( QWidget* parent = nullptr ); + ~SimpleDialog(); + + void createConnection(); + +private: + void onOkClicked(); + void onCancelClicked(); + void onTokenReady( const QString& token ); + void onAuthClicked(); + void onAssetsClicked(); + void onCasesClicked(); + void onVectorNamesClicked(); + void onFindBlobIdClicked(); + void onParquetClicked(); + void onShowContentParquetClicked(); + void onRealizationsClicked(); + + bool isTokenValid(); + +private: + QLabel* label; + QPushButton* okButton; + QPushButton* cancelButton; + QPushButton* authButton; + QPushButton* assetsButton; + QPushButton* casesButton; + QPushButton* vectorNamesButton; + QPushButton* blobIdButton; + QPushButton* parquetDownloadButton; + QPushButton* showContentParquetButton; + QPushButton* realizationIdsButton; + + QPointer m_sumoConnector; +}; + +//================================================================================================== +/// +//================================================================================================== +class RicSumoDataFeature : public caf::CmdFeature +{ + CAF_CMD_HEADER_INIT; + +protected: + void onActionTriggered( bool isChecked ) override; + void setupActionLook( QAction* actionToSetup ) override; + +private: + SimpleDialog* m_dialog = nullptr; +}; diff --git a/ApplicationLibCode/UserInterface/RiuMainWindow.cpp b/ApplicationLibCode/UserInterface/RiuMainWindow.cpp index 12ecc081e8..511f776eb9 100644 --- a/ApplicationLibCode/UserInterface/RiuMainWindow.cpp +++ b/ApplicationLibCode/UserInterface/RiuMainWindow.cpp @@ -684,6 +684,7 @@ void RiuMainWindow::createToolBars() { QToolBar* toolbar = addToolBar( tr( "Test" ) ); toolbar->setObjectName( toolbar->windowTitle() ); + toolbar->addAction( cmdFeatureMgr->action( "RicSumoDataFeature" ) ); toolbar->addAction( cmdFeatureMgr->action( "RicLaunchRegressionTestsFeature" ) ); toolbar->addAction( cmdFeatureMgr->action( "RicLaunchRegressionTestDialogFeature" ) ); toolbar->addAction( cmdFeatureMgr->action( "RicShowClassNamesFeature" ) ); diff --git a/ApplicationLibCode/UserInterface/RiuPropertyViewTabWidget.cpp b/ApplicationLibCode/UserInterface/RiuPropertyViewTabWidget.cpp index 122be125d4..16028dc8a1 100644 --- a/ApplicationLibCode/UserInterface/RiuPropertyViewTabWidget.cpp +++ b/ApplicationLibCode/UserInterface/RiuPropertyViewTabWidget.cpp @@ -114,7 +114,7 @@ QSize RiuPropertyViewTabWidget::sizeHint() const // qDebug() << "tab size hint" << w->sizeHint(); QSize pageSize = w->sizeHint(); - pageSize += QSize( 0, 100 ); + pageSize += QSize( 60, 100 ); maxSizeHint = maxSizeHint.expandedTo( pageSize ); }