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

Ability to store & remove server owner & deployment entries in dropdowns #1503

89 changes: 74 additions & 15 deletions src/components/cylc/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
rules: [(val) => Boolean(val) || 'Required'],
}
}">
<!-- Owner combobox -->
<v-combobox
class="w-100"
id="cylc-owner-combobox"
Expand All @@ -55,8 +56,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:default="owner"
:items="Array.from(owners)"
v-model="owner"
@keyup.enter="owners.add(owner)"
/>
@keyup.enter="addOwner(owner)"
>
<template v-slot:item="{ item, props }">
<!-- HTML that describe how select should render items when the select is open -->
<v-list-item
:title="item.title"
v-bind="props"
>
<template v-slot:append v-if="item.title !== ownerOnLoad">
<v-icon
@click.stop="removeOwner(item.title)"
color="pink-accent-4"
:icon="mdiClose"
/>
</template>
</v-list-item>
</template>
</v-combobox>
<!-- Deployment combobox -->
<v-combobox
class="w-100"
id="cylc-deployment-combobox"
Expand All @@ -65,15 +83,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:default="deployment"
:items="Array.from(deployments)"
v-model="deployment"
@keyup.enter="deployments.add(deployment)"
/>
@keyup.enter="addDeployment(deployment)"
>
<template v-slot:item="{ item, props }">
<!-- HTML that describe how select should render items when the select is open -->
<v-list-item
:title="item.title"
v-bind="props"
>
<template v-slot:append v-if="item.title !== deploymentOnLoad">
<v-icon
@click.stop="removeDeployment(item.title)"
color="pink-accent-4"
:icon="mdiClose"
/>
</template>
</v-list-item>
</template>
</v-combobox>
<v-btn
v-if="store.state.user.user.mode !== 'single user' && isNewRoute"
v-if="store.state.user.user.mode !== 'single user' && isNewRoute && owner && deployment"
data-cy="multiuser-go-btn"
:href="url"
variant="flat"
class="px-8"
color="green"
@click="addOwner(owner); addDeployment(deployment);"
>
Go
</v-btn>
Expand All @@ -83,22 +118,46 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</template>

<script setup>
import { ref, computed } from 'vue'
import { computed, onMounted } from 'vue'
import { useStore } from 'vuex'
import {
mdiClose
} from '@mdi/js'

import useLocalStorage from '@/composables/useLocalStorage.js'

const store = useStore()
const {
itemOnLoad: ownerOnLoad,
item: owner,
items: owners,
addToLocalStorage: addOwner,
removeFromLocalStorage: removeOwner
} = useLocalStorage('owners', store.state.user.user.owner)

// owner logic
const ownerOnLoad = store.state.user.user.owner
const owner = ref(ownerOnLoad)
const owners = ref(new Set([ownerOnLoad]))
const {
itemOnLoad: deploymentOnLoad,
item: deployment,
items: deployments,
addToLocalStorage: addDeployment,
removeFromLocalStorage: removeDeployment
} = useLocalStorage('deployments', window.location.host)

// deployment logic
const deploymentOnLoad = window.location.host
const deployment = ref(deploymentOnLoad)
const deployments = ref(new Set([deploymentOnLoad]))
onMounted(() => {
// Set load state for owners
if (!localStorage.owners) {
localStorage.setItem('owners', JSON.stringify(Array.from(owners.value)))
} else {
owners.value = new Set(JSON.parse(localStorage.owners))
}
// Set load state for deployments
if (!localStorage.deployments) {
localStorage.setItem('deployments', JSON.stringify(Array.from(deployments.value)))
} else {
deployments.value = new Set(JSON.parse(localStorage.deployments))
}
})

// route logic
const url = computed(() => `//${deployment.value}/user/${owner.value}/cylc/#`)

const isNewRoute = computed(() => {
Expand Down
62 changes: 62 additions & 0 deletions src/composables/useLocalStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ref } from 'vue'

/**
* @callback addToLocalStorage
* @param {*} item Item to add to local storage
* @return {void}
*/

/**
* @callback removeFromLocalStorage
* @param {*} item Item to remove from local storage
* @return {void}
*/

/**
* @typedef {Object} LocalStorage
* @property {*} itemOnLoad - The default item on load
* @property {import('vue').Ref} item - The selected item
* @property {import('vue').Ref<Set>} items - The array of items in local storage
* @property {addToLocalStorage} addToLocalStorage - Adds a value to local storage
* @property {removeFromLocalStorage} removeFromLocalStorage - Removes a value from local storage
*/

/**
* Function for initialising, adding to and removing from local storage.
* Values are stored in arrays with and can be accessed
* via a key which specified by the 'key' parameter.
* The local storage array is initialized with a value
* specified by the 'initialValue' parameter.
* Note: this could be replaced by a VueUse composable in the future
* https://vueuse.org/core/useLocalStorage/
*
* @param {string} key
* @param {*} initialValue
* @return {LocalStorage}
*/
export default function useLocalStorage (key, initialValue) {
markgrahamdawson marked this conversation as resolved.
Show resolved Hide resolved
const itemOnLoad = initialValue
const item = ref(itemOnLoad)
const items = ref(new Set([itemOnLoad]))

function addToLocalStorage (item) {
if (item) {
items.value.add(item)
localStorage.setItem(key, JSON.stringify(Array.from(items.value)))
}
}
function removeFromLocalStorage (item) {
if (item) {
items.value.delete(item)
localStorage.setItem(key, JSON.stringify(Array.from(items.value)))
}
}

return {
itemOnLoad,
item,
items,
addToLocalStorage,
removeFromLocalStorage
}
}
5 changes: 5 additions & 0 deletions tests/e2e/specs/header.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ describe('Header Component multiuser', () => {
.get('.v-combobox__content').contains('userTest')
.get('.v-combobox__content').contains('userTest123')

// Test to see if values are in local storage
cy.getAllLocalStorage().then((result) => {
expect(JSON.parse(result[window.location.origin].owners)).to.deep.equal(['userTest', 'userTest123'])
})

cy.get('body').type('{esc}') // Closes combobox
.get('[data-cy=multiuser-go-btn]')
.should('be.visible')
Expand Down
26 changes: 26 additions & 0 deletions tests/unit/composables/useLocalStorage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import useLocalStorage from '@/composables/useLocalStorage.js'

describe('useLocalStorage composable', () => {
const {
items,
addToLocalStorage,
removeFromLocalStorage
} = useLocalStorage('itemsLocalStoreKey', 'test initial item')

localStorage.setItem('itemsLocalStoreKey', JSON.stringify(Array.from(items.value)))

it('should add a value to the local store', () => {
addToLocalStorage('test item')
expect(Array.from(items.value)).toStrictEqual(['test initial item', 'test item'])
})

it('should not add more than one of the same value to the local store', () => {
addToLocalStorage('test item')
expect(Array.from(items.value)).toStrictEqual(['test initial item', 'test item'])
})

it('should remove a value to the local store', () => {
removeFromLocalStorage('test item')
expect(Array.from(items.value)).toStrictEqual(['test initial item'])
})
})
markgrahamdawson marked this conversation as resolved.
Show resolved Hide resolved