mirror of
https://github.com/nextcloud/spreed.git
synced 2025-12-18 05:20:50 +01:00
feat(schedule): implement store and services
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
This commit is contained in:
parent
6f5b4ad13b
commit
7b5931a386
7 changed files with 305 additions and 1 deletions
|
|
@ -30,10 +30,19 @@ import storeConfig from '../../../../store/storeConfig.js'
|
|||
import { useActorStore } from '../../../../stores/actor.ts'
|
||||
import { useTokenStore } from '../../../../stores/token.ts'
|
||||
|
||||
let store
|
||||
|
||||
vi.mock('vuex', async () => {
|
||||
const vuex = await vi.importActual('vuex')
|
||||
return {
|
||||
...vuex,
|
||||
useStore: vi.fn(() => store),
|
||||
}
|
||||
})
|
||||
|
||||
describe('MessageItem.vue', () => {
|
||||
const TOKEN = 'XXTOKENXX'
|
||||
let testStoreConfig
|
||||
let store
|
||||
let messageProps
|
||||
let conversationProps
|
||||
let injected
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ import { useActorStore } from '../../../../../stores/actor.ts'
|
|||
import { useReactionsStore } from '../../../../../stores/reactions.js'
|
||||
import { generateOCSResponse } from '../../../../../test-helpers.js'
|
||||
|
||||
vi.mock('vuex', async () => {
|
||||
const vuex = await vi.importActual('vuex')
|
||||
return {
|
||||
...vuex,
|
||||
useStore: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../../../../../services/reactionsService', () => ({
|
||||
getReactionsDetails: vi.fn(),
|
||||
addReactionToMessage: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -7,12 +7,16 @@ import type { AxiosRequestConfig } from '@nextcloud/axios'
|
|||
import type {
|
||||
clearHistoryResponse,
|
||||
deleteMessageResponse,
|
||||
deleteScheduledMessageResponse,
|
||||
editMessageParams,
|
||||
editMessageResponse,
|
||||
editScheduledMessageParams,
|
||||
editScheduledMessageResponse,
|
||||
getMessageContextParams,
|
||||
getMessageContextResponse,
|
||||
getRecentThreadsParams,
|
||||
getRecentThreadsResponse,
|
||||
getScheduledMessagesResponse,
|
||||
getSubscribedThreadsParams,
|
||||
getSubscribedThreadsResponse,
|
||||
getThreadResponse,
|
||||
|
|
@ -25,6 +29,8 @@ import type {
|
|||
receiveMessagesResponse,
|
||||
renameThreadParams,
|
||||
renameThreadResponse,
|
||||
scheduleMessageParams,
|
||||
scheduleMessageResponse,
|
||||
setReadMarkerParams,
|
||||
setReadMarkerResponse,
|
||||
setThreadNotificationLevelParams,
|
||||
|
|
@ -340,19 +346,104 @@ async function renameThread(token: string, threadId: number, threadTitle: string
|
|||
} as renameThreadParams, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of scheduled messages of this user for given conversation
|
||||
*
|
||||
* @param token the conversation token
|
||||
* @param [options] Axios request options
|
||||
*/
|
||||
async function getScheduledMessages(token: string, options?: AxiosRequestConfig): getScheduledMessagesResponse {
|
||||
return axios.get(generateOcsUrl('apps/spreed/api/v1/chat/{token}/schedule', { token }), options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a new message to be poster
|
||||
*
|
||||
* @param payload The request payload
|
||||
* @param payload.token The conversation token
|
||||
* @param payload.message The message text
|
||||
* @param payload.sendAt The timestamp of when message should be posted
|
||||
* @param payload.replyTo The message id to be replied to
|
||||
* @param payload.silent whether the message should trigger a notifications
|
||||
* @param payload.threadId The thread id to post the message in
|
||||
* @param payload.threadTitle The thread title to set when creating a new thread
|
||||
* @param [options] Axios request options
|
||||
*/
|
||||
async function scheduleMessage({
|
||||
token,
|
||||
message,
|
||||
sendAt,
|
||||
replyTo,
|
||||
silent,
|
||||
threadId,
|
||||
threadTitle,
|
||||
}: scheduleMessageParams & { token: string }, options?: AxiosRequestConfig): scheduleMessageResponse {
|
||||
return axios.post(generateOcsUrl('apps/spreed/api/v1/chat/{token}/schedule', { token }), {
|
||||
message,
|
||||
sendAt,
|
||||
replyTo,
|
||||
silent,
|
||||
threadId,
|
||||
threadTitle,
|
||||
} as scheduleMessageParams, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit an already scheduled message
|
||||
*
|
||||
* @param payload The request payload
|
||||
* @param payload.token The conversation token
|
||||
* @param payload.messageId The id of scheduled message
|
||||
* @param payload.message The message text
|
||||
* @param payload.sendAt The timestamp of when message should be posted
|
||||
* @param payload.silent whether the message should trigger a notifications
|
||||
* @param payload.threadTitle The thread title to set when creating a new thread
|
||||
* @param [options] Axios request options
|
||||
*/
|
||||
async function editScheduledMessage({
|
||||
token,
|
||||
messageId,
|
||||
message,
|
||||
sendAt,
|
||||
silent,
|
||||
threadTitle,
|
||||
}: editScheduledMessageParams & { token: string, messageId: string }, options?: AxiosRequestConfig): editScheduledMessageResponse {
|
||||
return axios.post(generateOcsUrl('apps/spreed/api/v1/chat/{token}/schedule/{messageId}', { token, messageId }), {
|
||||
message,
|
||||
sendAt,
|
||||
silent,
|
||||
threadTitle,
|
||||
} as editScheduledMessageParams, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a scheduled message from the queue
|
||||
*
|
||||
* @param token The conversation token
|
||||
* @param messageId The id of scheduled message
|
||||
* @param [options] Axios request options
|
||||
*/
|
||||
async function deleteScheduledMessage(token: string, messageId: string, options?: AxiosRequestConfig): deleteScheduledMessageResponse {
|
||||
return axios.delete(generateOcsUrl('apps/spreed/api/v1/chat/{token}/schedule/{messageId}', { token, messageId }), options)
|
||||
}
|
||||
|
||||
export {
|
||||
clearConversationHistory,
|
||||
deleteMessage,
|
||||
deleteScheduledMessage,
|
||||
editMessage,
|
||||
editScheduledMessage,
|
||||
fetchMessages,
|
||||
getMessageContext,
|
||||
getRecentThreadsForConversation,
|
||||
getScheduledMessages,
|
||||
getSingleThreadForConversation,
|
||||
getSubscribedThreads,
|
||||
pollNewMessages,
|
||||
postNewMessage,
|
||||
postRichObjectToConversation,
|
||||
renameThread,
|
||||
scheduleMessage,
|
||||
setConversationUnread,
|
||||
setThreadNotificationLevel,
|
||||
summarizeChat,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,14 @@ vi.mock('../services/conversationsService', () => ({
|
|||
setCallPermissions: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('vuex', async () => {
|
||||
const vuex = await vi.importActual('vuex')
|
||||
return {
|
||||
...vuex,
|
||||
useStore: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../services/messagesService', () => ({
|
||||
updateLastReadMessage: vi.fn(),
|
||||
setConversationUnread: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@ import BrowserStorage from '../../services/BrowserStorage.js'
|
|||
import { EventBus } from '../../services/EventBus.ts'
|
||||
import { useChatExtrasStore } from '../chatExtras.ts'
|
||||
|
||||
vi.mock('vuex', async () => {
|
||||
const vuex = await vi.importActual('vuex')
|
||||
return {
|
||||
...vuex,
|
||||
useStore: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('chatExtrasStore', () => {
|
||||
const token = 'TOKEN'
|
||||
let chatExtrasStore
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@
|
|||
*/
|
||||
|
||||
import type {
|
||||
BigIntChatMessage,
|
||||
ChatMessage,
|
||||
ChatTask,
|
||||
editScheduledMessageParams,
|
||||
ScheduledMessage,
|
||||
scheduleMessageParams,
|
||||
ThreadInfo,
|
||||
} from '../types/index.ts'
|
||||
|
||||
|
|
@ -14,19 +18,25 @@ import { t } from '@nextcloud/l10n'
|
|||
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import ConfirmDialog from '../components/UIShared/ConfirmDialog.vue'
|
||||
import { PARTICIPANT } from '../constants.ts'
|
||||
import BrowserStorage from '../services/BrowserStorage.js'
|
||||
import { EventBus } from '../services/EventBus.ts'
|
||||
import {
|
||||
deleteScheduledMessage as deleteScheduledMessageApi,
|
||||
editScheduledMessage as editScheduledMessageApi,
|
||||
getRecentThreadsForConversation,
|
||||
getScheduledMessages as getScheduledMessagesApi,
|
||||
getSingleThreadForConversation,
|
||||
getSubscribedThreads,
|
||||
renameThread as renameThreadApi,
|
||||
scheduleMessage as scheduleMessageApi,
|
||||
setThreadNotificationLevel as setThreadNotificationLevelApi,
|
||||
summarizeChat,
|
||||
} from '../services/messagesService.ts'
|
||||
import { parseMentions, parseSpecialSymbols } from '../utils/textParse.ts'
|
||||
import { useActorStore } from './actor.ts'
|
||||
|
||||
const FOLLOWED_THREADS_FETCH_LIMIT = 100
|
||||
const pendingFetchSingleThreadRequests = new Set<number>()
|
||||
|
|
@ -47,6 +57,10 @@ export const useChatExtrasStore = defineStore('chatExtras', () => {
|
|||
const tasksCount = ref(0)
|
||||
const tasksDoneCount = ref(0)
|
||||
const chatSummary = ref<Record<string, Record<number, ChatTask>>>({})
|
||||
const scheduledMessages = ref<Record<string, Record<string, ScheduledMessage>>>({})
|
||||
|
||||
const actorStore = useActorStore()
|
||||
const vuexStore = useStore()
|
||||
|
||||
/**
|
||||
* Returns known thread information from the store
|
||||
|
|
@ -150,6 +164,29 @@ export const useChatExtrasStore = defineStore('chatExtras', () => {
|
|||
|| t('spreed', 'Error occurred during a summary generation')
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of scheduled messages (sorted by sendAt, prepared for chat)
|
||||
*
|
||||
* @param token - conversation token
|
||||
*/
|
||||
function getScheduledMessagesList(token: string) {
|
||||
return Object.values(scheduledMessages.value[token] ?? {})
|
||||
.sort((a, b) => a.sendAt - b.sendAt)
|
||||
.map((message) => parseScheduledToChatMessage(token, message))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scheduled messages for given conversation (sorted by sendAt timestamp)
|
||||
*
|
||||
* @param token - conversation token
|
||||
* @param messageId
|
||||
*/
|
||||
function getScheduledMessage(token: string, messageId: string): BigIntChatMessage | undefined {
|
||||
if (scheduledMessages.value[token]?.[messageId]) {
|
||||
return parseScheduledToChatMessage(token, scheduledMessages.value[token][messageId])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a thread to the store for given conversation
|
||||
*
|
||||
|
|
@ -609,6 +646,125 @@ export const useChatExtrasStore = defineStore('chatExtras', () => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts ScheduledMessage to BigIntChatMessage format (to render in chat)
|
||||
*
|
||||
* @param token - conversation token
|
||||
* @param message - scheduled message object
|
||||
*/
|
||||
function parseScheduledToChatMessage(token: string, message: ScheduledMessage): BigIntChatMessage {
|
||||
return {
|
||||
token,
|
||||
id: message.id,
|
||||
actorId: message.actorId,
|
||||
actorType: message.actorType,
|
||||
actorDisplayName: actorStore.displayName,
|
||||
message: message.message,
|
||||
messageType: message.messageType,
|
||||
referenceId: '',
|
||||
systemMessage: '',
|
||||
isReplyable: false,
|
||||
markdown: true,
|
||||
messageParameters: {},
|
||||
parent: message.parent,
|
||||
reactions: {},
|
||||
timestamp: message.sendAt,
|
||||
expirationTimestamp: 0,
|
||||
threadId: message.threadId,
|
||||
threadTitle: message.threadTitle,
|
||||
isThread: !!message.threadId,
|
||||
silent: message.silent,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch scheduled messages for given conversation
|
||||
*
|
||||
* @param token - conversation token
|
||||
*/
|
||||
async function fetchScheduledMessages(token: string) {
|
||||
try {
|
||||
const response = await getScheduledMessagesApi(token)
|
||||
if (!scheduledMessages.value[token]) {
|
||||
scheduledMessages.value[token] = {}
|
||||
}
|
||||
|
||||
// Patch scheduled messages into ChatMessage format to be able to show in the chat
|
||||
response.data.ocs.data.forEach((message) => {
|
||||
scheduledMessages.value[token][message.id] = message
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error while fetching scheduled messages:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a message to be posted with given payload
|
||||
*
|
||||
* @param token - conversation token
|
||||
* @param payload - action payload
|
||||
*/
|
||||
async function scheduleMessage(token: string, payload: scheduleMessageParams) {
|
||||
try {
|
||||
const response = await scheduleMessageApi({ token, ...payload })
|
||||
if (!scheduledMessages.value[token]) {
|
||||
scheduledMessages.value[token] = {}
|
||||
}
|
||||
scheduledMessages.value[token][response.data.ocs.data.id] = response.data.ocs.data
|
||||
|
||||
await vuexStore.dispatch('setConversationProperties', {
|
||||
token,
|
||||
properties: {
|
||||
hasScheduledMessages: true,
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error while scheduling message:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit already scheduled message with given payload
|
||||
*
|
||||
* @param token - conversation token
|
||||
* @param messageId - id of message to edit
|
||||
* @param payload - action payload
|
||||
*/
|
||||
async function editScheduledMessage(token: string, messageId: string, payload: editScheduledMessageParams) {
|
||||
try {
|
||||
const response = await editScheduledMessageApi({ token, messageId, ...payload })
|
||||
scheduledMessages.value[token][messageId] = response.data.ocs.data
|
||||
} catch (e) {
|
||||
console.error('Error while editing scheduled message:', e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete already scheduled message
|
||||
*
|
||||
* @param token - conversation token
|
||||
* @param messageId - id of message to delete
|
||||
*/
|
||||
async function deleteScheduledMessage(token: string, messageId: string) {
|
||||
try {
|
||||
await deleteScheduledMessageApi(token, messageId)
|
||||
|
||||
delete scheduledMessages.value[token][messageId]
|
||||
|
||||
// Check if there are any scheduled messages left
|
||||
if (Object.keys(scheduledMessages.value[token] ?? {}).length === 0) {
|
||||
await vuexStore.dispatch('setConversationProperties', {
|
||||
token,
|
||||
properties: {
|
||||
hasScheduledMessages: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error while deleting scheduled message:', e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
threads,
|
||||
followedThreads,
|
||||
|
|
@ -622,6 +778,7 @@ export const useChatExtrasStore = defineStore('chatExtras', () => {
|
|||
tasksCount,
|
||||
tasksDoneCount,
|
||||
chatSummary,
|
||||
scheduledMessages,
|
||||
|
||||
followedThreadsList,
|
||||
|
||||
|
|
@ -634,6 +791,8 @@ export const useChatExtrasStore = defineStore('chatExtras', () => {
|
|||
getChatSummaryTaskQueue,
|
||||
hasChatSummaryTaskRequested,
|
||||
getChatSummary,
|
||||
getScheduledMessagesList,
|
||||
getScheduledMessage,
|
||||
|
||||
addThread,
|
||||
fetchSingleThread,
|
||||
|
|
@ -662,5 +821,9 @@ export const useChatExtrasStore = defineStore('chatExtras', () => {
|
|||
requestChatSummary,
|
||||
storeChatSummary,
|
||||
dismissChatSummary,
|
||||
fetchScheduledMessages,
|
||||
scheduleMessage,
|
||||
editScheduledMessage,
|
||||
deleteScheduledMessage,
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ export type TokenMap<T> = Record<string, T>
|
|||
export type IdMap<T> = Record<number | string, T>
|
||||
export type TokenIdMap<T> = TokenMap<IdMap<T>>
|
||||
|
||||
type BigInt<T, K extends PropertyKey = 'id'>
|
||||
= K extends keyof T
|
||||
? Omit<T, K> & Record<K, string>
|
||||
: T & Record<K, string>
|
||||
|
||||
type SpreedCapabilities = components['schemas']['Capabilities']
|
||||
|
||||
// From https://github.com/nextcloud/password_policy/blob/master/lib/Capabilities.php
|
||||
|
|
@ -250,11 +255,16 @@ export type File = RichObject<'size' | 'path' | 'link' | 'mimetype' | 'preview-a
|
|||
height: string
|
||||
}
|
||||
export type ChatMessage = components['schemas']['ChatMessageWithParent']
|
||||
/* This supports Snowflake ID */
|
||||
export type BigIntChatMessage = BigInt<ChatMessage>
|
||||
|
||||
export type ChatTask = SummarizeChatTask & {
|
||||
fromMessageId: number
|
||||
summary?: string
|
||||
}
|
||||
|
||||
export type ScheduledMessage = components['schemas']['ScheduledMessage']
|
||||
|
||||
export type receiveMessagesParams = operations['chat-receive-messages']['parameters']['query']
|
||||
export type receiveMessagesResponse = ApiResponse<operations['chat-receive-messages']['responses'][200]['content']['application/json']>
|
||||
export type getMessageContextParams = operations['chat-get-message-context']['parameters']['query']
|
||||
|
|
@ -276,6 +286,13 @@ export type SummarizeChatTask = operations['chat-summarize-chat']['responses'][2
|
|||
export type upcomingRemindersResponse = ApiResponse<operations['chat-get-upcoming-reminders']['responses'][200]['content']['application/json']>
|
||||
export type UpcomingReminder = components['schemas']['ChatReminderUpcoming']
|
||||
|
||||
export type getScheduledMessagesResponse = ApiResponse<operations['chat-get-scheduled-messages']['responses'][200]['content']['application/json']>
|
||||
export type scheduleMessageParams = operations['chat-schedule-message']['requestBody']['content']['application/json']
|
||||
export type scheduleMessageResponse = ApiResponse<operations['chat-schedule-message']['responses'][201]['content']['application/json']>
|
||||
export type editScheduledMessageParams = operations['chat-edit-scheduled-message']['requestBody']['content']['application/json']
|
||||
export type editScheduledMessageResponse = ApiResponse<operations['chat-edit-scheduled-message']['responses'][202]['content']['application/json']>
|
||||
export type deleteScheduledMessageResponse = ApiResponse<operations['chat-delete-schedule-message']['responses'][200]['content']['application/json']>
|
||||
|
||||
// Threads
|
||||
export type Thread = components['schemas']['Thread']
|
||||
export type ThreadAttendee = components['schemas']['ThreadAttendee']
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue