From a00ac1822c69b8684d480e61b5508d05225b86bc Mon Sep 17 00:00:00 2001 From: Jorge Miguel Lobo Escalona Date: Thu, 12 Sep 2024 18:59:08 +0200 Subject: [PATCH] F OpenNebula/one#6708: Fixed review of support tab in fireedge (#3227) --- .../CreateForm/Steps/General/schema.js | 20 ++++- .../components/Tables/Support/actions.js | 2 + .../Tabs/Support/Comments/CommentBar/index.js | 8 +- .../Support/Comments/CommentBar/schema.js | 21 ++++- .../src/client/constants/translates.js | 2 + .../src/client/features/OneApi/support.js | 60 ++++++++++--- src/fireedge/src/server/routes/api/index.js | 3 + .../server/routes/api/zendesk/functions.js | 85 ++++++++++++++----- .../src/server/routes/api/zendesk/routes.js | 3 + 9 files changed, 163 insertions(+), 41 deletions(-) diff --git a/src/fireedge/src/client/components/Forms/Support/CreateForm/Steps/General/schema.js b/src/fireedge/src/client/components/Forms/Support/CreateForm/Steps/General/schema.js index 4a7d0dd3751..316362cb8e3 100644 --- a/src/fireedge/src/client/components/Forms/Support/CreateForm/Steps/General/schema.js +++ b/src/fireedge/src/client/components/Forms/Support/CreateForm/Steps/General/schema.js @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { string, object, ObjectSchema } from 'yup' +import { INPUT_TYPES, SEVERITIES, T } from 'client/constants' import { Field, arrayToOptions, getValidationFromFields } from 'client/utils' -import { T, INPUT_TYPES, SEVERITIES } from 'client/constants' +// eslint-disable-next-line no-unused-vars +import { ObjectSchema, mixed, object, string } from 'yup' /** @type {Field} name field */ export const SUBJECT = { @@ -51,10 +52,23 @@ export const SEVERITY = { grid: { xs: 12, md: 12 }, } +/** @type {Field} Attachment field */ +export const ATTACHMENTS = { + name: 'ATTACHMENTS', + label: T.Upload, + type: INPUT_TYPES.FILE, + validation: mixed() + .notRequired() + .test('fileSize', T.FileTooLarge, (value) => + value?.size ? value.size <= 50 * 1024 ** 2 : true + ), + grid: { xs: 12, md: 12 }, +} + /** * @returns {Field[]} Fields */ -export const FIELDS = [SUBJECT, BODY, SEVERITY] +export const FIELDS = [SUBJECT, BODY, SEVERITY, ATTACHMENTS] /** * @param {object} [stepProps] - Step props diff --git a/src/fireedge/src/client/components/Tables/Support/actions.js b/src/fireedge/src/client/components/Tables/Support/actions.js index bd16b118d29..01f02c8a255 100644 --- a/src/fireedge/src/client/components/Tables/Support/actions.js +++ b/src/fireedge/src/client/components/Tables/Support/actions.js @@ -63,12 +63,14 @@ const Actions = () => { SUBJECT: subject, BODY: body, SEVERITY: severity, + ATTACHMENTS: attachments, } = formData.template await createTicket({ subject, body, version, severity, + attachments, }) }, }, diff --git a/src/fireedge/src/client/components/Tabs/Support/Comments/CommentBar/index.js b/src/fireedge/src/client/components/Tabs/Support/Comments/CommentBar/index.js index a7a532a5311..b61c3849317 100644 --- a/src/fireedge/src/client/components/Tabs/Support/Comments/CommentBar/index.js +++ b/src/fireedge/src/client/components/Tabs/Support/Comments/CommentBar/index.js @@ -60,14 +60,14 @@ const CommentBar = ({ resolver: yupResolver(FORM.SCHEMA), }) - const onSubmit = (fields) => { + const onSubmit = async (fields) => { const commentBody = { id: ticket.id, body: marked.parse(sanitize`${fields.BODY}`), - // attachments: fields.ATTACHMENTS, + attachments: fields.ATTACHMENTS, } - fields.solved && (commentBody.solved = true) - update(commentBody) + fields.SOLVED && (commentBody.solved = true) + await update(commentBody) setComments([ ...comments, { diff --git a/src/fireedge/src/client/components/Tabs/Support/Comments/CommentBar/schema.js b/src/fireedge/src/client/components/Tabs/Support/Comments/CommentBar/schema.js index 22c4ebc42a2..83d87f39194 100644 --- a/src/fireedge/src/client/components/Tabs/Support/Comments/CommentBar/schema.js +++ b/src/fireedge/src/client/components/Tabs/Support/Comments/CommentBar/schema.js @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { boolean, object, string } from 'yup' -// eslint-disable-next-line no-unused-vars import { INPUT_TYPES, T } from 'client/constants' +import { boolean, mixed, object, string } from 'yup' +// eslint-disable-next-line no-unused-vars import { Field, ObjectSchema, getValidationFromFields } from 'client/utils' /** @type {Field} Body message field */ @@ -31,16 +31,29 @@ export const BODY = { /** @type {Field} Solved field */ export const SOLVED = { name: 'SOLVED', - label: `${T.Description} (${T.WeSupportMarkdown})`, + label: T.MarkAsclosed, type: INPUT_TYPES.CHECKBOX, validation: boolean().default(() => false), grid: { xs: 12, md: 12 }, } +/** @type {Field} Attachment field */ +export const ATTACHMENTS = { + name: 'ATTACHMENTS', + label: T.Upload, + type: INPUT_TYPES.FILE, + validation: mixed() + .notRequired() + .test('fileSize', T.FileTooLarge, (value) => + value?.size ? value.size <= 50 * 1024 ** 2 : true + ), + grid: { xs: 12, md: 12 }, +} + /** * @returns {Field[]} Fields */ -export const FIELDS = [BODY, SOLVED] +export const FIELDS = [BODY, SOLVED, ATTACHMENTS] /** * @param {object} [stepProps] - Step props diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 51a4ab1ec1e..a1000a0006a 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -516,6 +516,7 @@ module.exports = { ResolutionTicket: 'You should just use this. If you want to place the ticket as solved', AddComment: 'Add comment to close the ticket', + MarkAsclosed: 'Please consider to this request resolved', /* sections - system */ User: 'User', @@ -655,6 +656,7 @@ module.exports = { Images: 'Images', File: 'File', Files: 'Files', + FileTooLarge: 'File too large', Marketplace: 'Marketplace', Marketplaces: 'Marketplaces', App: 'App', diff --git a/src/fireedge/src/client/features/OneApi/support.js b/src/fireedge/src/client/features/OneApi/support.js index 88263cb58b1..9b7e2a18a2f 100644 --- a/src/fireedge/src/client/features/OneApi/support.js +++ b/src/fireedge/src/client/features/OneApi/support.js @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ +import { TicketComment } from 'client/constants' +import { ONE_RESOURCES_POOL, oneApi } from 'client/features/OneApi' +import http from 'client/utils/rest' import { Actions as ActionsSupport, Commands as CommandsSupport, } from 'server/routes/api/support/routes' import { Actions, Commands } from 'server/routes/api/zendesk/routes' -import { TicketComment } from 'client/constants' -import { ONE_RESOURCES_POOL, oneApi } from 'client/features/OneApi' - const { SUPPORT_POOL } = ONE_RESOURCES_POOL const authSupportApi = oneApi.injectEndpoints({ @@ -162,11 +162,29 @@ const authSupportApi = oneApi.injectEndpoints({ * @returns {object} Response data from request * @throws Fails when response isn't code 200 */ - query: (params) => { - const name = Actions.ZENDESK_UPDATE - const command = { name, ...Commands[name] } + queryFn: async (params) => { + const { attachments, id, body, solved } = params + try { + const data = new FormData() + attachments && data.append('attachments', attachments) + solved && data.append('solved', solved) + data.append('body', body) - return { params, command } + const response = await http.request({ + url: `/api/zendesk/${id}`, + method: 'PUT', + data, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + + return { data: response.data } + } catch (axiosError) { + const { response } = axiosError + + return { error: { status: response?.status, data: response?.data } } + } }, invalidatesTags: (_, __, { id }) => [{ type: SUPPORT_POOL, id }], }), @@ -182,11 +200,31 @@ const authSupportApi = oneApi.injectEndpoints({ * @returns {object} Response data from request * @throws Fails when response isn't code 200 */ - query: (params) => { - const name = Actions.ZENDESK_CREATE - const command = { name, ...Commands[name] } + queryFn: async (params) => { + const { attachments, subject, body, version, severity } = params + try { + const data = new FormData() + data.append('subject', subject) + data.append('body', body) + data.append('version', version) + data.append('severity', severity) + attachments && data.append('attachments', attachments) - return { params, command } + const response = await http.request({ + url: `/api/zendesk`, + method: 'POST', + data, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + + return { data: response.data } + } catch (axiosError) { + const { response } = axiosError + + return { error: { status: response?.status, data: response?.data } } + } }, invalidatesTags: [SUPPORT_POOL], }), diff --git a/src/fireedge/src/server/routes/api/index.js b/src/fireedge/src/server/routes/api/index.js index c6a01822edc..ad43e2e9438 100644 --- a/src/fireedge/src/server/routes/api/index.js +++ b/src/fireedge/src/server/routes/api/index.js @@ -26,11 +26,13 @@ const { defaultWebpackMode, defaultConfigErrorMessage, defaultTmpPath, + from: fromData, } = require('server/utils/constants/defaults') const { writeInLogger } = require('server/utils/logger') const { getSunstoneConfig } = require('server/utils/yml') genPathResources() +const { postBody } = fromData const appConfig = getSunstoneConfig() const optsMulter = { dest: appConfig.tmpdir || defaultTmpPath } @@ -121,6 +123,7 @@ routes.forEach((file) => { }) } serverDataSource.files = parseFiles(req && req.files) + serverDataSource[postBody] = req.body return action( res, diff --git a/src/fireedge/src/server/routes/api/zendesk/functions.js b/src/fireedge/src/server/routes/api/zendesk/functions.js index 8d678415216..8bc333cd3f6 100644 --- a/src/fireedge/src/server/routes/api/zendesk/functions.js +++ b/src/fireedge/src/server/routes/api/zendesk/functions.js @@ -34,6 +34,7 @@ const httpBadRequest = httpResponse(badRequest, '', '') * @param {string} configFormatCreate.body - body * @param {string} configFormatCreate.version - one version * @param {string} configFormatCreate.severity - ticket severity + * @param {object} configFormatCreate.attachments - attachment file * @returns {object|undefined} format message create ticket */ const formatCreate = ({ @@ -41,10 +42,11 @@ const formatCreate = ({ body = '', version = '', severity = '', + attachments = [], }) => { if (!(subject && body && version && severity)) return - return { + const rtn = { request: { subject, comment: { @@ -58,6 +60,11 @@ const formatCreate = ({ tags: [severity], }, } + + attachments?.length > 0 && + (rtn.request.comment.uploads = attachments.filter((att) => att)) + + return rtn } /** @@ -313,7 +320,7 @@ const create = ( params = {}, userData = {} ) => { - const { subject, body, version, severity } = params + const { subject, body, version, severity, attachments } = params const { user, password } = userData if ( subject && @@ -326,21 +333,61 @@ const create = ( ) { const session = getSession(user, password) if (session.zendesk && session.zendesk.id) { - /** CREATE TICKET ZENDESK */ const zendeskClient = zendesk.createClient(session.zendesk) - const ticket = formatCreate(params) - zendeskClient.requests.create(ticket, (err, req, result) => { - let method = ok - let data = '' - if (err) { - method = internalServerError - data = parseBufferError(err) - } else if (result) { - data = result - } - response.locals.httpCode = httpResponse(method, data) - next() - }) + + const sendRequest = (requestParams = {}) => { + /** CREATE TICKET ZENDESK */ + const ticket = formatCreate(requestParams) + zendeskClient.requests.create(ticket, (err, _, result) => { + let method = ok + let data = '' + + if (err) { + method = internalServerError + data = parseBufferError(err) + } else if (result) { + data = result + } + response.locals.httpCode = httpResponse(method, data) + next() + }) + } + + /** UPLOAD FILES */ + let uploadedAttachments + if ( + attachments && + typeof zendeskClient?.attachments?.upload === 'function' + ) { + attachments.forEach((att = {}) => { + if (att && att.originalname && att.path) { + zendeskClient.attachments.upload( + att.path, + { + filename: att.originalname, + }, + (err, _, result) => { + const token = + (result && result.upload && result.upload.token) || '' + if (uploadedAttachments) { + uploadedAttachments.push(token) + } else { + uploadedAttachments = [token] + } + if ( + !err && + token && + uploadedAttachments.length === attachments.length + ) { + sendRequest({ ...params, attachments: uploadedAttachments }) + } + } + ) + } + }) + } else { + sendRequest({ ...params, attachments }) + } } else { response.locals.httpCode = httpResponse(unauthorized) next() @@ -374,6 +421,7 @@ const update = ( ) => { const { id, body, attachments } = params const { user, password } = userData + if (Number.isInteger(parseInt(id, 10)) && body && user && password) { const session = getSession(userData.user, userData.password) if (session.zendesk && session.zendesk.id) { @@ -401,8 +449,7 @@ const update = ( let uploadedAttachments if ( attachments && - zendeskClient.attachments && - typeof zendeskClient.attachments.upload === 'function' + typeof zendeskClient?.attachments?.upload === 'function' ) { attachments.forEach((att = {}) => { if (att && att.originalname && att.path) { @@ -411,7 +458,7 @@ const update = ( { filename: att.originalname, }, - (err, req, result) => { + (err, _, result) => { const token = (result && result.upload && result.upload.token) || '' if (uploadedAttachments) { diff --git a/src/fireedge/src/server/routes/api/zendesk/routes.js b/src/fireedge/src/server/routes/api/zendesk/routes.js index 3e248fcb850..252fcf7afce 100644 --- a/src/fireedge/src/server/routes/api/zendesk/routes.js +++ b/src/fireedge/src/server/routes/api/zendesk/routes.js @@ -70,6 +70,9 @@ module.exports = { severity: { from: postBody, }, + attachments: { + from: 'files', + }, }, }, [ZENDESK_UPDATE]: {