Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove password logic form modal implementation #10189

Merged
merged 1 commit into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 1 addition & 30 deletions packages/design-system/src/components/OcModal/OcModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,10 @@
:placeholder="inputPlaceholder"
:label="inputLabel"
:type="inputType"
:password-policy="inputPasswordPolicy"
:generate-password-method="inputGeneratePasswordMethod"
:description-message="inputDescription"
:disabled="inputDisabled"
:fix-message-line="true"
:selection-range="inputSelectionRange"
@password-challenge-completed="$emit('passwordChallengeCompleted')"
@password-challenge-failed="$emit('passwordChallengeFailed')"
@update:model-value="inputOnInput"
@keydown.enter.prevent="confirm"
/>
Expand Down Expand Up @@ -107,7 +103,6 @@ import OcIcon from '../OcIcon/OcIcon.vue'
import OcTextInput from '../OcTextInput/OcTextInput.vue'
import { FocusTrap } from 'focus-trap-vue'
import { FocusTargetOrFalse, FocusTargetValueOrFalse } from 'focus-trap'
import { PasswordPolicy } from '../../helpers'

/**
* Modals are generally used to force the user to focus on confirming or completing a single action.
Expand Down Expand Up @@ -364,22 +359,6 @@ export default defineComponent({
required: false,
default: false
},
/**
* Password policy for the input
*/
inputPasswordPolicy: {
type: Object as PropType<PasswordPolicy>,
required: false,
default: () => ({})
},
/**
* Method to generate random password for the input
*/
inputGeneratePasswordMethod: {
type: Function as PropType<(...args: unknown[]) => string>,
required: false,
default: null
},
/**
* Overwrite default focused element
* Can be `#id, .class`.
Expand All @@ -397,15 +376,7 @@ export default defineComponent({
default: false
}
},
emits: [
'cancel',
'confirm',
'confirm-secondary',
'input',
'checkbox-changed',
'passwordChallengeCompleted',
'passwordChallengeFailed'
],
emits: ['cancel', 'confirm', 'confirm-secondary', 'input', 'checkbox-changed'],
setup() {
const primaryButton = ref(null)
const secondaryButton = ref(null)
Expand Down
106 changes: 106 additions & 0 deletions packages/web-app-files/src/components/Modals/SetLinkPasswordModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<template>
<oc-text-input
:model-value="password"
:label="$gettext('Password')"
type="password"
:password-policy="inputPasswordPolicy"
:generate-password-method="inputGeneratePasswordMethod"
:fix-message-line="true"
:placeholder="link.password ? '●●●●●●●●' : null"
:error-message="errorMessage"
class="oc-modal-body-input"
@password-challenge-completed="confirmDisabled = false"
@password-challenge-failed="confirmDisabled = true"
@keydown.enter.prevent="onConfirm"
@update:model-value="onInput"
/>
<div class="oc-flex oc-flex-right oc-flex-middle oc-mt-m">
<oc-button
class="oc-modal-body-actions-cancel oc-ml-s"
appearance="outline"
variation="passive"
@click="onCancel"
>{{ $gettext('Cancel') }}
</oc-button>
<oc-button
class="oc-modal-body-actions-confirm oc-ml-s"
appearance="filled"
variation="primary"
:disabled="confirmDisabled"
@click="onConfirm"
>{{ link.password ? $gettext('Apply') : $gettext('Set') }}
</oc-button>
</div>
</template>

<script lang="ts">
import { defineComponent, ref, unref, PropType } from 'vue'
import { useGettext } from 'vue3-gettext'
import { useClientService, usePasswordPolicyService, useStore } from '@ownclouders/web-pkg'
import { Share } from '@ownclouders/web-client/src/helpers'

export default defineComponent({
name: 'SetLinkPasswordModal',
props: {
link: { type: Object as PropType<Share>, required: true }
},
setup(props, { expose }) {
const store = useStore()
const clientService = useClientService()
const passwordPolicyService = usePasswordPolicyService()
const { $gettext } = useGettext()

const password = ref('')
const errorMessage = ref<string>()
const confirmDisabled = ref(true)

const onInput = (value: string) => {
password.value = value
errorMessage.value = undefined
}

const onConfirm = async () => {
try {
await store.dispatch('Files/updateLink', {
id: props.link.id,
client: clientService.owncloudSdk,
params: { ...props.link, password: unref(password) }
})
store.dispatch('hideModal')
store.dispatch('showMessage', {
title: $gettext('Link was updated successfully')
})
} catch (e) {
// Human-readable error message is provided, for example when password is on banned list
if (e.statusCode === 400) {
errorMessage.value = $gettext(e.message)
return
}

store.dispatch('showErrorMessage', {
title: $gettext('Failed to update link'),
error: e
})
}
}

const onCancel = () => {
store.dispatch('hideModal')
}

expose({ onConfirm, onCancel })

return {
password,
confirmDisabled,
onInput,
onConfirm,
onCancel,
errorMessage,
passwordPolicyService,
inputPasswordPolicy: passwordPolicyService.getPolicy(),
inputGeneratePasswordMethod: () => passwordPolicyService.generatePassword()
}
}
})
</script>
33 changes: 9 additions & 24 deletions packages/web-app-files/src/components/SideBar/Shares/FileLinks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ import {
useCapabilityFilesSharingPublicAlias,
useCapabilityFilesSharingPublicPasswordEnforcedFor,
useAbility,
usePasswordPolicyService,
useExpirationRules,
useDefaultLinkPermissions,
useFileActionsCreateLink,
Expand All @@ -143,6 +142,7 @@ import {
import DetailsAndEdit from './Links/DetailsAndEdit.vue'
import NameAndCopy from './Links/NameAndCopy.vue'
import CreateQuickLink from './Links/CreateQuickLink.vue'
import SetLinkPasswordModal from '../../Modals/SetLinkPasswordModal.vue'
import { getLocaleFromLanguage } from '@ownclouders/web-pkg'
import {
Resource,
Expand All @@ -169,7 +169,6 @@ export default defineComponent({
const clientService = useClientService()
const { can } = ability
const { expirationRules } = useExpirationRules()
const passwordPolicyService = usePasswordPolicyService()
const hasResharing = useCapabilityFilesSharingResharing()
const hasShareJail = useCapabilityShareJailEnabled()
const { defaultLinkPermissions } = useDefaultLinkPermissions()
Expand Down Expand Up @@ -269,28 +268,14 @@ export default defineComponent({
}
}

const showQuickLinkPasswordModal = (params) => {
const modal = {
const showPasswordModal = (params) => {
return store.dispatch('createModal', {
variation: 'passive',
title: $gettext('Set password'),
cancelText: $gettext('Cancel'),
confirmText: $gettext('Set'),
hasInput: true,
inputDescription: $gettext('Passwords for links are required.'),
inputPasswordPolicy: passwordPolicyService.getPolicy(),
inputGeneratePasswordMethod: () => passwordPolicyService.generatePassword(),
inputLabel: $gettext('Password'),
inputType: 'password',
onInput: () => store.dispatch('setModalInputErrorMessage', ''),
onPasswordChallengeCompleted: () => store.dispatch('setModalConfirmButtonDisabled', false),
onPasswordChallengeFailed: () => store.dispatch('setModalConfirmButtonDisabled', true),
onCancel: () => store.dispatch('hideModal'),
onConfirm: (newPassword: string) => {
return updatePublicLink({ params: { ...params, password: newPassword } })
}
}

return store.dispatch('createModal', modal)
hideActions: true,
customComponent: SetLinkPasswordModal,
customComponentAttrs: { link: params }
})
}

return {
Expand All @@ -316,7 +301,7 @@ export default defineComponent({
canEditLink,
expirationRules,
updatePublicLink,
showQuickLinkPasswordModal,
showPasswordModal,
defaultLinkPermissions,
addNewLink
}
Expand Down Expand Up @@ -465,7 +450,7 @@ export default defineComponent({
}

if (!link.password && !this.canDeletePublicLinkPassword(link)) {
this.showQuickLinkPasswordModal(params)
this.showPasswordModal(params)
} else {
this.updatePublicLink({ params })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ import * as EmailValidator from 'email-validator'
import {
createLocationSpaces,
useConfigurationManager,
LinkRoleDropdown
LinkRoleDropdown,
useStore
} from '@ownclouders/web-pkg'
import {
linkRoleInternalFile,
Expand All @@ -170,6 +171,7 @@ import { createFileRouteOptions } from '@ownclouders/web-pkg'
import { OcDrop } from 'design-system/src/components'
import { usePasswordPolicyService, ExpirationRules } from '@ownclouders/web-pkg'
import { useGettext } from 'vue3-gettext'
import SetLinkPasswordModal from '../../../Modals/SetLinkPasswordModal.vue'

export default defineComponent({
name: 'DetailsAndEdit',
Expand Down Expand Up @@ -210,7 +212,8 @@ export default defineComponent({
},
emits: ['removePublicLink', 'updateLink'],
setup(props, { emit }) {
const { current } = useGettext()
const store = useStore()
const { $gettext, current } = useGettext()
const configurationManager = useConfigurationManager()
const passwordPolicyService = usePasswordPolicyService()

Expand All @@ -236,6 +239,16 @@ export default defineComponent({
})
}

const showPasswordModal = () => {
return store.dispatch('createModal', {
variation: 'passive',
title: props.link.password ? $gettext('Edit password') : $gettext('Add password'),
hideActions: true,
customComponent: SetLinkPasswordModal,
customComponentAttrs: { link: props.link }
})
}

return {
space: inject<Ref<SpaceResource>>('space'),
resource: inject<Ref<Resource>>('resource'),
Expand All @@ -244,7 +257,8 @@ export default defineComponent({
updateLink,
updateSelectedRole,
currentLinkRole,
isRunningOnEos: computed(() => configurationManager.options.isRunningOnEos)
isRunningOnEos: computed(() => configurationManager.options.isRunningOnEos),
showPasswordModal
}
},
data() {
Expand Down Expand Up @@ -503,38 +517,6 @@ export default defineComponent({
this.createModal(modal)
},

showPasswordModal() {
const modal = {
variation: 'passive',
title: this.link.password ? this.$gettext('Edit password') : this.$gettext('Add password'),
cancelText: this.$gettext('Cancel'),
confirmText: this.link.password ? this.$gettext('Apply') : this.$gettext('Set'),
hasInput: true,
confirmDisabled: true,
inputLabel: this.$gettext('Password'),
inputPasswordPolicy: this.passwordPolicyService.getPolicy(),
inputGeneratePasswordMethod: () => this.passwordPolicyService.generatePassword(),
inputPlaceholder: this.link.password ? '●●●●●●●●' : null,
inputType: 'password',
onCancel: this.hideModal,
onInput: () => this.setModalInputErrorMessage(''),
onPasswordChallengeCompleted: () => this.setModalConfirmButtonDisabled(false),
onPasswordChallengeFailed: () => this.setModalConfirmButtonDisabled(true),
onConfirm: (password) => {
this.updateLink({
link: {
...this.link,
password
},
onSuccess: () => {
this.hideModal()
}
})
}
}
this.createModal(modal)
},

toggleNotifyUploads() {
if (this.currentLinkNotifyUploads) {
this.$emit('updateLink', {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import SetLinkPasswordModal from '../../../../src/components/Modals/SetLinkPasswordModal.vue'
import {
createStore,
defaultComponentMocks,
defaultPlugins,
defaultStoreMockOptions,
shallowMount
} from 'web-test-helpers'

describe('SetLinkPasswordModal', () => {
it('should render a text input field for the password', () => {
const { wrapper } = getWrapper()

expect(wrapper.find('oc-text-input-stub').exists()).toBeTruthy()
})
describe('method "onConfirm"', () => {
it('updates the link', async () => {
const { wrapper, storeOptions } = getWrapper()
await wrapper.vm.onConfirm()

expect(storeOptions.modules.Files.actions.updateLink).toHaveBeenCalled()
expect(storeOptions.actions.showMessage).toHaveBeenCalled()
})
it('shows an error message on error', async () => {
const { wrapper, storeOptions } = getWrapper()
storeOptions.modules.Files.actions.updateLink.mockRejectedValue(new Error(''))
await wrapper.vm.onConfirm()

expect(storeOptions.actions.showErrorMessage).toHaveBeenCalled()
})
})
})

function getWrapper({ link = {} } = {}) {
const mocks = { ...defaultComponentMocks() }

const storeOptions = defaultStoreMockOptions
const store = createStore(storeOptions)

return {
mocks,
storeOptions,
wrapper: shallowMount(SetLinkPasswordModal, {
props: {
link
},
global: {
plugins: [...defaultPlugins(), store],
mocks,
provide: mocks
}
})
}
}
Loading