diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index 35c47953..86c6e07a 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -37,6 +37,15 @@ using namespace bridgepp; +namespace { + + + QString const bugReportFile = ":qml/Resources/bug_report_flow.json"; + + +} + + //**************************************************************************************************************************************************** // //**************************************************************************************************************************************************** @@ -89,7 +98,8 @@ void QMLBackend::init(GRPCConfig const &serviceConfig) { this->setUseSSLForIMAP(sslForIMAP); this->setUseSSLForSMTP(sslForSMTP); this->retrieveUserList(); - this->retrieveBugReportFlow(); + if (!reportFlow_.parse(bugReportFile)) + app().log().error(QString("Cannot parse BugReportFlow description file: %1").arg(bugReportFile)); } @@ -216,7 +226,10 @@ bool QMLBackend::areSameFileOrFolder(QUrl const &lhs, QUrl const &rhs) const { /// \return Set of question for this category. //**************************************************************************************************************************************************** QVariantList QMLBackend::getQuestionSet(quint8 categoryId) const { - return questionsSet_[categoryId]; + QVariantList list = reportFlow_.questionSet(categoryId); + if (list.count() == 0) + app().log().error(QString("Bug category not found (id: %1)").arg(categoryId)); + return list; }; @@ -225,7 +238,8 @@ QVariantList QMLBackend::getQuestionSet(quint8 categoryId) const { /// \param[in] answer The answer to that question. //**************************************************************************************************************************************************** void QMLBackend::setQuestionAnswer(quint8 questionId, QString const &answer) { - this->answers_[questionId] = answer; + if (!reportFlow_.setAnswer(questionId, answer)) + app().log().error(QString("Bug Report Question not found (id: %1)").arg(questionId)); } @@ -234,16 +248,7 @@ void QMLBackend::setQuestionAnswer(quint8 questionId, QString const &answer) { /// \return concatenate answers for set of questions. //**************************************************************************************************************************************************** QString QMLBackend::collectAnswer(quint8 categoryId) const { - QString answers; - QVariantList sets = this->getQuestionSet(categoryId); - foreach(const QVariant& var, sets) { - answers += " - "; - answers += questions_[var.toInt()].toMap()["text"].toString(); - answers += " "; - answers += answers_[var.toInt()]; - answers += "\n\r"; - } - return answers; + return reportFlow_.collectAnswers(categoryId); } @@ -622,14 +627,14 @@ QStringList QMLBackend::availableKeychain() const { /// \return The value for the 'bugCategories' property. //**************************************************************************************************************************************************** QStringList QMLBackend::bugCategories() const { - return categories_; + return reportFlow_.categories(); } //**************************************************************************************************************************************************** /// \return The value for the 'bugQuestions' property. //**************************************************************************************************************************************************** QVariantList QMLBackend::bugQuestions() const { - return questions_; + return reportFlow_.questions(); } @@ -1221,31 +1226,6 @@ void QMLBackend::retrieveUserList() { } -//**************************************************************************************************************************************************** -// -//**************************************************************************************************************************************************** -void QMLBackend::retrieveBugReportFlow() { - categories_.clear(); - questions_.clear(); - questionsSet_.clear(); - QString val; - QFile file; - file.setFileName(":qml/Resources/bug_report_flow.json"); - file.open(QIODevice::ReadOnly | QIODevice::Text); - val = file.readAll(); - file.close(); - QJsonDocument d = QJsonDocument::fromJson(val.toUtf8()); - QJsonObject root = d.object(); - QJsonObject data = root.value(QString("data_v1.0.0")).toObject(); - QJsonArray categoriesJson = data.value(QString("categories")).toArray(); - foreach (const QJsonValue & v, categoriesJson) { - categories_.append(v.toObject()["name"].toString()); - questionsSet_.append(v.toObject()["questions"].toArray().toVariantList()); - } - questions_ = data.value(QString("questions")).toArray().toVariantList(); -} - - //**************************************************************************************************************************************************** // //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index 119a30bc..6714863c 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -24,6 +24,7 @@ #include "BuildConfig.h" #include "TrayIcon.h" #include "UserList.h" +#include #include #include #include @@ -278,7 +279,6 @@ private: // member functions void retrieveUserList(); ///< Retrieve the list of users via gRPC. void connectGrpcEvents(); ///< Connect gRPC that need to be forwarded to QML via backend signals void displayBadEventDialog(QString const& userID); ///< Displays the bad event dialog for a user. - void retrieveBugReportFlow(); ///< Get the bug report flow description file and parse it. private: // data members UserList *users_ { nullptr }; ///< The user list. Owned by backend. @@ -294,10 +294,7 @@ private: // data members bool isInternetOn_ { true }; ///< Does bridge consider internet as on? QList badEventDisplayQueue_; ///< THe queue for displaying 'bad event feedback request dialog'. std::unique_ptr trayIcon_; ///< The tray icon for the application. - QStringList categories_; ///< The list of Bug Category parsed from the description file. - QVariantList questions_; ///< The list of Questions parsed from the description file. - QList questionsSet_; ///< Sets of questions per bug category. - QMap answers_; ///< Map of QuestionId/Answer for the bug form. + BugReportFlow reportFlow_; ///< The bug report flow. friend class AppController; }; diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/BugQuestionView.qml b/internal/frontend/bridge-gui/bridge-gui/qml/BugQuestionView.qml index cc76bd75..1c8c4895 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/BugQuestionView.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/BugQuestionView.qml @@ -48,7 +48,7 @@ SettingsView { Label { Layout.fillWidth: true colorScheme: root.colorScheme - text: qsTr("Give us more details") + text: qsTr("Describe the issue") type: Label.Heading } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json index 7d9820eb..b42694a8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Resources/bug_report_flow.json @@ -33,13 +33,13 @@ "questions": [ { "id": 0, - "text": "What did you expect to happen?", + "text": "What happened?", "tips": "Expected behavior", "type": 1 }, { "id": 1, - "text": "What happened instead?", + "text": "What did you want or expect to happen?", "tips": "Result", "type": 1 }, @@ -51,15 +51,15 @@ }, { "id": 3, - "text": "Can you reproduce your issue?", + "text": "Can you reproduce this issue? (If you repeat the actions, the same thing happens.)", "type": 2, - "answerList": ["yes", "no", "I don't know"] + "answerList": ["Yes", "No", "I don't know"] }, { "id": 4, - "text": "Do you have such software running?", + "text": "Are you running any of these software?", "type": 3, - "answerList": ["VPN", "anti-virus", "firewall", "cache cleaner", "None of this"] + "answerList": ["VPN", "Antivirus", "Firewall", "Cache cleaner", "None of the above"] } ] } diff --git a/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt b/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt index 87530c4c..95228d6a 100644 --- a/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt +++ b/internal/frontend/bridge-gui/bridgepp/CMakeLists.txt @@ -135,6 +135,7 @@ add_custom_command( add_library(bridgepp + bridgepp/BugReportFlow/BugReportFlow.cpp bridgepp/BugReportFlow/BugReportFlow.h bridgepp/BridgeUtils.cpp bridgepp/BridgeUtils.h bridgepp/CLI/CLIUtils.cpp bridgepp/CLI/CLIUtils.h bridgepp/Exception/Exception.h bridgepp/Exception/Exception.cpp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/BugReportFlow/BugReportFlow.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/BugReportFlow/BugReportFlow.cpp new file mode 100644 index 00000000..e0099d6b --- /dev/null +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/BugReportFlow/BugReportFlow.cpp @@ -0,0 +1,172 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge 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. +// +// Proton Mail Bridge 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 for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + +#include "BugReportFlow.h" + + +namespace { + + + QString const currentFormatVersion = "1.0.0"; + + +} + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +BugReportFlow::BugReportFlow() { +} + + +//**************************************************************************************************************************************************** +/// \param[in] filepath The path of the file to parse. +/// \return True iff the file can be properly parsed. +//**************************************************************************************************************************************************** +bool BugReportFlow::parse(const QString& filepath) { + if (!QFile(filepath).exists()) + return false; + + this->filepath_ = filepath; + return parseFile(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] categoryId The id of the bug category. +/// \return Set of question for this category. +//**************************************************************************************************************************************************** +QVariantList BugReportFlow::questionSet(quint8 categoryId) const { + if (categoryId >= questionsSet_.count() - 1) + return QVariantList(); + return questionsSet_[categoryId]; +}; + + +//**************************************************************************************************************************************************** +/// \param[in] questionId The id of the question. +/// \param[in] answer The answer to that question. +/// \return true iff questionId match an existing question. +//**************************************************************************************************************************************************** +bool BugReportFlow::setAnswer(quint8 questionId, QString const &answer) { + if (questionId >= questions_.count() - 1) + return false; + + this->answers_[questionId] = answer; + return true; +} + + +//**************************************************************************************************************************************************** +/// \param[in] categoryId The id of the question set. +/// \return concatenate answers for set of questions. +//**************************************************************************************************************************************************** +QString BugReportFlow::collectAnswers(quint8 categoryId) const { + QString answers; + + QVariantList sets = this->questionSet(categoryId); + foreach(const QVariant& var, sets) { + answers += " - "; + answers += questions_[var.toInt()].toMap()["text"].toString(); + answers += " "; + answers += answers_[var.toInt()]; + answers += "\n\r"; + } + return answers; +} + + +//**************************************************************************************************************************************************** +/// \return The value for the 'bugCategories' property. +//**************************************************************************************************************************************************** +QStringList BugReportFlow::categories() const { + return categories_; +} + + +//**************************************************************************************************************************************************** +/// \return The value for the 'bugQuestions' property. +//**************************************************************************************************************************************************** +QVariantList BugReportFlow::questions() const { + return questions_; +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +bool BugReportFlow::parseFile() { + categories_.clear(); + questions_.clear(); + questionsSet_.clear(); + + QJsonObject data = getJsonDataObj(getJsonRootObj()); + + QJsonArray categoriesJson = data.value("categories").toArray(); + foreach (const QJsonValue & v, categoriesJson) { + categories_.append(v.toObject()["name"].toString()); + questionsSet_.append(v.toObject()["questions"].toArray().toVariantList()); + } + questions_ = data.value("questions").toArray().toVariantList(); + return true; +} + +QJsonObject BugReportFlow::getJsonRootObj() { + QFile file(filepath_); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return QJsonObject(); + + const QString& val = file.readAll(); + file.close(); + QJsonDocument d = QJsonDocument::fromJson(val.toUtf8()); + return d.object(); +} + + +QJsonObject BugReportFlow::getJsonDataObj(const QJsonObject& root) { + QString version = getJsonVersion(root); + if (version.isEmpty()) + return QJsonObject(); + + QJsonValue data = root.value(QString("data_v%1").arg(version)); + if (data == QJsonValue::Undefined || !data.isObject()) + return QJsonObject(); + QJsonObject dataObj = data.toObject(); + + return migrateData(dataObj, version); +} + + +QString BugReportFlow::getJsonVersion(const QJsonObject& root) { + QJsonValue metadata = root.value("metadata"); + if (metadata == QJsonValue::Undefined || !metadata.isObject()) { + return QString(); + } + QJsonValue version = metadata.toObject().value("version"); + if (version == QJsonValue::Undefined || !version.isString()) { + return QString(); + } + return version.toString(); +} + + +QJsonObject BugReportFlow::migrateData(const QJsonObject& data, const QString& version) { + if (version != currentFormatVersion) + return QJsonObject(); + // nothing to migrate now but migration should be done here. + return data; +} \ No newline at end of file diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/BugReportFlow/BugReportFlow.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/BugReportFlow/BugReportFlow.h new file mode 100644 index 00000000..88b8c17c --- /dev/null +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/BugReportFlow/BugReportFlow.h @@ -0,0 +1,57 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge 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. +// +// Proton Mail Bridge 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 for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + + +#ifndef BRIDGE_GUI_BUG_REPORT_FLOW_H +#define BRIDGE_GUI_BUG_REPORT_FLOW_H + +//**************************************************************************************************************************************************** +/// \brief Bug Report Flow parser. +//**************************************************************************************************************************************************** +class BugReportFlow { + +public: // member functions. + BugReportFlow(); ///< Default constructor. + BugReportFlow(BugReportFlow const &) = delete; ///< Disabled copy-constructor. + BugReportFlow(BugReportFlow &&) = delete; ///< Disabled assignment copy-constructor. + ~BugReportFlow() = default; ///< Destructor. + + bool parse(const QString& filepath); ///< Initialize the Bug Report Flow. + + QVariantList questionSet(quint8 categoryId) const; ///< Retrieve the set of question for a given bug category. + bool setAnswer(quint8 questionId, QString const &answer); ///< Feed an answer for a given question. + QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions. + QStringList categories() const; ///< Getter for the 'bugCategories' property. + QVariantList questions() const; ///< Getter for the 'bugQuestions' property. + +private: // member functions + bool parseFile(); ///< Parse the bug report flow description file. + QJsonObject getJsonRootObj(); ///< Extract the JSON root object. + QJsonObject getJsonDataObj(const QJsonObject& root); ///< Extract the JSON data object. + QString getJsonVersion(const QJsonObject& root); ///< Extract the JSON version of the file. + QJsonObject migrateData(const QJsonObject& data, const QString& version); ///< Migrate data if needed/possible. + +private: // data members + QString filepath_; ///< The file path of the BugReportFlow description file. + QStringList categories_; ///< The list of Bug Category parsed from the description file. + QVariantList questions_; ///< The list of Questions parsed from the description file. + QList questionsSet_; ///< Sets of questions per bug category. + QMap answers_; ///< Map of QuestionId/Answer for the bug form. +}; + + +#endif // BRIDGE_GUI_BUG_REPORT_FLOW_H