From 3159b7fe6fbee6f23ec00c10c4bb4a82bf512627 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Fri, 4 Oct 2024 18:25:32 +0200 Subject: [PATCH 1/2] fix: migrate pollService to TS Signed-off-by: Maksim Sukharev --- src/services/pollService.js | 65 ---------------------------- src/services/pollService.ts | 69 ++++++++++++++++++++++++++++++ src/stores/__tests__/polls.spec.js | 21 +++++---- src/stores/polls.js | 17 +++++--- src/types/index.ts | 10 +++++ 5 files changed, 103 insertions(+), 79 deletions(-) delete mode 100644 src/services/pollService.js create mode 100644 src/services/pollService.ts diff --git a/src/services/pollService.js b/src/services/pollService.js deleted file mode 100644 index d4f2dc9f74a..00000000000 --- a/src/services/pollService.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -import axios from '@nextcloud/axios' -import { generateOcsUrl } from '@nextcloud/router' - -const pollService = { - // For API documentation see https://nextcloud-talk.readthedocs.io/en/latest/poll/ - - /** - * - * @param {string} token The conversation token - * @param {string} question The question of the polln - * @param {Array} options The options participants can vote for - * @param {number} resultMode Result mode of the poll - * @param {number} maxVotes Maximum amount of options a user can vote for, 0 means unlimited - * @return {object} The poll object - */ - async postNewPoll(token, question, options, resultMode, maxVotes) { - return axios.post(generateOcsUrl('apps/spreed/api/v1/poll/{token}', { token }), { - question, - options, - resultMode, - maxVotes, - }) - }, - - /** - * - * @param {string} token The conversation token - * @param {number} pollId ID of the poll - * @return {object} The poll object - */ - async getPollData(token, pollId) { - return axios.get(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId })) - }, - - /** - * Submit poll vote - * - * @param {string} token The conversation token - * @param {number} pollId ID of the poll - * @param {Array} optionIds The option IDs the participant wants to vote for - * @return {object} The poll object - */ - async submitVote(token, pollId, optionIds) { - return axios.post(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId }), { - optionIds, - }) - }, - - /** - * Ends the poll - * - * @param {string} token The conversation token - * @param {number} pollId ID of the poll - * @return {object} The poll object - */ - async endPoll(token, pollId) { - return axios.delete(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId })) - }, -} - -export default pollService diff --git a/src/services/pollService.ts b/src/services/pollService.ts new file mode 100644 index 00000000000..60ed202c755 --- /dev/null +++ b/src/services/pollService.ts @@ -0,0 +1,69 @@ +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import axios from '@nextcloud/axios' +import { generateOcsUrl } from '@nextcloud/router' + +import type { + closePollResponse, + createPollParams, + createPollResponse, + getPollResponse, + votePollParams, + votePollResponse, +} from '../types/index.ts' + +type createPollPayload = { token: string } & createPollParams + +/** + * @param payload The payload + * @param payload.token The conversation token + * @param payload.question The question of the poll + * @param payload.options The options participants can vote for + * @param payload.resultMode Result mode of the poll (0 - always visible | 1 - hidden until the poll is closed) + * @param payload.maxVotes Maximum amount of options a user can vote for (0 - unlimited | 1 - single answer) + */ +const createPoll = async ({ token, question, options, resultMode, maxVotes }: createPollPayload): createPollResponse => { + return axios.post(generateOcsUrl('apps/spreed/api/v1/poll/{token}', { token }), { + question, + options, + resultMode, + maxVotes, + } as createPollParams) +} + +/** + * @param token The conversation token + * @param pollId Id of the poll + */ +const getPollData = async (token: string, pollId: string): getPollResponse => { + return axios.get(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId })) +} + +/** + * @param token The conversation token + * @param pollId Id of the poll + * @param optionIds Indexes of options the participant votes for + */ +const submitVote = async (token: string, pollId: string, optionIds: votePollParams['optionIds']): votePollResponse => { + return axios.post(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId }), { + optionIds, + } as votePollParams) +} + +/** + * @param token The conversation token + * @param pollId Id of the poll + */ +const endPoll = async (token: string, pollId: string): closePollResponse => { + return axios.delete(generateOcsUrl('apps/spreed/api/v1/poll/{token}/{pollId}', { token, pollId })) +} + +export { + createPoll, + getPollData, + submitVote, + endPoll, +} diff --git a/src/stores/__tests__/polls.spec.js b/src/stores/__tests__/polls.spec.js index 62a9f94bad2..d44de007014 100644 --- a/src/stores/__tests__/polls.spec.js +++ b/src/stores/__tests__/polls.spec.js @@ -6,12 +6,17 @@ import flushPromises from 'flush-promises' import { setActivePinia, createPinia } from 'pinia' import { ATTENDEE } from '../../constants.js' -import pollService from '../../services/pollService.js' +import { + createPoll, + getPollData, + submitVote, + endPoll, +} from '../../services/pollService.ts' import { generateOCSResponse } from '../../test-helpers.js' import { usePollsStore } from '../polls.js' -jest.mock('../../services/pollService.js', () => ({ - postNewPoll: jest.fn(), +jest.mock('../../services/pollService', () => ({ + createPoll: jest.fn(), getPollData: jest.fn(), submitVote: jest.fn(), endPoll: jest.fn(), @@ -88,7 +93,7 @@ describe('pollsStore', () => { it('receives a poll from server and adds it to the store', async () => { // Arrange const response = generateOCSResponse({ payload: poll }) - pollService.getPollData.mockResolvedValue(response) + getPollData.mockResolvedValue(response) // Act await pollsStore.getPollData({ token: TOKEN, pollId: poll.id }) @@ -101,7 +106,7 @@ describe('pollsStore', () => { // Arrange jest.useFakeTimers() const response = generateOCSResponse({ payload: poll }) - pollService.getPollData.mockResolvedValue(response) + getPollData.mockResolvedValue(response) // Act pollsStore.debounceGetPollData({ token: TOKEN, pollId: poll.id }) @@ -116,7 +121,7 @@ describe('pollsStore', () => { it('creates a poll and adds it to the store', async () => { // Arrange const response = generateOCSResponse({ payload: poll }) - pollService.postNewPoll.mockResolvedValue(response) + createPoll.mockResolvedValue(response) // Act await pollsStore.createPoll({ token: TOKEN, ...pollRequest }) @@ -129,7 +134,7 @@ describe('pollsStore', () => { // Arrange pollsStore.addPoll({ token: TOKEN, poll }) const response = generateOCSResponse({ payload: pollWithVote }) - pollService.submitVote.mockResolvedValue(response) + submitVote.mockResolvedValue(response) // Act await pollsStore.submitVote({ token: TOKEN, pollId: poll.id, optionIds: [0] }) @@ -142,7 +147,7 @@ describe('pollsStore', () => { // Arrange pollsStore.addPoll({ token: TOKEN, poll: pollWithVote }) const response = generateOCSResponse({ payload: pollWithVoteEnded }) - pollService.endPoll.mockResolvedValue(response) + endPoll.mockResolvedValue(response) // Act await pollsStore.endPoll({ token: TOKEN, pollId: poll.id }) diff --git a/src/stores/polls.js b/src/stores/polls.js index 83302c898b0..3592b822e77 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.js @@ -9,7 +9,12 @@ import Vue from 'vue' import { showError, showInfo, TOAST_PERMANENT_TIMEOUT } from '@nextcloud/dialogs' import { t } from '@nextcloud/l10n' -import pollService from '../services/pollService.js' +import { + createPoll, + getPollData, + submitVote, + endPoll, +} from '../services/pollService.ts' export const usePollsStore = defineStore('polls', { state: () => ({ @@ -39,7 +44,7 @@ export const usePollsStore = defineStore('polls', { async getPollData({ token, pollId }) { try { - const response = await pollService.getPollData(token, pollId) + const response = await getPollData(token, pollId) this.addPoll({ token, poll: response.data.ocs.data }) } catch (error) { console.error(error) @@ -72,13 +77,13 @@ export const usePollsStore = defineStore('polls', { async createPoll({ token, question, options, resultMode, maxVotes }) { try { - const response = await pollService.postNewPoll( + const response = await createPoll({ token, question, options, resultMode, maxVotes, - ) + }) this.addPoll({ token, poll: response.data.ocs.data }) return response.data.ocs.data @@ -89,7 +94,7 @@ export const usePollsStore = defineStore('polls', { async submitVote({ token, pollId, optionIds }) { try { - const response = await pollService.submitVote(token, pollId, optionIds) + const response = await submitVote(token, pollId, optionIds) this.addPoll({ token, poll: response.data.ocs.data }) } catch (error) { console.error(error) @@ -99,7 +104,7 @@ export const usePollsStore = defineStore('polls', { async endPoll({ token, pollId }) { try { - const response = await pollService.endPoll(token, pollId) + const response = await endPoll(token, pollId) this.addPoll({ token, poll: response.data.ocs.data }) } catch (error) { console.error(error) diff --git a/src/types/index.ts b/src/types/index.ts index d9b706301a8..86d22a1c251 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -160,3 +160,13 @@ export type requestAssistanceResponse = ApiResponse export type switchToBreakoutRoomParams = operations['breakout_room-switch-breakout-room']['requestBody']['content']['application/json'] export type switchToBreakoutRoomResponse = ApiResponse + +// Polls +export type Poll = components['schemas']['Poll'] + +export type getPollResponse = ApiResponse +export type createPollParams = operations['poll-create-poll']['requestBody']['content']['application/json'] +export type createPollResponse = ApiResponse +export type votePollParams = Required['requestBody']['content']['application/json'] +export type votePollResponse = ApiResponse +export type closePollResponse = ApiResponse From d5136f13c15c1e503a1bd41f2e6aa771af163850 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Fri, 4 Oct 2024 18:55:52 +0200 Subject: [PATCH 2/2] fix: migrate polls store to TS Signed-off-by: Maksim Sukharev --- .../Message/MessagePart/MessageBody.vue | 2 +- .../Message/MessagePart/Poll.vue | 2 +- .../NewMessage/NewMessagePollEditor.vue | 2 +- src/components/PollViewer/PollViewer.vue | 2 +- src/store/messagesStore.js | 2 +- src/stores/__tests__/polls.spec.js | 2 +- src/stores/{polls.js => polls.ts} | 39 ++++++++++++------- 7 files changed, 32 insertions(+), 19 deletions(-) rename src/stores/{polls.js => polls.ts} (75%) diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue index 574c6471c72..8a90977072d 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue @@ -141,7 +141,7 @@ import CallButton from '../../../../TopBar/CallButton.vue' import { useIsInCall } from '../../../../../composables/useIsInCall.js' import { useMessageInfo } from '../../../../../composables/useMessageInfo.js' import { EventBus } from '../../../../../services/EventBus.js' -import { usePollsStore } from '../../../../../stores/polls.js' +import { usePollsStore } from '../../../../../stores/polls.ts' import { parseSpecialSymbols, parseMentions } from '../../../../../utils/textParse.ts' // Regular expression to check for Unicode emojis in message text diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue index 4c2197446ac..ae9f9423a73 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/Poll.vue @@ -39,7 +39,7 @@ import { t } from '@nextcloud/l10n' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import { POLL } from '../../../../../constants.js' -import { usePollsStore } from '../../../../../stores/polls.js' +import { usePollsStore } from '../../../../../stores/polls.ts' export default { name: 'Poll', diff --git a/src/components/NewMessage/NewMessagePollEditor.vue b/src/components/NewMessage/NewMessagePollEditor.vue index a65e22a10be..166137a5547 100644 --- a/src/components/NewMessage/NewMessagePollEditor.vue +++ b/src/components/NewMessage/NewMessagePollEditor.vue @@ -77,7 +77,7 @@ import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadi import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' -import { usePollsStore } from '../../stores/polls.js' +import { usePollsStore } from '../../stores/polls.ts' export default { name: 'NewMessagePollEditor', diff --git a/src/components/PollViewer/PollViewer.vue b/src/components/PollViewer/PollViewer.vue index 5e978013c99..2085a089050 100644 --- a/src/components/PollViewer/PollViewer.vue +++ b/src/components/PollViewer/PollViewer.vue @@ -108,7 +108,7 @@ import { useId } from '../../composables/useId.ts' import { useIsInCall } from '../../composables/useIsInCall.js' import { POLL } from '../../constants.js' import { EventBus } from '../../services/EventBus.js' -import { usePollsStore } from '../../stores/polls.js' +import { usePollsStore } from '../../stores/polls.ts' export default { name: 'PollViewer', diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index 6a659c19c2f..260696b3eef 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -30,7 +30,7 @@ import { } from '../services/messagesService.ts' import { useChatExtrasStore } from '../stores/chatExtras.js' import { useGuestNameStore } from '../stores/guestName.js' -import { usePollsStore } from '../stores/polls.js' +import { usePollsStore } from '../stores/polls.ts' import { useReactionsStore } from '../stores/reactions.js' import { useSharedItemsStore } from '../stores/sharedItems.js' import CancelableRequest from '../utils/cancelableRequest.js' diff --git a/src/stores/__tests__/polls.spec.js b/src/stores/__tests__/polls.spec.js index d44de007014..c278b63ce25 100644 --- a/src/stores/__tests__/polls.spec.js +++ b/src/stores/__tests__/polls.spec.js @@ -13,7 +13,7 @@ import { endPoll, } from '../../services/pollService.ts' import { generateOCSResponse } from '../../test-helpers.js' -import { usePollsStore } from '../polls.js' +import { usePollsStore } from '../polls.ts' jest.mock('../../services/pollService', () => ({ createPoll: jest.fn(), diff --git a/src/stores/polls.js b/src/stores/polls.ts similarity index 75% rename from src/stores/polls.js rename to src/stores/polls.ts index 3592b822e77..672061a5bc3 100644 --- a/src/stores/polls.js +++ b/src/stores/polls.ts @@ -15,9 +15,22 @@ import { submitVote, endPoll, } from '../services/pollService.ts' - +import type { + ChatMessage, + createPollParams, + Poll, votePollParams +} from '../types/index.ts' + +type createPollPayload = { token: string } & createPollParams +type submitVotePayload = { token: string, pollId: string } & Pick +type State = { + polls: Record>, + debouncedFunctions: Record void>>, + activePoll: null, + pollToastsQueue: Record>, +} export const usePollsStore = defineStore('polls', { - state: () => ({ + state: (): State => ({ polls: {}, debouncedFunctions: {}, activePoll: null, @@ -25,24 +38,24 @@ export const usePollsStore = defineStore('polls', { }), getters: { - getPoll: (state) => (token, pollId) => { + getPoll: (state) => (token: string, pollId: string): Poll => { return state.polls[token]?.[pollId] }, - isNewPoll: (state) => (pollId) => { + isNewPoll: (state) => (pollId: number) => { return state.pollToastsQueue[pollId] !== undefined }, }, actions: { - addPoll({ token, poll }) { + addPoll({ token, poll }: { token: string, poll: Poll }) { if (!this.polls[token]) { Vue.set(this.polls, token, {}) } Vue.set(this.polls[token], poll.id, poll) }, - async getPollData({ token, pollId }) { + async getPollData({ token, pollId }: { token: string, pollId: string }) { try { const response = await getPollData(token, pollId) this.addPoll({ token, poll: response.data.ocs.data }) @@ -60,7 +73,7 @@ export const usePollsStore = defineStore('polls', { * @param { string } root0.token The token of the conversation * @param { number } root0.pollId The id of the poll */ - debounceGetPollData({ token, pollId }) { + debounceGetPollData({ token, pollId }: { token: string, pollId: string }) { if (!this.debouncedFunctions[token]) { Vue.set(this.debouncedFunctions, token, {}) } @@ -75,7 +88,7 @@ export const usePollsStore = defineStore('polls', { this.debouncedFunctions[token][pollId]() }, - async createPoll({ token, question, options, resultMode, maxVotes }) { + async createPoll({ token, question, options, resultMode, maxVotes }: createPollPayload) { try { const response = await createPoll({ token, @@ -92,7 +105,7 @@ export const usePollsStore = defineStore('polls', { } }, - async submitVote({ token, pollId, optionIds }) { + async submitVote({ token, pollId, optionIds }: submitVotePayload) { try { const response = await submitVote(token, pollId, optionIds) this.addPoll({ token, poll: response.data.ocs.data }) @@ -102,7 +115,7 @@ export const usePollsStore = defineStore('polls', { } }, - async endPoll({ token, pollId }) { + async endPoll({ token, pollId }: { token: string, pollId: string }) { try { const response = await endPoll(token, pollId) this.addPoll({ token, poll: response.data.ocs.data }) @@ -112,7 +125,7 @@ export const usePollsStore = defineStore('polls', { } }, - setActivePoll({ token, pollId, name }) { + setActivePoll({ token, pollId, name }: { token: string, pollId: string, name: string }) { Vue.set(this, 'activePoll', { token, id: pollId, name }) }, @@ -122,7 +135,7 @@ export const usePollsStore = defineStore('polls', { } }, - addPollToast({ token, message }) { + addPollToast({ token, message }: { token: string, message: ChatMessage }) { const pollId = message.messageParameters.object.id const name = message.messageParameters.object.name @@ -141,7 +154,7 @@ export const usePollsStore = defineStore('polls', { Vue.set(this.pollToastsQueue, pollId, toast) }, - hidePollToast(pollId) { + hidePollToast(pollId: string) { if (this.pollToastsQueue[pollId]) { this.pollToastsQueue[pollId].hideToast() Vue.delete(this.pollToastsQueue, pollId)