Browse Source

fix(files): auto reload sidebar tags on update

Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
pull/53544/head
skjnldsv 6 months ago
parent
commit
63907b0bfd
  1. 16
      apps/files/src/services/WebdavClient.ts
  2. 89
      apps/systemtags/src/components/SystemTags.vue
  3. 85
      cypress/e2e/systemtags/files-inline-action.cy.ts

16
apps/files/src/services/WebdavClient.ts

@ -2,6 +2,18 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later * SPDX-License-Identifier: AGPL-3.0-or-later
*/ */
import { davGetClient } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed } from 'webdav'
import type { Node } from '@nextcloud/files'
export const client = davGetClient()
import { getClient, getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav'
export const client = getClient()
export const fetchNode = async (path: string): Promise<Node> => {
const propfindPayload = getDefaultPropfind()
const result = await client.stat(`${getRootPath()}${path}`, {
details: true,
data: propfindPayload,
}) as ResponseDataDetailed<FileStat>
return resultToNode(result.data)
}

89
apps/systemtags/src/components/SystemTags.vue

@ -8,42 +8,48 @@
<NcLoadingIcon v-if="loadingTags" <NcLoadingIcon v-if="loadingTags"
:name="t('systemtags', 'Loading collaborative tags …')" :name="t('systemtags', 'Loading collaborative tags …')"
:size="32" /> :size="32" />
<template v-else>
<NcSelectTags class="system-tags__select"
:input-label="t('systemtags', 'Search or create collaborative tags')"
:placeholder="t('systemtags', 'Collaborative tags …')"
:options="sortedTags"
:value="selectedTags"
:create-option="createOption"
:disabled="disabled"
:taggable="true"
:passthru="true"
:fetch-tags="false"
:loading="loading"
@input="handleInput"
@option:selected="handleSelect"
@option:created="handleCreate"
@option:deselected="handleDeselect">
<template #no-options>
{{ t('systemtags', 'No tags to select, type to create a new tag') }}
</template>
</NcSelectTags>
</template>
<NcSelectTags v-show="!loadingTags"
class="system-tags__select"
:input-label="t('systemtags', 'Search or create collaborative tags')"
:placeholder="t('systemtags', 'Collaborative tags …')"
:options="sortedTags"
:value="selectedTags"
:create-option="createOption"
:disabled="disabled"
:taggable="true"
:passthru="true"
:fetch-tags="false"
:loading="loading"
@input="handleInput"
@option:selected="handleSelect"
@option:created="handleCreate"
@option:deselected="handleDeselect">
<template #no-options>
{{ t('systemtags', 'No tags to select, type to create a new tag') }}
</template>
</NcSelectTags>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
// FIXME Vue TypeScript ESLint errors // FIXME Vue TypeScript ESLint errors
/* eslint-disable */ /* eslint-disable */
import type { Node } from '@nextcloud/files'
import type { Tag, TagWithId } from '../types.js'
import Vue from 'vue' import Vue from 'vue'
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js' import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
import NcSelectTags from '@nextcloud/vue/dist/Components/NcSelectTags.js' import NcSelectTags from '@nextcloud/vue/dist/Components/NcSelectTags.js'
import { translate as t } from '@nextcloud/l10n'
import { emit, subscribe } from '@nextcloud/event-bus'
import { showError } from '@nextcloud/dialogs' import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { defaultBaseTag } from '../utils.js' import { defaultBaseTag } from '../utils.js'
import { fetchLastUsedTagIds, fetchTags } from '../services/api.js' import { fetchLastUsedTagIds, fetchTags } from '../services/api.js'
import { fetchNode } from '../../../files/src/services/WebdavClient.js'
import { logger } from '../logger.ts'
import { import {
createTagForFile, createTagForFile,
deleteTagForFile, deleteTagForFile,
@ -51,8 +57,6 @@ import {
setTagForFile, setTagForFile,
} from '../services/files.js' } from '../services/files.js'
import type { Tag, TagWithId } from '../types.js'
export default Vue.extend({ export default Vue.extend({
name: 'SystemTags', name: 'SystemTags',
@ -123,6 +127,10 @@ export default Vue.extend({
}, },
}, },
mounted() {
subscribe('systemtags:node:updated', this.onTagUpdated)
},
methods: { methods: {
t, t,
@ -177,6 +185,8 @@ export default Vue.extend({
showError(t('systemtags', 'Failed to select tag')) showError(t('systemtags', 'Failed to select tag'))
} }
this.loading = false this.loading = false
this.updateAndDispatchNodeTagsEvent(this.fileId)
}, },
async handleCreate(tag: Tag) { async handleCreate(tag: Tag) {
@ -190,6 +200,8 @@ export default Vue.extend({
showError(t('systemtags', 'Failed to create tag')) showError(t('systemtags', 'Failed to create tag'))
} }
this.loading = false this.loading = false
this.updateAndDispatchNodeTagsEvent(this.fileId)
}, },
async handleDeselect(tag: TagWithId) { async handleDeselect(tag: TagWithId) {
@ -200,6 +212,35 @@ export default Vue.extend({
showError(t('systemtags', 'Failed to delete tag')) showError(t('systemtags', 'Failed to delete tag'))
} }
this.loading = false this.loading = false
this.updateAndDispatchNodeTagsEvent(this.fileId)
},
async onTagUpdated(node: Node) {
if (node.fileid !== this.fileId) {
return
}
this.loadingTags = true
try {
this.selectedTags = await fetchTagsForFile(this.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to load selected tags'))
}
this.loadingTags = false
},
async updateAndDispatchNodeTagsEvent(fileId: number) {
const path = window.OCA?.Files?.Sidebar?.file || ''
try {
const node = await fetchNode(path)
if (node) {
emit('systemtags:node:updated', node)
}
} catch (error) {
logger.error('Failed to fetch node for system tags update', { error, fileId })
}
}, },
}, },
}) })

