diff --git a/services/static-webserver/client/source/class/osparc/data/model/Conversation.js b/services/static-webserver/client/source/class/osparc/data/model/Conversation.js index 6f59640c9e1..f09ae798c51 100644 --- a/services/static-webserver/client/source/class/osparc/data/model/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/data/model/Conversation.js @@ -45,7 +45,7 @@ qx.Class.define("osparc.data.model.Conversation", { this.__listenToConversationMessageWS(); if (conversationData.type === "SUPPORT") { - this.__fetchLastMessage(); + this.__fetchFirstAndLastMessages(); } }, @@ -132,6 +132,13 @@ qx.Class.define("osparc.data.model.Conversation", { event: "changeNameAlias", }, + firstMessage: { + check: "Object", + nullable: true, + init: null, + event: "changeFirstMessage", + }, + lastMessage: { check: "Object", nullable: true, @@ -154,7 +161,7 @@ qx.Class.define("osparc.data.model.Conversation", { }, members: { - __fetchLastMessagePromise: null, + __fetchingFirstAndLastMessage: null, __nextRequestParams: null, __messages: null, @@ -200,24 +207,34 @@ qx.Class.define("osparc.data.model.Conversation", { }); }, - __fetchLastMessage: function() { - if (this.__fetchLastMessagePromise) { - return this.__fetchLastMessagePromise; + __fetchFirstAndLastMessages: function() { + if (this.__fetchingFirstAndLastMessage) { + return this.__fetchingFirstAndLastMessage; } - let promise = osparc.store.ConversationsSupport.getInstance().fetchLastMessage(this.getConversationId()); - promise - .then(lastMessage => { - this.addMessage(lastMessage); - promise = null; - return lastMessage; + this.__fetchingFirstAndLastMessage = true; + osparc.store.ConversationsSupport.getInstance().fetchLastMessage(this.getConversationId()) + .then(resp => { + const messages = resp["data"]; + if (messages.length) { + this.addMessage(messages[0]); + this.setLastMessage(messages[0]); + } + // fetch first message only if there is more than one message + if (resp["_meta"]["total"] === 1) { + this.setFirstMessage(messages[0]); + } else if (resp["_meta"]["total"] > 1) { + osparc.store.ConversationsSupport.getInstance().fetchFirstMessage(this.getConversationId(), resp["_meta"]) + .then(firstMessages => { + if (firstMessages.length) { + this.setFirstMessage(firstMessages[0]); + } + }); + } + return null; }) - .finally(() => { - this.__fetchLastMessagePromise = null; - }); - - this.__fetchLastMessagePromise = promise; - return promise; + .catch(err => osparc.FlashMessenger.logError(err)) + .finally(() => this.__fetchingFirstAndLastMessage = null); }, amIOwner: function() { diff --git a/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js b/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js index 773af71ce60..2aab56d6e68 100644 --- a/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js +++ b/services/static-webserver/client/source/class/osparc/desktop/StudyEditor.js @@ -288,7 +288,7 @@ qx.Class.define("osparc.desktop.StudyEditor", { }, this); study.listenToChanges(); // this includes the listener on the workbench and ui - study.addListener("projectDocumentChanged", e => this.projectDocumentChanged(e.getData()), this); + study.addListener("projectDocumentChanged", e => this.__projectDocumentChanged(e.getData()), this); if (osparc.utils.DisabledPlugins.isRTCEnabled()) { this.__listenToProjectDocument(); @@ -998,7 +998,7 @@ qx.Class.define("osparc.desktop.StudyEditor", { /** * @param {JSON Patch} data It will soon be used to patch the project document https://datatracker.ietf.org/doc/html/rfc6902 */ - projectDocumentChanged: function(patchData) { + __projectDocumentChanged: function(patchData) { patchData["userGroupId"] = osparc.auth.Data.getInstance().getGroupId(); // avoid echo loop if (this.__blockUpdates) { diff --git a/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js b/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js index 53a2150bbbb..89f849bf68b 100644 --- a/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js +++ b/services/static-webserver/client/source/class/osparc/store/ConversationsSupport.js @@ -140,13 +140,6 @@ qx.Class.define("osparc.store.ConversationsSupport", { }, fetchLastMessage: function(conversationId) { - if ( - conversationId in this.__conversationsCached && - this.__conversationsCached[conversationId].getLastMessage() - ) { - return Promise.resolve(this.__conversationsCached[conversationId].getLastMessage()); - } - const params = { url: { conversationId, @@ -154,15 +147,21 @@ qx.Class.define("osparc.store.ConversationsSupport", { limit: 1, } }; - return osparc.data.Resources.fetch("conversationsSupport", "getMessagesPage", params) - .then(messagesData => { - if (messagesData && messagesData.length) { - const lastMessage = messagesData[0]; - this.__addMessageToConversation(conversationId, lastMessage); - return lastMessage; - } - return null; - }); + const options = { + resolveWResponse: true + }; + return osparc.data.Resources.fetch("conversationsSupport", "getMessagesPage", params, options); + }, + + fetchFirstMessage: function(conversationId, conversationPaginationMetadata) { + const params = { + url: { + conversationId, + offset: Math.max(0, conversationPaginationMetadata["total"] - 1), + limit: 1, + } + }; + return osparc.data.Resources.fetch("conversationsSupport", "getMessagesPage", params); }, postMessage: function(conversationId, message) { diff --git a/services/static-webserver/client/source/class/osparc/support/Conversation.js b/services/static-webserver/client/source/class/osparc/support/Conversation.js index 6bb15a6384e..f8c89118505 100644 --- a/services/static-webserver/client/source/class/osparc/support/Conversation.js +++ b/services/static-webserver/client/source/class/osparc/support/Conversation.js @@ -155,7 +155,9 @@ qx.Class.define("osparc.support.Conversation", { if (showLayout) { this.__populateShareProjectCB(); const currentStudy = osparc.store.Store.getInstance().getCurrentStudy(); - currentStudy.addListener("changeAccessRights", () => this.__populateShareProjectCB(), this); + if (currentStudy) { + currentStudy.addListener("changeAccessRights", () => this.__populateShareProjectCB(), this); + } } }, diff --git a/services/static-webserver/client/source/class/osparc/support/ConversationListItem.js b/services/static-webserver/client/source/class/osparc/support/ConversationListItem.js index ab88649bfad..d605405d07b 100644 --- a/services/static-webserver/client/source/class/osparc/support/ConversationListItem.js +++ b/services/static-webserver/client/source/class/osparc/support/ConversationListItem.js @@ -27,7 +27,13 @@ qx.Class.define("osparc.support.ConversationListItem", { // decorate this.getChildControl("thumbnail").setDecorator("circled"); + this.getChildControl("title").set({ + rich: false, // let ellipsis work + }); this.getChildControl("subtitle").set({ + rich: false, // let ellipsis work + }); + this.getChildControl("sub-subtitle").set({ textColor: "text-disabled", }); }, @@ -48,14 +54,18 @@ qx.Class.define("osparc.support.ConversationListItem", { this.__populateWithLastMessage(); conversation.addListener("changeLastMessage", this.__populateWithLastMessage, this); + + this.__populateWithFirstMessage(); + conversation.addListener("changeFirstMessage", this.__populateWithFirstMessage, this); }, __populateWithLastMessage: function() { - const lastMessage = this.getConversation().getLastMessage(); + const conversation = this.getConversation(); + const lastMessage = conversation.getLastMessage(); if (lastMessage) { const date = osparc.utils.Utils.formatDateAndTime(new Date(lastMessage.created)); this.set({ - subtitle: date, + role: date, }); const userGroupId = lastMessage.userGroupId; osparc.store.Users.getInstance().getUser(userGroupId) @@ -63,11 +73,34 @@ qx.Class.define("osparc.support.ConversationListItem", { if (user) { this.set({ thumbnail: user.getThumbnail(), - subtitle: user.getLabel() + " - " + date, + subtitle: user.getLabel() + ": " + lastMessage["content"], + }); + } + }); + } + }, + + __populateWithFirstMessage: function() { + const conversation = this.getConversation(); + const firstMessage = conversation.getFirstMessage(); + if (firstMessage) { + const userGroupId = firstMessage.userGroupId; + osparc.store.Users.getInstance().getUser(userGroupId) + .then(user => { + if (user) { + const amISupporter = osparc.store.Groups.getInstance().amIASupportUser(); + let subSubtitle = "Started"; + if (amISupporter) { + subSubtitle += " by " + user.getLabel(); + } + const date = osparc.utils.Utils.formatDateAndTime(new Date(firstMessage.created)); + subSubtitle += " on " + date; + this.set({ + subSubtitle, }); } }); } }, - } + }, }); diff --git a/services/static-webserver/client/source/class/osparc/support/ConversationPage.js b/services/static-webserver/client/source/class/osparc/support/ConversationPage.js index fd6381b3ba0..6d190dfa9cb 100644 --- a/services/static-webserver/client/source/class/osparc/support/ConversationPage.js +++ b/services/static-webserver/client/source/class/osparc/support/ConversationPage.js @@ -249,6 +249,11 @@ qx.Class.define("osparc.support.ConversationPage", { if (extraContext && Object.keys(extraContext).length) { const ticketIdLabel = createExtraContextLabel(`Ticket ID: ${osparc.utils.Utils.uuidToShort(conversation.getConversationId())}`); extraContextLayout.add(ticketIdLabel); + const contextProjectId = conversation.getContextProjectId(); + if (contextProjectId) { + const projectIdLabel = createExtraContextLabel(`Project ID: ${osparc.utils.Utils.uuidToShort(contextProjectId)}`); + extraContextLayout.add(projectIdLabel); + } if (amISupporter) { const fogbugzLink = conversation.getFogbugzLink(); if (fogbugzLink) { @@ -260,12 +265,6 @@ qx.Class.define("osparc.support.ConversationPage", { }); extraContextLayout.add(fogbugzLabel); } - const contextProjectId = conversation.getContextProjectId(); - if (contextProjectId) { - const projectIdLabel = createExtraContextLabel(`Project ID: ${osparc.utils.Utils.uuidToShort(contextProjectId)}`); - extraContextLayout.add(projectIdLabel); - } - } } }; diff --git a/services/static-webserver/client/source/class/osparc/support/SupportCenter.js b/services/static-webserver/client/source/class/osparc/support/SupportCenter.js index c13997267df..76bde6c257e 100644 --- a/services/static-webserver/client/source/class/osparc/support/SupportCenter.js +++ b/services/static-webserver/client/source/class/osparc/support/SupportCenter.js @@ -51,7 +51,7 @@ qx.Class.define("osparc.support.SupportCenter", { }, statics: { - WINDOW_WIDTH: 430, + WINDOW_WIDTH: 450, WINDOW_HEIGHT: 700, REQUEST_CALL_MESSAGE: "Dear Support,\nI would like to make an appointment for a support call.", diff --git a/services/static-webserver/client/source/class/osparc/ui/list/CollaboratorListItem.js b/services/static-webserver/client/source/class/osparc/ui/list/CollaboratorListItem.js index 78b6aec28f9..211b126b7d6 100644 --- a/services/static-webserver/client/source/class/osparc/ui/list/CollaboratorListItem.js +++ b/services/static-webserver/client/source/class/osparc/ui/list/CollaboratorListItem.js @@ -16,7 +16,7 @@ ************************************************************************ */ qx.Class.define("osparc.ui.list.CollaboratorListItem", { - extend: osparc.ui.list.ListItem, + extend: osparc.ui.list.ListItemWithMenu, properties: { collabType: { @@ -30,20 +30,6 @@ qx.Class.define("osparc.ui.list.CollaboratorListItem", { nullable: true }, - accessRights: { - check: "Object", - apply: "__applyAccessRights", - event: "changeAccessRights", - nullable: true - }, - - showOptions: { - check: "Boolean", - apply: "__applyShowOptions", - event: "changeShowOptions", - nullable: true - }, - resourceType : { check: "String", event: "changeResourceType", @@ -103,31 +89,6 @@ qx.Class.define("osparc.ui.list.CollaboratorListItem", { return roleInfo; }, - _createChildControlImpl: function(id) { - let control; - switch (id) { - case "options": { - const iconSize = 25; - control = new qx.ui.form.MenuButton().set({ - maxWidth: iconSize, - maxHeight: iconSize, - alignX: "center", - alignY: "middle", - icon: "@FontAwesome5Solid/ellipsis-v/"+(iconSize-11), - focusable: false - }); - this._add(control, { - row: 0, - column: 3, - rowSpan: 2 - }); - break; - } - } - - return control || this.base(arguments, id); - }, - // overridden _applyTitle: function(value) { if (value === null) { @@ -175,7 +136,8 @@ qx.Class.define("osparc.ui.list.CollaboratorListItem", { } }, - __applyAccessRights: function(value) { + // overridden + _applyAccessRights: function(value) { if (value === null) { return; } @@ -278,10 +240,5 @@ qx.Class.define("osparc.ui.list.CollaboratorListItem", { return menu; }, - - __applyShowOptions: function(value) { - const optionsMenu = this.getChildControl("options"); - optionsMenu.setVisibility(value ? "visible" : "excluded"); - } } }); diff --git a/services/static-webserver/client/source/class/osparc/ui/list/ListItem.js b/services/static-webserver/client/source/class/osparc/ui/list/ListItem.js index 257c7d3185c..19bb4d2473a 100644 --- a/services/static-webserver/client/source/class/osparc/ui/list/ListItem.js +++ b/services/static-webserver/client/source/class/osparc/ui/list/ListItem.js @@ -108,6 +108,12 @@ qx.Class.define("osparc.ui.list.ListItem", { nullable : true }, + subSubtitle: { + check : "String", + apply : "__applySubSubtitle", + nullable : true + }, + role: { check : "String", apply : "__applyRole", @@ -146,7 +152,7 @@ qx.Class.define("osparc.ui.list.ListItem", { this._add(control, { row: 0, column: 0, - rowSpan: 2 + rowSpan: 3 }); break; case "title": @@ -183,6 +189,17 @@ qx.Class.define("osparc.ui.list.ListItem", { column: 1 }); break; + case "sub-subtitle": + control = new qx.ui.basic.Label().set({ + font: "text-12", + selectable: true, + rich: true, + }); + this._add(control, { + row: 2, + column: 1 + }); + break; case "role": control = new qx.ui.basic.Label().set({ font: "text-13", @@ -191,7 +208,7 @@ qx.Class.define("osparc.ui.list.ListItem", { this._add(control, { row: 0, column: 2, - rowSpan: 2 + rowSpan: 3 }); break; } @@ -247,6 +264,14 @@ qx.Class.define("osparc.ui.list.ListItem", { label.setValue(value); }, + __applySubSubtitle: function(value) { + if (value === null) { + return; + } + const label = this.getChildControl("sub-subtitle"); + label.setValue(value); + }, + __applyRole: function(value) { if (value === null) { return;