Skip to content

Commit

Permalink
Merge pull request #14007 from nextcloud/fix/noid/groupware-store
Browse files Browse the repository at this point in the history
fix(integrations): move groupware-related API to dedicated store
  • Loading branch information
Antreesy authored Dec 17, 2024
2 parents ee6b4ec + 0c60eb1 commit d650265
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 163 deletions.
8 changes: 5 additions & 3 deletions src/components/NewMessage/NewMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManage
import { EventBus } from '../../services/EventBus.ts'
import { shareFile } from '../../services/filesSharingServices.js'
import { useChatExtrasStore } from '../../stores/chatExtras.js'
import { useGroupwareStore } from '../../stores/groupware.ts'
import { useSettingsStore } from '../../stores/settings.js'
import { fetchClipboardContent } from '../../utils/clipboard.js'
import { parseSpecialSymbols } from '../../utils/textParse.ts'
Expand Down Expand Up @@ -312,6 +313,7 @@ export default {
const { createTemporaryMessage } = useTemporaryMessage()
return {
chatExtrasStore: useChatExtrasStore(),
groupwareStore: useGroupwareStore(),
settingsStore: useSettingsStore(),
supportTypingStatus,
autoComplete,
Expand Down Expand Up @@ -474,7 +476,7 @@ export default {
},

userAbsence() {
return this.chatExtrasStore.absence[this.token]
return this.groupwareStore.absence[this.token]
},

showChatSummary() {
Expand Down Expand Up @@ -988,13 +990,13 @@ export default {
// TODO replace with status message id 'vacationing'
if (this.conversation.status === 'dnd') {
// Fetch actual absence status from server
await this.chatExtrasStore.getUserAbsence({
await this.groupwareStore.getUserAbsence({
token: this.token,
userId: this.conversation.name,
})
} else {
// Remove stored absence status
this.chatExtrasStore.removeUserAbsence(this.token)
this.groupwareStore.removeUserAbsence(this.token)
}
},

Expand Down
8 changes: 4 additions & 4 deletions src/components/TopBar/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ import ConversationIcon from '../ConversationIcon.vue'
import { useGetParticipants } from '../../composables/useGetParticipants.js'
import { AVATAR, CONVERSATION } from '../../constants.js'
import { getTalkConfig } from '../../services/CapabilitiesManager.ts'
import { useChatExtrasStore } from '../../stores/chatExtras.js'
import { useGroupwareStore } from '../../stores/groupware.ts'
import { useSidebarStore } from '../../stores/sidebar.js'
import { getStatusMessage } from '../../utils/userStatus.ts'
import { localCallParticipantModel, localMediaModel } from '../../utils/webrtc/index.js'
Expand Down Expand Up @@ -193,7 +193,7 @@ export default {
AVATAR,
localCallParticipantModel,
localMediaModel,
chatExtrasStore: useChatExtrasStore(),
groupwareStore: useGroupwareStore(),
sidebarStore: useSidebarStore(),
isMobile: useIsMobile(),
CONVERSATION,
Expand Down Expand Up @@ -284,7 +284,7 @@ export default {
},

nextEvent() {
return this.chatExtrasStore.getNextEvent(this.token)
return this.groupwareStore.getNextEvent(this.token)
},

eventInfo() {
Expand Down Expand Up @@ -318,7 +318,7 @@ export default {
// Do not fetch upcoming events for guests (401 unauthorzied) or in sidebar
return
}
this.chatExtrasStore.getUpcomingEvents(value)
this.groupwareStore.getUpcomingEvents(value)
}
},
},
Expand Down
14 changes: 0 additions & 14 deletions src/services/conversationsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,6 @@ const fetchConversations = async function(options) {
return axios.get(generateOcsUrl('apps/spreed/api/v4/room'), options)
}

/**
* fetch future events for a given conversation within the next 31 days.
*
* @param {string} location room's absolute url
*/
const getUpcomingEvents = async (location) => {
return axios.get(generateOcsUrl('/apps/dav/api/v1/events/upcoming'), {
params: {
location,
},
})
}

/**
* Fetches a conversation from the server.
*
Expand Down Expand Up @@ -356,7 +343,6 @@ export {
fetchConversations,
fetchConversation,
fetchNoteToSelfConversation,
getUpcomingEvents,
searchListedConversations,
createOneToOneConversation,
createGroupConversation,
Expand Down
11 changes: 0 additions & 11 deletions src/services/coreService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { generateOcsUrl } from '@nextcloud/router'
import { getTalkConfig, hasTalkFeature } from './CapabilitiesManager.ts'
import { SHARE } from '../constants.js'
import type {
OutOfOfficeResponse,
TaskProcessingResponse,
} from '../types/index.ts'

Expand Down Expand Up @@ -64,18 +63,8 @@ const deleteTaskById = async function(id: number, options?: object): Promise<nul
return axios.delete(generateOcsUrl('taskprocessing/task/{id}', { id }), options)
}

/**
* Get absence information for a user (in a given 1-1 conversation).
*
* @param userId user id
*/
const getUserAbsence = async (userId: string): OutOfOfficeResponse => {
return axios.get(generateOcsUrl('/apps/dav/api/v1/outOfOffice/{userId}/now', { userId }))
}

export {
autocompleteQuery,
getTaskById,
deleteTaskById,
getUserAbsence,
}
37 changes: 37 additions & 0 deletions src/services/groupwareService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* SPDX-FileCopyrightText: 2024 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 {
OutOfOfficeResponse,
UpcomingEventsResponse,
} from '../types/index.ts'

/**
* Get upcoming events for a given conversation within the next 31 days.
* @param location conversation's absolute URL
*/
const getUpcomingEvents = async (location: string): UpcomingEventsResponse => {
return axios.get(generateOcsUrl('/apps/dav/api/v1/events/upcoming'), {
params: {
location,
},
})
}

/**
* Get absence information for a user (in a given 1-1 conversation).
* @param userId user id
*/
const getUserAbsence = async (userId: string): OutOfOfficeResponse => {
return axios.get(generateOcsUrl('/apps/dav/api/v1/outOfOffice/{userId}/now', { userId }))
}

export {
getUpcomingEvents,
getUserAbsence,
}
3 changes: 3 additions & 0 deletions src/store/conversationsStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { talkBroadcastChannel } from '../services/talkBroadcastChannel.js'
import { useBreakoutRoomsStore } from '../stores/breakoutRooms.ts'
import { useChatExtrasStore } from '../stores/chatExtras.js'
import { useFederationStore } from '../stores/federation.ts'
import { useGroupwareStore } from '../stores/groupware.ts'
import { useReactionsStore } from '../stores/reactions.js'
import { useTalkHashStore } from '../stores/talkHash.js'

Expand Down Expand Up @@ -370,6 +371,8 @@ const actions = {
// FIXME: rename to deleteConversationsFromStore or a better name
const chatExtrasStore = useChatExtrasStore()
chatExtrasStore.purgeChatExtras(token)
const groupwareStore = useGroupwareStore()
groupwareStore.purgeGroupwareStore(token)
const reactionsStore = useReactionsStore()
reactionsStore.purgeReactionsStore(token)
context.dispatch('purgeMessagesStore', token)
Expand Down
64 changes: 0 additions & 64 deletions src/stores/__tests__/chatExtras.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,11 @@
import { setActivePinia, createPinia } from 'pinia'

import BrowserStorage from '../../services/BrowserStorage.js'
import { getUserAbsence } from '../../services/coreService.ts'
import { EventBus } from '../../services/EventBus.ts'
import { generateOCSErrorResponse, generateOCSResponse } from '../../test-helpers.js'
import { useChatExtrasStore } from '../chatExtras.js'

jest.mock('../../services/coreService', () => ({
getUserAbsence: jest.fn(),
}))

describe('chatExtrasStore', () => {
const token = 'TOKEN'
const userId = 'alice'
const payload = { id: 1, userId: 'alice', firstDay: '2023-11-15', lastDay: '2023-11-17', status: 'absence status', message: 'absence message' }
let chatExtrasStore

beforeEach(async () => {
Expand All @@ -29,57 +21,6 @@ describe('chatExtrasStore', () => {
jest.clearAllMocks()
})

describe('absence status', () => {
it('processes a response from server and stores absence status', async () => {
// Arrange
const response = generateOCSResponse({ payload })
getUserAbsence.mockResolvedValueOnce(response)

// Act
await chatExtrasStore.getUserAbsence({ token, userId })

// Assert
expect(getUserAbsence).toHaveBeenCalledWith(userId)
expect(chatExtrasStore.absence[token]).toEqual(payload)
})

it('does not show error if absence status is not found', async () => {
// Arrange
const errorNotFound = generateOCSErrorResponse({ payload: null, status: 404 })
const errorOther = generateOCSErrorResponse({ payload: null, status: 500 })
getUserAbsence
.mockRejectedValueOnce(errorNotFound)
.mockRejectedValueOnce(errorOther)
console.error = jest.fn()

// Act
await chatExtrasStore.getUserAbsence({ token, userId })
await chatExtrasStore.getUserAbsence({ token, userId })

// Assert
expect(getUserAbsence).toHaveBeenCalledTimes(2)
expect(console.error).toHaveBeenCalledTimes(1)
expect(chatExtrasStore.absence[token]).toEqual(null)
})

it('removes absence status from the store', async () => {
// Arrange
const response = generateOCSResponse({ payload })
getUserAbsence.mockResolvedValueOnce(response)
const token2 = 'TOKEN_2'

// Act
await chatExtrasStore.getUserAbsence({ token, userId })
chatExtrasStore.removeUserAbsence(token)
chatExtrasStore.removeUserAbsence(token2)

// Assert
expect(chatExtrasStore.absence[token]).not.toBeDefined()
expect(chatExtrasStore.absence[token2]).not.toBeDefined()
})

})

describe('reply message', () => {
it('adds reply message id to the store', () => {
// Act
Expand Down Expand Up @@ -171,18 +112,13 @@ describe('chatExtrasStore', () => {
describe('purge store', () => {
it('clears store for provided token', async () => {
// Arrange
const response = generateOCSResponse({ payload })
getUserAbsence.mockResolvedValueOnce(response)

await chatExtrasStore.getUserAbsence({ token: 'token-1', userId })
chatExtrasStore.setParentIdToReply({ token: 'token-1', id: 101 })
chatExtrasStore.setChatInput({ token: 'token-1', text: 'message-1' })

// Act
chatExtrasStore.purgeChatExtras('token-1')

// Assert
expect(chatExtrasStore.absence['token-1']).not.toBeDefined()
expect(chatExtrasStore.parentToReply['token-1']).not.toBeDefined()
expect(chatExtrasStore.chatInput['token-1']).not.toBeDefined()
})
Expand Down
95 changes: 95 additions & 0 deletions src/stores/__tests__/groupware.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { setActivePinia, createPinia } from 'pinia'

import { getUserAbsence } from '../../services/groupwareService.ts'
import { generateOCSErrorResponse, generateOCSResponse } from '../../test-helpers.js'
import { useGroupwareStore } from '../groupware.ts'

jest.mock('../../services/groupwareService', () => ({
getUserAbsence: jest.fn(),
}))

describe('groupwareStore', () => {
const token = 'TOKEN'
const userId = 'alice'
const payload = { id: 1, userId: 'alice', firstDay: '2023-11-15', lastDay: '2023-11-17', status: 'absence status', message: 'absence message' }
let groupwareStore

beforeEach(async () => {
setActivePinia(createPinia())
groupwareStore = useGroupwareStore()
})

afterEach(async () => {
jest.clearAllMocks()
})

describe('absence status', () => {
it('processes a response from server and stores absence status', async () => {
// Arrange
const response = generateOCSResponse({ payload })
getUserAbsence.mockResolvedValueOnce(response)

// Act
await groupwareStore.getUserAbsence({ token, userId })

// Assert
expect(getUserAbsence).toHaveBeenCalledWith(userId)
expect(groupwareStore.absence[token]).toEqual(payload)
})

it('does not show error if absence status is not found', async () => {
// Arrange
const errorNotFound = generateOCSErrorResponse({ payload: null, status: 404 })
const errorOther = generateOCSErrorResponse({ payload: null, status: 500 })
getUserAbsence
.mockRejectedValueOnce(errorNotFound)
.mockRejectedValueOnce(errorOther)
console.error = jest.fn()

// Act
await groupwareStore.getUserAbsence({ token, userId })
await groupwareStore.getUserAbsence({ token, userId })

// Assert
expect(getUserAbsence).toHaveBeenCalledTimes(2)
expect(console.error).toHaveBeenCalledTimes(1)
expect(groupwareStore.absence[token]).toEqual(null)
})

it('removes absence status from the store', async () => {
// Arrange
const response = generateOCSResponse({ payload })
getUserAbsence.mockResolvedValueOnce(response)
const token2 = 'TOKEN_2'

// Act
await groupwareStore.getUserAbsence({ token, userId })
groupwareStore.removeUserAbsence(token)
groupwareStore.removeUserAbsence(token2)

// Assert
expect(groupwareStore.absence[token]).not.toBeDefined()
expect(groupwareStore.absence[token2]).not.toBeDefined()
})
})

describe('purge store', () => {
it('clears store for provided token', async () => {
// Arrange
const response = generateOCSResponse({ payload })
getUserAbsence.mockResolvedValueOnce(response)

await groupwareStore.getUserAbsence({ token: 'token-1', userId })

// Act
groupwareStore.purgeGroupwareStore('token-1')

// Assert
expect(groupwareStore.absence['token-1']).not.toBeDefined()
})
})
})
Loading

0 comments on commit d650265

Please sign in to comment.