forked from Silverfish/proton-bridge
feat(GODT-2816): Wait until mandatory fields are filled then fill body and title.
This commit is contained in:
committed by
Romain Le Jeune
parent
3d64c5f894
commit
80d729e3e5
@ -33,7 +33,7 @@ const (
|
||||
DefaultMaxSessionCountForBugReport = 10
|
||||
)
|
||||
|
||||
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, description, username, email, client string, attachLogs bool) error {
|
||||
func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, title, description, username, email, client string, attachLogs bool) error {
|
||||
var account string
|
||||
|
||||
if info, err := bridge.QueryUserInfo(username); err == nil {
|
||||
@ -82,7 +82,7 @@ func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, descript
|
||||
OS: osType,
|
||||
OSVersion: osVersion,
|
||||
|
||||
Title: "[Bridge] Bug",
|
||||
Title: "[Bridge] Bug - " + title,
|
||||
Description: description,
|
||||
|
||||
Client: client,
|
||||
|
||||
@ -221,6 +221,15 @@ bool QMLBackend::areSameFileOrFolder(QUrl const &lhs, QUrl const &rhs) const {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] categoryId The id of the bug category.
|
||||
/// \return Set of question for this category.
|
||||
//****************************************************************************************************************************************************
|
||||
QString QMLBackend::getBugCategory(quint8 categoryId) const {
|
||||
return reportFlow_.getCategory(categoryId);
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] categoryId The id of the bug category.
|
||||
/// \return Set of question for this category.
|
||||
@ -919,14 +928,15 @@ void QMLBackend::triggerReset() const {
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] category The category of the bug.
|
||||
/// \param[in] description The description of the bug.
|
||||
/// \param[in] address The email address.
|
||||
/// \param[in] emailClient The email client.
|
||||
/// \param[in] includeLogs Should the logs be included in the report.
|
||||
//****************************************************************************************************************************************************
|
||||
void QMLBackend::reportBug(QString const &description, QString const &address, QString const &emailClient, bool includeLogs) const {
|
||||
void QMLBackend::reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs) const {
|
||||
HANDLE_EXCEPTION(
|
||||
app().grpc().reportBug(description, address, emailClient, includeLogs);
|
||||
app().grpc().reportBug(category, description, address, emailClient, includeLogs);
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -58,6 +58,7 @@ public: // member functions.
|
||||
Q_INVOKABLE bool isPortFree(int port) const; ///< Check if a given network port is available.
|
||||
Q_INVOKABLE QString nativePath(QUrl const &url) const; ///< Retrieve the native path of a local URL.
|
||||
Q_INVOKABLE bool areSameFileOrFolder(QUrl const &lhs, QUrl const &rhs) const; ///< Check if two local URL point to the same file.
|
||||
Q_INVOKABLE QString getBugCategory(quint8 categoryId) const; ///< Get a Category name.
|
||||
Q_INVOKABLE QVariantList getQuestionSet(quint8 categoryId) const; ///< Retrieve the set of question for a given bug category.
|
||||
Q_INVOKABLE void setQuestionAnswer(quint8 questionId, QString const &answer); ///< Feed an answer for a given question.
|
||||
Q_INVOKABLE QString getQuestionAnswer(quint8 questionId) const; ///< Get the answer for a given question.
|
||||
@ -193,7 +194,7 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge
|
||||
void checkUpdates() const; ///< Slot for the update check.
|
||||
void installUpdate() const; ///< Slot for the update install.
|
||||
void triggerReset() const; ///< Slot for the triggering of reset.
|
||||
void reportBug(QString const &description, QString const &address, QString const &emailClient, bool includeLogs) const; ///< Slot for the bug report.
|
||||
void reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs) const; ///< Slot for the bug report.
|
||||
void exportTLSCertificates() const; ///< Slot for the export of the TLS certificates.
|
||||
void onResetFinished(); ///< Slot for the reset finish signal.
|
||||
void onVersionChanged(); ///< Slot for the version change signal.
|
||||
|
||||
@ -21,7 +21,7 @@ SettingsView {
|
||||
property var questions:Backend.bugQuestions
|
||||
property var categoryId:0
|
||||
property var questionSet:ListModel{}
|
||||
|
||||
property bool error: questionRepeater.error
|
||||
signal questionAnswered
|
||||
|
||||
function setCategoryId(catId) {
|
||||
@ -44,8 +44,38 @@ SettingsView {
|
||||
type: Label.Heading
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
Layout.fillWidth: true
|
||||
color: root.colorScheme.text_weak
|
||||
font.family: ProtonStyle.font_family
|
||||
font.letterSpacing: ProtonStyle.caption_letter_spacing
|
||||
font.pixelSize: ProtonStyle.caption_font_size
|
||||
font.weight: ProtonStyle.fontWeight_400
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectedTextColor: root.colorScheme.text_invert
|
||||
// No way to set lineHeight: ProtonStyle.caption_line_height
|
||||
selectionColor: root.colorScheme.interaction_norm
|
||||
text: qsTr("* Mandatory questions")
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: questionRepeater
|
||||
model: root.questionSet
|
||||
property bool error :{
|
||||
for (var i = 0; i < questionRepeater.count; i++) {
|
||||
if (questionRepeater.itemAt(i).error)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function validate(){
|
||||
for (var i = 0; i < questionRepeater.count; i++) {
|
||||
questionRepeater.itemAt(i).validate()
|
||||
}
|
||||
}
|
||||
|
||||
QuestionItem {
|
||||
Layout.fillWidth: true
|
||||
@ -62,7 +92,7 @@ SettingsView {
|
||||
mandatory: root.questions[modelData].mandatory ? root.questions[modelData].mandatory : false
|
||||
answerList: root.questions[modelData].answerList ? root.questions[modelData].answerList : []
|
||||
|
||||
onAnswerChanged:{
|
||||
onAnswerChanged: {
|
||||
Backend.setQuestionAnswer(modelData, answer);
|
||||
}
|
||||
|
||||
@ -70,7 +100,7 @@ SettingsView {
|
||||
function onVisibleChanged() {
|
||||
setDefaultValue(Backend.getQuestionAnswer(modelData))
|
||||
}
|
||||
target:root
|
||||
target: root
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,10 +111,12 @@ SettingsView {
|
||||
Button {
|
||||
id: continueButton
|
||||
colorScheme: root.colorScheme
|
||||
enabled: !loading
|
||||
enabled: !loading && !root.error
|
||||
text: qsTr("Continue")
|
||||
|
||||
onClicked: {
|
||||
questionRepeater.validate()
|
||||
if (!root.error)
|
||||
submit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ SettingsView {
|
||||
|
||||
property var selectedAddress
|
||||
property var categoryId:-1
|
||||
property string category: Backend.getBugCategory(root.categoryId)
|
||||
|
||||
signal bugReportWasSent
|
||||
|
||||
@ -41,7 +42,7 @@ SettingsView {
|
||||
|
||||
function submit() {
|
||||
sendButton.loading = true;
|
||||
Backend.reportBug(description.text, address.text, emailClient.text, includeLogs.checked);
|
||||
Backend.reportBug(root.category, description.text, address.text, emailClient.text, includeLogs.checked);
|
||||
}
|
||||
|
||||
fillHeight: true
|
||||
@ -69,7 +70,7 @@ SettingsView {
|
||||
// want TextArea implicitHeight (which is height of all text)
|
||||
// to be considered in SettingsView internal scroll view
|
||||
implicitHeight: height
|
||||
label: qsTr("Your answers")
|
||||
label: "Your answers to: " + qsTr(root.category);
|
||||
readOnly : true
|
||||
}
|
||||
TextField {
|
||||
|
||||
@ -236,7 +236,15 @@ FocusScope {
|
||||
KeyNavigation.tab: root.KeyNavigation.tab
|
||||
KeyNavigation.up: root.KeyNavigation.up
|
||||
bottomPadding: 8
|
||||
color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled
|
||||
color: {
|
||||
if (!control.enabled) {
|
||||
return root.colorScheme.text_disabled
|
||||
}
|
||||
if (control.readOnly) {
|
||||
return root.colorScheme.text_hint
|
||||
}
|
||||
return root.colorScheme.text_norm
|
||||
}
|
||||
|
||||
// enforcing default focus here within component
|
||||
focus: root.focus
|
||||
@ -258,7 +266,7 @@ FocusScope {
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
border.color: {
|
||||
if (!control.enabled) {
|
||||
if (!control.enabled || control.readOnly) {
|
||||
return root.colorScheme.field_disabled;
|
||||
}
|
||||
if (control.activeFocus) {
|
||||
|
||||
@ -45,6 +45,15 @@ Item {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
property bool error: {
|
||||
if (root.type === QuestionItem.InputType.TextInput)
|
||||
return textInput.error;
|
||||
if (root.type === QuestionItem.InputType.Radio)
|
||||
return selectionRadio.error;
|
||||
if (root.type === QuestionItem.InputType.Checkbox)
|
||||
return selectionCheckBox.error;
|
||||
return false
|
||||
}
|
||||
|
||||
function setDefaultValue(defaultValue) {
|
||||
textInput.setDefaultValue(defaultValue)
|
||||
@ -52,6 +61,12 @@ Item {
|
||||
selectionCheckBox.setDefaultValue(defaultValue)
|
||||
}
|
||||
|
||||
function validate() {
|
||||
textInput.validate()
|
||||
selectionRadio.validate()
|
||||
selectionCheckBox.validate()
|
||||
}
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
|
||||
ColumnLayout {
|
||||
@ -61,7 +76,7 @@ Item {
|
||||
Label {
|
||||
id: mainLabel
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr(root.text)
|
||||
text: root.mandatory ? qsTr(root.text+" *") : qsTr(root.text)
|
||||
type: Label.Body
|
||||
}
|
||||
ColumnLayout {
|
||||
@ -98,7 +113,7 @@ Item {
|
||||
}
|
||||
onTextChanged: {
|
||||
// Rise max length error immediately while typing if mandatory field
|
||||
if (mandatory && textInput.text.length > textInput._maxLength) {
|
||||
if (textInput.text.length > textInput._maxLength) {
|
||||
validate();
|
||||
}
|
||||
}
|
||||
@ -108,9 +123,11 @@ Item {
|
||||
|
||||
ButtonGroup {
|
||||
id: selectionRadio
|
||||
|
||||
property string text: {
|
||||
return checkedButton ? checkedButton.text : "";
|
||||
}
|
||||
property bool error: root.mandatory
|
||||
|
||||
function setDefaultValue(defaultValue) {
|
||||
const values = root.type === QuestionItem.InputType.Radio ? defaultValue : [];
|
||||
@ -118,6 +135,17 @@ Item {
|
||||
buttons[i].checked = values.includes(buttons[i].text);
|
||||
}
|
||||
}
|
||||
function validate() {
|
||||
if (mandatory && selectionRadio.text.length === 0) {
|
||||
error = true;
|
||||
return
|
||||
}
|
||||
error = false;
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
validate();
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: root.answerList
|
||||
@ -142,6 +170,7 @@ Item {
|
||||
}
|
||||
return str.slice(0, -delimitor.length);
|
||||
}
|
||||
property bool error: root.mandatory
|
||||
|
||||
function setDefaultValue(defaultValue) {
|
||||
const values = root.type === QuestionItem.InputType.Checkbox ? defaultValue.split(delimitor) : [];
|
||||
@ -149,6 +178,18 @@ Item {
|
||||
buttons[i].checked = values.includes(buttons[i].text);
|
||||
}
|
||||
}
|
||||
|
||||
function validate() {
|
||||
if (mandatory && selectionCheckBox.text.length === 0) {
|
||||
error = true;
|
||||
return
|
||||
}
|
||||
error = false;
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
validate();
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
model: root.answerList
|
||||
|
||||
@ -137,13 +137,13 @@ TEST_F(BugReportFlowFixture, validFile) {
|
||||
|
||||
EXPECT_TRUE(flow_.setAnswer(0, "pwet"));
|
||||
EXPECT_FALSE(flow_.setAnswer(1, "pwet"));
|
||||
qDebug() << flow_.collectAnswers(0);
|
||||
EXPECT_EQ(flow_.collectAnswers(0), "Category: I can't receive mail\n\r - What happened?\n\rpwet\n\r");
|
||||
|
||||
EXPECT_EQ(flow_.collectAnswers(0), " - What happened?\n\rpwet\n\r");
|
||||
EXPECT_EQ(flow_.collectAnswers(1), "");
|
||||
EXPECT_EQ(flow_.getAnswer(0), "pwet");
|
||||
EXPECT_EQ(flow_.getAnswer(1), "");
|
||||
flow_.clearAnswers();
|
||||
EXPECT_EQ(flow_.collectAnswers(0), "Category: I can't receive mail\n\r");
|
||||
EXPECT_EQ(flow_.collectAnswers(0), "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -53,17 +53,17 @@ bool BugReportFlow::parse(const QString& filepath) {
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'bugCategories' property.
|
||||
//****************************************************************************************************************************************************
|
||||
QStringList BugReportFlow::categories() const {
|
||||
QStringList BugReportFlow::categories() const {
|
||||
return categories_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \return The value for the 'bugQuestions' property.
|
||||
//****************************************************************************************************************************************************
|
||||
QVariantList BugReportFlow::questions() const {
|
||||
QVariantList BugReportFlow::questions() const {
|
||||
return questions_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
@ -91,6 +91,19 @@ bool BugReportFlow::setAnswer(quint8 questionId, QString const &answer) {
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] questionId The id of the question.
|
||||
/// \return answer the given question.
|
||||
//****************************************************************************************************************************************************
|
||||
QString BugReportFlow::getCategory(quint8 categoryId) const {
|
||||
QString category;
|
||||
if (categoryId <= categories_.count() - 1) {
|
||||
category = categories_[categoryId];
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] questionId The id of the question.
|
||||
/// \return answer the given question.
|
||||
@ -113,7 +126,6 @@ QString BugReportFlow::collectAnswers(quint8 categoryId) const {
|
||||
if (categoryId > categories_.count() - 1)
|
||||
return answers;
|
||||
|
||||
answers += "Category: " + categories_[categoryId] + "\n\r";
|
||||
QVariantList sets = this->questionSet(categoryId);
|
||||
for (QVariant const &var: sets) {
|
||||
const QString& answer = getAnswer(var.toInt());
|
||||
|
||||
@ -38,9 +38,9 @@ public: // member functions.
|
||||
[[nodiscard]] QStringList categories() const; ///< Getter for the 'bugCategories' property.
|
||||
[[nodiscard]] QVariantList questions() const; ///< Getter for the 'bugQuestions' property.
|
||||
[[nodiscard]] QVariantList questionSet(quint8 categoryId) const; ///< Retrieve the set of question for a given bug category.
|
||||
|
||||
[[nodiscard]] bool setAnswer(quint8 questionId, QString const &answer); ///< Feed an answer for a given question.
|
||||
[[nodiscard]] QString getAnswer(quint8 questionId) const; ///< Collect answer for a given questions.
|
||||
[[nodiscard]] QString getCategory(quint8 categoryId) const; ///< Get category name.
|
||||
[[nodiscard]] QString getAnswer(quint8 questionId) const; ///< Get answer for a given question.
|
||||
[[nodiscard]] QString collectAnswers(quint8 categoryId) const; ///< Collect answer for a given set of questions.
|
||||
void clearAnswers(); ///< Clear all collected answers.
|
||||
|
||||
|
||||
@ -353,16 +353,18 @@ grpc::Status GRPCClient::currentEmailClient(QString &outName) {
|
||||
|
||||
|
||||
//****************************************************************************************************************************************************
|
||||
/// \param[in] category The category of the bug.
|
||||
/// \param[in] description The description of the bug.
|
||||
/// \param[in] address The email address.
|
||||
/// \param[in] emailClient The email client.
|
||||
/// \param[in] includeLogs Should the report include the logs.
|
||||
/// \return The status for the gRPC call.
|
||||
//****************************************************************************************************************************************************
|
||||
grpc::Status GRPCClient::reportBug(QString const &description, QString const &address, QString const &emailClient, bool includeLogs) {
|
||||
grpc::Status GRPCClient::reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs) {
|
||||
ReportBugRequest request;
|
||||
request.set_ostype(QSysInfo::productType().toStdString());
|
||||
request.set_osversion(QSysInfo::prettyProductName().toStdString());
|
||||
request.set_title(category.toStdString());
|
||||
request.set_description(description.toStdString());
|
||||
request.set_address(address.toStdString());
|
||||
request.set_emailclient(emailClient.toStdString());
|
||||
|
||||
@ -77,7 +77,7 @@ public: // member functions.
|
||||
grpc::Status colorSchemeName(QString &outName); ///< Performs the "colorSchemeName' gRPC call.
|
||||
grpc::Status setColorSchemeName(QString const &name); ///< Performs the "setColorSchemeName' gRPC call.
|
||||
grpc::Status currentEmailClient(QString &outName); ///< Performs the 'currentEmailClient' gRPC call.
|
||||
grpc::Status reportBug(QString const &description, QString const &address, QString const &emailClient, bool includeLogs); ///< Performs the 'ReportBug' gRPC call.
|
||||
grpc::Status reportBug(QString const &category, QString const &description, QString const &address, QString const &emailClient, bool includeLogs); ///< Performs the 'ReportBug' gRPC call.
|
||||
grpc::Status exportTLSCertificates(QString const &folderPath); ///< Performs the 'ExportTLSCertificates' gRPC call.
|
||||
grpc::Status quit(); ///< Perform the "Quit" gRPC call.
|
||||
grpc::Status restart(); ///< Performs the Restart gRPC call.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -147,10 +147,11 @@ message GuiReadyResponse {
|
||||
message ReportBugRequest {
|
||||
string osType = 1;
|
||||
string osVersion = 2;
|
||||
string description = 3;
|
||||
string address = 4;
|
||||
string emailClient = 5;
|
||||
bool includeLogs = 6;
|
||||
string title = 3;
|
||||
string description = 4;
|
||||
string address = 5;
|
||||
string emailClient = 6;
|
||||
bool includeLogs = 7;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -330,6 +330,7 @@ func (s *Service) ReportBug(_ context.Context, report *ReportBugRequest) (*empty
|
||||
s.log.WithFields(logrus.Fields{
|
||||
"osType": report.OsType,
|
||||
"osVersion": report.OsVersion,
|
||||
"title": report.Title,
|
||||
"description": report.Description,
|
||||
"address": report.Address,
|
||||
"emailClient": report.EmailClient,
|
||||
@ -345,6 +346,7 @@ func (s *Service) ReportBug(_ context.Context, report *ReportBugRequest) (*empty
|
||||
context.Background(),
|
||||
report.OsType,
|
||||
report.OsVersion,
|
||||
report.Title,
|
||||
report.Description,
|
||||
report.Address,
|
||||
report.Address,
|
||||
|
||||
Reference in New Issue
Block a user