You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

actions.web.ts 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. // @ts-expect-error
  2. import { FEEDBACK_REQUEST_IN_PROGRESS } from '../../../modules/UI/UIErrors';
  3. import { IStore } from '../app/types';
  4. import { IJitsiConference } from '../base/conference/reducer';
  5. import { openDialog } from '../base/dialog/actions';
  6. import { extractFqnFromPath } from '../dynamic-branding/functions.any';
  7. import {
  8. CANCEL_FEEDBACK,
  9. SUBMIT_FEEDBACK_ERROR,
  10. SUBMIT_FEEDBACK_SUCCESS
  11. } from './actionTypes';
  12. import FeedbackDialog from './components/FeedbackDialog.web';
  13. import { sendFeedbackToJaaSRequest, shouldSendJaaSFeedbackMetadata } from './functions.web';
  14. /**
  15. * Caches the passed in feedback in the redux store.
  16. *
  17. * @param {number} score - The quality score given to the conference.
  18. * @param {string} message - A description entered by the participant that
  19. * explains the rating.
  20. * @returns {{
  21. * type: CANCEL_FEEDBACK,
  22. * message: string,
  23. * score: number
  24. * }}
  25. */
  26. export function cancelFeedback(score: number, message: string) {
  27. return {
  28. type: CANCEL_FEEDBACK,
  29. message,
  30. score
  31. };
  32. }
  33. /**
  34. * Potentially open the {@code FeedbackDialog}. It will not be opened if it is
  35. * already open or feedback has already been submitted.
  36. *
  37. * @param {JistiConference} conference - The conference for which the feedback
  38. * would be about. The conference is passed in because feedback can occur after
  39. * a conference has been left, so references to it may no longer exist in redux.
  40. * @param {string} title - The feedback dialog title.
  41. * @returns {Promise} Resolved with value - false if the dialog is enabled and
  42. * resolved with true if the dialog is disabled or the feedback was already
  43. * submitted. Rejected if another dialog is already displayed.
  44. */
  45. export function maybeOpenFeedbackDialog(conference: IJitsiConference, title?: string) {
  46. type R = {
  47. feedbackSubmitted: boolean;
  48. showThankYou: boolean;
  49. wasDialogShown: boolean;
  50. };
  51. return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<R> => {
  52. const state = getState();
  53. const { feedbackPercentage = 100 } = state['features/base/config'];
  54. if (config.iAmRecorder) {
  55. // Intentionally fall through the if chain to prevent further action
  56. // from being taken with regards to showing feedback.
  57. } else if (state['features/base/dialog'].component === FeedbackDialog) {
  58. // Feedback is currently being displayed.
  59. return Promise.reject(FEEDBACK_REQUEST_IN_PROGRESS);
  60. } else if (state['features/feedback'].submitted) {
  61. // Feedback has been submitted already.
  62. return Promise.resolve({
  63. feedbackSubmitted: true,
  64. showThankYou: true,
  65. wasDialogShown: false
  66. });
  67. } else if (
  68. (conference.isCallstatsEnabled() || shouldSendJaaSFeedbackMetadata(state))
  69. && feedbackPercentage > Math.random() * 100) {
  70. return new Promise(resolve => {
  71. dispatch(openFeedbackDialog(conference, title, () => {
  72. const { submitted } = getState()['features/feedback'];
  73. resolve({
  74. feedbackSubmitted: submitted,
  75. showThankYou: false,
  76. wasDialogShown: true
  77. });
  78. }));
  79. });
  80. }
  81. // If the feedback functionality isn't enabled we show a "thank you"
  82. // message. Signaling it (true), so the caller of requestFeedback can
  83. // act on it.
  84. return Promise.resolve({
  85. feedbackSubmitted: false,
  86. showThankYou: true,
  87. wasDialogShown: false
  88. });
  89. };
  90. }
  91. /**
  92. * Opens {@code FeedbackDialog}.
  93. *
  94. * @param {JitsiConference} conference - The JitsiConference that is being
  95. * rated. The conference is passed in because feedback can occur after a
  96. * conference has been left, so references to it may no longer exist in redux.
  97. * @param {string} [title] - The feedback dialog title.
  98. * @param {Function} [onClose] - An optional callback to invoke when the dialog
  99. * is closed.
  100. * @returns {Object}
  101. */
  102. export function openFeedbackDialog(conference?: IJitsiConference, title?: string, onClose?: Function) {
  103. return openDialog(FeedbackDialog, {
  104. conference,
  105. onClose,
  106. title
  107. });
  108. }
  109. /**
  110. * Sends feedback metadata to JaaS endpoint.
  111. *
  112. * @param {JitsiConference} conference - The JitsiConference that is being rated.
  113. * @param {Object} feedback - The feedback message and score.
  114. *
  115. * @returns {Promise}
  116. */
  117. export function sendJaasFeedbackMetadata(conference: IJitsiConference, feedback: Object) {
  118. return (_dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  119. const state = getState();
  120. if (!shouldSendJaaSFeedbackMetadata(state)) {
  121. return Promise.resolve();
  122. }
  123. const { jaasFeedbackMetadataURL } = state['features/base/config'];
  124. const { jwt, user, tenant } = state['features/base/jwt'];
  125. const meetingFqn = extractFqnFromPath();
  126. const feedbackData = {
  127. ...feedback,
  128. sessionId: conference.sessionId,
  129. userId: user?.id,
  130. meetingFqn,
  131. jwt,
  132. tenant
  133. };
  134. return sendFeedbackToJaaSRequest(jaasFeedbackMetadataURL, feedbackData);
  135. };
  136. }
  137. /**
  138. * Send the passed in feedback.
  139. *
  140. * @param {number} score - An integer between 1 and 5 indicating the user
  141. * feedback. The negative integer -1 is used to denote no score was selected.
  142. * @param {string} message - Detailed feedback from the user to explain the
  143. * rating.
  144. * @param {JitsiConference} conference - The JitsiConference for which the
  145. * feedback is being left.
  146. * @returns {Function}
  147. */
  148. export function submitFeedback(
  149. score: number,
  150. message: string,
  151. conference: IJitsiConference) {
  152. return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
  153. const state = getState();
  154. const promises = [];
  155. if (conference.isCallstatsEnabled()) {
  156. promises.push(conference.sendFeedback(score, message));
  157. }
  158. if (shouldSendJaaSFeedbackMetadata(state)) {
  159. promises.push(dispatch(sendJaasFeedbackMetadata(conference, {
  160. score,
  161. message
  162. })));
  163. }
  164. return Promise.allSettled(promises)
  165. .then(results => {
  166. const rejected = results.find((result): result is PromiseRejectedResult => result?.status === 'rejected');
  167. if (typeof rejected === 'undefined') {
  168. dispatch({ type: SUBMIT_FEEDBACK_SUCCESS });
  169. return Promise.resolve();
  170. }
  171. const error = rejected.reason;
  172. dispatch({
  173. type: SUBMIT_FEEDBACK_ERROR,
  174. error
  175. });
  176. return Promise.reject(error);
  177. });
  178. };
  179. }