85
cypress/e2e/systemtags/files-inline-action.cy.ts

@ -22,8 +22,10 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
it('See first assigned tag in the file list', () => { it('See first assigned tag in the file list', () => {
const tag = randomBytes(8).toString('base64') const tag = randomBytes(8).toString('base64')
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
getRowForFile('file.txt').should('be.visible') getRowForFile('file.txt').should('be.visible')
triggerActionForFile('file.txt', 'details') triggerActionForFile('file.txt', 'details')
cy.wait('@getNode')
cy.get('[data-cy-sidebar]') cy.get('[data-cy-sidebar]')
.should('be.visible') .should('be.visible')
@ -36,13 +38,14 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
.click() .click()
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag') cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
cy.get('[data-cy-sidebar]')
.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag}{enter}`)
getCollaborativeTagsInput()
.type(`{selectAll}${tag}{enter}`)
cy.wait('@assignTag') cy.wait('@assignTag')
closeSidebar()
cy.wait('@getNode')
// Close the sidebar and reload to check the file list
closeSidebar()
cy.reload() cy.reload()
getRowForFile('file.txt') getRowForFile('file.txt')
@ -56,8 +59,10 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
const tag1 = randomBytes(5).toString('base64') const tag1 = randomBytes(5).toString('base64')
const tag2 = randomBytes(5).toString('base64') const tag2 = randomBytes(5).toString('base64')
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
getRowForFile('file.txt').should('be.visible') getRowForFile('file.txt').should('be.visible')
triggerActionForFile('file.txt', 'details') triggerActionForFile('file.txt', 'details')
cy.wait('@getNode')
cy.get('[data-cy-sidebar]') cy.get('[data-cy-sidebar]')
.should('be.visible') .should('be.visible')
@ -70,17 +75,20 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
.click() .click()
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag') cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
cy.get('[data-cy-sidebar]').within(() => {
cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag1}{enter}`)
cy.wait('@assignTag')
cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag2}{enter}`)
cy.wait('@assignTag')
})
// Assign first tag
getCollaborativeTagsInput()
.type(`{selectAll}${tag1}{enter}`)
cy.wait('@assignTag')
cy.wait('@getNode')
// Assign second tag
getCollaborativeTagsInput()
.type(`{selectAll}${tag2}{enter}`)
cy.wait('@assignTag')
cy.wait('@getNode')
// Close the sidebar and reload to check the file list
closeSidebar() closeSidebar()
cy.reload() cy.reload()
@ -97,11 +105,10 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
const tag2 = randomBytes(4).toString('base64') const tag2 = randomBytes(4).toString('base64')
const tag3 = randomBytes(4).toString('base64') const tag3 = randomBytes(4).toString('base64')
cy.intercept('PROPFIND', `**/remote.php/dav/files/${user.userId}/file.txt`).as('getNode')
getRowForFile('file.txt').should('be.visible') getRowForFile('file.txt').should('be.visible')
cy.intercept('PROPFIND', '**/remote.php/dav/**').as('sidebarLoaded')
triggerActionForFile('file.txt', 'details') triggerActionForFile('file.txt', 'details')
cy.wait('@sidebarLoaded')
cy.wait('@getNode')
cy.get('[data-cy-sidebar]') cy.get('[data-cy-sidebar]')
.should('be.visible') .should('be.visible')
@ -114,23 +121,26 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
.click() .click()
cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag') cy.intercept('PUT', '**/remote.php/dav/systemtags-relations/files/**').as('assignTag')
cy.get('[data-cy-sidebar]').within(() => {
cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag1}{enter}`)
cy.wait('@assignTag')
cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag2}{enter}`)
cy.wait('@assignTag')
cy.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.type(`${tag3}{enter}`)
cy.wait('@assignTag')
})
// Assign first tag
getCollaborativeTagsInput()
.type(`{selectAll}${tag1}{enter}`)
cy.wait('@assignTag')
cy.wait('@getNode')
// Assign second tag
getCollaborativeTagsInput()
.type(`{selectAll}${tag2}{enter}`)
cy.wait('@assignTag')
cy.wait('@getNode')
// Assign third tag
getCollaborativeTagsInput()
.type(`{selectAll}${tag3}{enter}`)
cy.wait('@assignTag')
cy.wait('@getNode')
// Close the sidebar and reload to check the file list
closeSidebar() closeSidebar()
cy.reload() cy.reload()
@ -153,3 +163,10 @@ describe('Systemtags: Files integration', { testIsolation: true }, () => {
}) })
}) })
}) })
function getCollaborativeTagsInput(): Cypress.Chainable<JQuery<HTMLElement>> {
return cy.get('[data-cy-sidebar]')
.findByRole('combobox', { name: /collaborative tags/i })
.should('be.visible')
.should('not.have.attr', 'disabled', { timeout: 5000 })
}
Loading…
Cancel
Save