Skip to content

Commit 2589946

Browse files
authored
✨ [Frontend] OEC to Support chat (#8288)
1 parent e89806f commit 2589946

File tree

8 files changed

+232
-59
lines changed

8 files changed

+232
-59
lines changed

services/static-webserver/client/source/class/osparc/FlashMessenger.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,12 @@ qx.Class.define("osparc.FlashMessenger", {
8686
console.error(error);
8787
}
8888
const msg = this.extractMessage(error, defaultMessage);
89-
const flashMessage = this.getInstance().logAs(msg, "ERROR", duration);
89+
let flashMessage = null;
9090
if (error && error["supportId"]) {
91-
flashMessage.addWidget(this.__createCopyOECWidget(msg, error["supportId"]));
92-
flashMessage.setDuration(flashMessage.getDuration()*2);
91+
flashMessage = new osparc.ui.message.FlashMessageOEC(msg, duration, error["supportId"]);
92+
this.getInstance().addFlashMessage(flashMessage);
93+
} else {
94+
flashMessage = this.getInstance().logAs(msg, "ERROR", duration);
9395
}
9496
return flashMessage;
9597
},
@@ -142,14 +144,15 @@ qx.Class.define("osparc.FlashMessenger", {
142144

143145
log: function(logMessage) {
144146
const message = this.self().extractMessage(logMessage);
145-
146147
const level = logMessage.level.toUpperCase(); // "DEBUG", "INFO", "WARNING", "ERROR"
147-
148148
const flashMessage = new osparc.ui.message.FlashMessage(message, level, logMessage.duration);
149+
this.addFlashMessage(flashMessage);
150+
return flashMessage;
151+
},
152+
153+
addFlashMessage: function(flashMessage) {
149154
flashMessage.addListener("closeMessage", () => this.removeMessage(flashMessage), this);
150155
this.__messages.push(flashMessage);
151-
152-
return flashMessage;
153156
},
154157

155158
/**

services/static-webserver/client/source/class/osparc/conversation/AddMessage.js

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ qx.Class.define("osparc.conversation.AddMessage", {
6262
let control;
6363
switch (id) {
6464
case "add-comment-layout": {
65-
const grid = new qx.ui.layout.Grid(8, 5);
66-
grid.setColumnWidth(0, 32);
67-
grid.setColumnFlex(1, 1);
68-
control = new qx.ui.container.Composite(grid);
65+
control = new qx.ui.container.Composite(new qx.ui.layout.HBox(0));
6966
this._add(control, {
7067
flex: 1
7168
});
@@ -77,39 +74,56 @@ qx.Class.define("osparc.conversation.AddMessage", {
7774
const myUsername = authData.getUsername();
7875
const myEmail = authData.getEmail();
7976
control.set({
80-
source: osparc.utils.Avatar.emailToThumbnail(myEmail, myUsername, 32)
81-
});
82-
const layout = this.getChildControl("add-comment-layout");
83-
layout.add(control, {
84-
row: 0,
85-
column: 0
77+
source: osparc.utils.Avatar.emailToThumbnail(myEmail, myUsername, 32),
78+
alignX: "center",
79+
alignY: "middle",
80+
marginRight: 8,
8681
});
82+
this.getChildControl("add-comment-layout").add(control);
8783
break;
8884
}
8985
case "comment-field":
9086
control = new osparc.editor.MarkdownEditor();
9187
control.addListener("keydown", e => {
9288
if (e.isCtrlPressed() && e.getKeyIdentifier() === "Enter") {
93-
this.__addComment();
89+
this.addComment();
9490
e.stopPropagation();
9591
e.preventDefault();
9692
}
9793
}, this);
9894
control.getChildControl("buttons").exclude();
99-
const layout = this.getChildControl("add-comment-layout");
100-
layout.add(control, {
101-
row: 0,
102-
column: 1
95+
control.getChildControl("text-area").getContentElement().setStyles({
96+
"border-top-right-radius": "0px", // no roundness there to match the arrow button
97+
});
98+
this.getChildControl("add-comment-layout").add(control, {
99+
flex: 1
103100
});
104101
break;
105102
case "add-comment-button":
106-
control = new qx.ui.form.Button(this.tr("Add message")).set({
107-
appearance: "form-button",
103+
control = new qx.ui.form.Button(null, "@FontAwesome5Solid/arrow-up/16").set({
104+
backgroundColor: "input_background",
108105
allowGrowX: false,
109106
alignX: "right"
110107
});
108+
control.getContentElement().setStyles({
109+
"border-bottom": "1px solid " + qx.theme.manager.Color.getInstance().resolve("default-button-active"),
110+
"border-top-left-radius": "0px", // no roundness there to match the message field
111+
"border-bottom-left-radius": "0px", // no roundness there to match the message field
112+
"border-bottom-right-radius": "0px", // no roundness there to match the message field
113+
});
114+
const commentField = this.getChildControl("comment-field").getChildControl("text-area");
115+
commentField.addListener("focus", () => {
116+
control.getContentElement().setStyles({
117+
"border-bottom": "1px solid " + qx.theme.manager.Color.getInstance().resolve("product-color"),
118+
});
119+
}, this);
120+
commentField.addListener("focusout", () => {
121+
control.getContentElement().setStyles({
122+
"border-bottom": "1px solid " + qx.theme.manager.Color.getInstance().resolve("default-button-active"),
123+
});
124+
}, this);
111125
control.addListener("execute", this.__addCommentPressed, this);
112-
this._add(control);
126+
this.getChildControl("add-comment-layout").add(control);
113127
break;
114128
case "notify-user-button":
115129
control = new qx.ui.form.Button("🔔 " + this.tr("Notify user")).set({
@@ -155,10 +169,10 @@ qx.Class.define("osparc.conversation.AddMessage", {
155169
},
156170

157171
__addCommentPressed: function() {
158-
this.getMessage() ? this.__editComment() : this.__addComment();
172+
this.getMessage() ? this.__editComment() : this.addComment();
159173
},
160174

161-
__addComment: function() {
175+
addComment: function() {
162176
const conversationId = this.getConversationId();
163177
if (conversationId) {
164178
this.__postMessage();

services/static-webserver/client/source/class/osparc/desktop/organizations/ServicesList.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ qx.Class.define("osparc.desktop.organizations.ServicesList", {
122122
const orgServices = [];
123123
Object.keys(servicesLatest).forEach(key => {
124124
const serviceLatest = servicesLatest[key];
125-
if (groupId in serviceLatest["accessRights"]) {
125+
if (serviceLatest["accessRights"] && groupId in serviceLatest["accessRights"]) {
126126
orgServices.push(serviceLatest);
127127
}
128128
});

services/static-webserver/client/source/class/osparc/support/ConversationPage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ qx.Class.define("osparc.support.ConversationPage", {
151151
if (conversation && amISupporter) {
152152
const extraContext = conversation.getExtraContext();
153153
if (extraContext && Object.keys(extraContext).length) {
154-
let extraContextText = `Support ID: ${conversation.getConversationId()}`;
154+
let extraContextText = `Ticket ID: ${conversation.getConversationId()}`;
155155
const contextProjectId = conversation.getContextProjectId();
156156
if (contextProjectId) {
157157
extraContextText += `<br>Project ID: ${contextProjectId}`;

services/static-webserver/client/source/class/osparc/support/SupportCenter.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ qx.Class.define("osparc.support.SupportCenter", {
3737

3838
this.getChildControl("conversations-intro-text");
3939
this.getChildControl("conversations-list");
40-
if (!osparc.store.Products.getInstance().amIASupportUser()) {
41-
this.getChildControl("ask-a-question-button");
42-
}
40+
this.getChildControl("ask-a-question-button");
4341
},
4442

4543
statics: {

services/static-webserver/client/source/class/osparc/ui/markdown/Markdown.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ qx.Class.define("osparc.ui.markdown.Markdown", {
100100
}
101101
};
102102
marked.use({ renderer });
103+
// By default, Markdown requires two spaces at the end of a line or a blank line between paragraphs to produce a line break.
104+
// With this, a single line break (Enter) in your Markdown input will render as a <br> in HTML.
105+
marked.setOptions({ breaks: true }); //
103106

104107
const html = marked.parse(value);
105108

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2025 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
qx.Class.define("osparc.ui.message.FlashMessageOEC", {
19+
extend: osparc.ui.message.FlashMessage,
20+
21+
/**
22+
* Constructor for the FlashMessage.
23+
*
24+
* @param {String} message Message that the user will read.
25+
* @param {Number} duration
26+
* @param {String} supportId
27+
*/
28+
construct: function(message, duration, supportId) {
29+
this.base(arguments, message, "ERROR", duration ? duration*2 : null);
30+
31+
if (osparc.product.Utils.isSupportEnabled()) {
32+
this.getChildControl("contact-support");
33+
} else {
34+
const oecAtom = this.getChildControl("oec-atom");
35+
this.bind("supportId", oecAtom, "label");
36+
}
37+
if (supportId) {
38+
this.setSupportId(supportId);
39+
}
40+
},
41+
42+
properties: {
43+
supportId: {
44+
check: "String",
45+
init: "",
46+
nullable: true,
47+
event: "changeSupportId",
48+
},
49+
},
50+
51+
members: {
52+
_createChildControlImpl: function(id) {
53+
let control;
54+
switch (id) {
55+
case "oec-atom":
56+
control = new qx.ui.basic.Atom().set({
57+
icon: "@FontAwesome5Solid/copy/10",
58+
iconPosition: "right",
59+
gap: 8,
60+
cursor: "pointer",
61+
alignX: "center",
62+
allowGrowX: false,
63+
});
64+
control.addListener("tap", () => this.__copyToClipboard());
65+
this.addWidget(control);
66+
break;
67+
case "contact-support":
68+
control = new qx.ui.basic.Atom().set({
69+
label: this.tr("Contact Support"),
70+
icon: "@FontAwesome5Solid/comments/10",
71+
iconPosition: "left",
72+
gap: 8,
73+
cursor: "pointer",
74+
alignX: "center",
75+
allowGrowX: false,
76+
});
77+
control.addListener("tap", () => this.__openSupportChat());
78+
this.addWidget(control);
79+
break;
80+
}
81+
return control || this.base(arguments, id);
82+
},
83+
84+
__getContext: function() {
85+
const dataToClipboard = {
86+
message: this.getMessage(),
87+
supportId: this.getSupportId(),
88+
timestamp: new Date().toString(),
89+
url: window.location.href,
90+
releaseTag: osparc.utils.Utils.getReleaseTag(),
91+
}
92+
if (osparc.store.Store.getInstance().getCurrentStudy()) {
93+
dataToClipboard["projectId"] = osparc.store.Store.getInstance().getCurrentStudy().getUuid();
94+
}
95+
return osparc.utils.Utils.prettifyJson(dataToClipboard);
96+
},
97+
98+
__getSupportFriendlyContext: function() {
99+
let curatedText = "Extra Context:";
100+
curatedText += "\nError: " + this.getMessage();
101+
curatedText += "\nSupportID: " + this.getSupportId();
102+
curatedText += "\nTimestamp: " + new Date().toISOString();
103+
curatedText += "\nURL: " + window.location.href;
104+
curatedText += "\nRelease Tag: " + osparc.utils.Utils.getReleaseTag();
105+
if (osparc.store.Store.getInstance().getCurrentStudy()) {
106+
curatedText += "\nProject ID: " + osparc.store.Store.getInstance().getCurrentStudy().getUuid();
107+
}
108+
return curatedText;
109+
},
110+
111+
__copyToClipboard: function() {
112+
osparc.utils.Utils.copyTextToClipboard(this.__getContext());
113+
},
114+
115+
__openSupportChat: function() {
116+
const supportCenter = osparc.support.SupportCenter.openWindow();
117+
supportCenter.openConversation(null);
118+
119+
const textToAddMessageField = msg => {
120+
if (
121+
supportCenter.getChildControl("conversation-page") &&
122+
supportCenter.getChildControl("conversation-page").getChildControl("conversation-content") &&
123+
supportCenter.getChildControl("conversation-page").getChildControl("conversation-content").getChildControl("add-message") &&
124+
supportCenter.getChildControl("conversation-page").getChildControl("conversation-content").getChildControl("add-message").getChildControl("comment-field")
125+
) {
126+
supportCenter.getChildControl("conversation-page").getChildControl("conversation-content").getChildControl("add-message").getChildControl("comment-field").setText(msg);
127+
supportCenter.getChildControl("conversation-page").getChildControl("conversation-content").getChildControl("add-message").addComment();
128+
}
129+
}
130+
131+
const caption = this.tr("Something went wrong");
132+
const introText = this.tr("Please describe what you were doing before the error (optional).\nThis will help our support team understand the context and resolve the issue faster.");
133+
const confirmationWindow = new osparc.ui.window.Confirmation(introText);
134+
confirmationWindow.setCaption(caption);
135+
confirmationWindow.getChildControl("message-label").setFont("text-13");
136+
const extraContextTA = new qx.ui.form.TextArea().set({
137+
font: "text-13",
138+
autoSize: true,
139+
minHeight: 70,
140+
maxHeight: 140
141+
});
142+
confirmationWindow.addWidget(extraContextTA);
143+
confirmationWindow.addCancelButton();
144+
confirmationWindow.setConfirmText(this.tr("Send Report"));
145+
confirmationWindow.open();
146+
confirmationWindow.addListener("close", () => {
147+
if (confirmationWindow.getConfirmed()) {
148+
const extraContext = extraContextTA.getValue()
149+
const friendlyContext = this.__getSupportFriendlyContext();
150+
const text = "Dear Support Team,\n" + extraContext + "\n" + friendlyContext;
151+
textToAddMessageField(text);
152+
} else {
153+
supportCenter.close();
154+
}
155+
});
156+
},
157+
}
158+
});

0 commit comments

Comments
 (0)