mirror of https://github.com/Qiskit/qiskit.org.git
Create advocates filter (#1122)
This commit is contained in:
parent
9c891b7aaa
commit
5ae48967ac
|
@ -91,4 +91,14 @@ $logo-size-small: 8rem;
|
|||
$icon-size: 0.75rem;
|
||||
|
||||
$carbon--theme: $carbon--theme--custom;
|
||||
@include carbon--theme();
|
||||
@include carbon--theme();
|
||||
|
||||
//== Checkbox
|
||||
|
||||
.bx--checkbox:focus + .bx--checkbox-label::before {
|
||||
box-shadow: 0 0 0 2px $white, 0 0 0 4px $purple-60;
|
||||
}
|
||||
|
||||
.bx--checkbox-label::before {
|
||||
border: 1px solid $black-100;
|
||||
}
|
||||
|
|
|
@ -17,16 +17,24 @@
|
|||
<template slot="filters-on-m-l-screen">
|
||||
<AppFieldset :label="filter.label">
|
||||
<client-only>
|
||||
<AppCheckbox
|
||||
<cv-checkbox
|
||||
v-for="option in filter.options"
|
||||
:key="option"
|
||||
:option="option"
|
||||
:checked="isRegionFilterChecked(option)"
|
||||
:label="option"
|
||||
:value="option"
|
||||
@change="updateRegionFilter(option, $event)"
|
||||
/>
|
||||
</client-only>
|
||||
</AppFieldset>
|
||||
</template>
|
||||
<template slot="filters-on-s-screen">
|
||||
<AppMultiSelect v-bind="filter" />
|
||||
<AppMultiSelect
|
||||
:label="filter.label"
|
||||
:options="filter.options"
|
||||
:value="regionFilters"
|
||||
@change-selection="updateRegionFilters($event)"
|
||||
/>
|
||||
</template>
|
||||
<template slot="results">
|
||||
<InfiniteScroll
|
||||
|
@ -44,33 +52,63 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { mapState, MapperForStateWithNamespace } from 'vuex'
|
||||
import { Component, Prop } from 'vue-property-decorator'
|
||||
import AdvocateCard from '~/components/advocates/AdvocateCard.vue'
|
||||
import AppMultiSelect from '~/components/ui/AppMultiSelect.vue'
|
||||
import AppFieldset from '~/components/ui/AppFieldset.vue'
|
||||
import AppCheckbox from '~/components/ui/AppCheckbox.vue'
|
||||
import AppFiltersResultsLayout from '~/components/ui/AppFiltersResultsLayout.vue'
|
||||
import InfiniteScroll from '~/components/ui/InfiniteScroll.vue'
|
||||
import AppLink from '~/components/ui/AppLink.vue'
|
||||
import { Advocate, ADVOCATES_WORLD_REGION_OPTIONS, State } from '~/store/modules/advocates.ts'
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
AdvocateCard,
|
||||
AppMultiSelect,
|
||||
AppFieldset,
|
||||
AppCheckbox,
|
||||
AppFiltersResultsLayout,
|
||||
InfiniteScroll,
|
||||
AppLink
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState<MapperForStateWithNamespace>('advocates', {
|
||||
regionFilters: (state: State) => state.regionFilters
|
||||
})
|
||||
}
|
||||
})
|
||||
export default class extends Vue {
|
||||
@Prop(Array) advocates!: any
|
||||
export default class MeetTheAdvocates extends Vue {
|
||||
@Prop(Array) advocates!: Advocate[]
|
||||
|
||||
filter = {
|
||||
/**
|
||||
* Region filters from Vuex store.
|
||||
*
|
||||
* Initialized with mapState.
|
||||
*/
|
||||
public regionFilters!: string[]
|
||||
|
||||
private filter = {
|
||||
label: 'Locations',
|
||||
options: ['Americas', 'Asia Pacific', 'Europe', 'Africa'],
|
||||
filterType: 'regionFilters'
|
||||
options: ADVOCATES_WORLD_REGION_OPTIONS
|
||||
}
|
||||
|
||||
isRegionFilterChecked (filterValue: string): boolean {
|
||||
return this.regionFilters.includes(filterValue)
|
||||
}
|
||||
|
||||
updateRegionFilter (option: string, isChecked: boolean): void {
|
||||
const regionFilters = this.regionFilters.filter(oldOption => oldOption !== option)
|
||||
|
||||
if (isChecked) {
|
||||
regionFilters.push(option)
|
||||
}
|
||||
|
||||
this.updateRegionFilters(regionFilters)
|
||||
}
|
||||
|
||||
updateRegionFilters (regionFilters: string[]): void {
|
||||
this.$store.commit('advocates/setRegionFilters', regionFilters)
|
||||
}
|
||||
|
||||
joinSlackLink: string = 'https://ibm.co/joinqiskitslack'
|
||||
|
|
|
@ -2842,6 +2842,12 @@
|
|||
"integrity": "sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.165",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz",
|
||||
"integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/markdown-it": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-10.0.3.tgz",
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
"@nuxtjs/axios": "^5.12.4",
|
||||
"carbon-components": "^10.25.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"lodash": "^4.17.20",
|
||||
"nuxt": "^2.14.11",
|
||||
"nuxt-lazy-load": "^1.2.4",
|
||||
"ts-node": "^9.1.1",
|
||||
|
@ -43,6 +44,7 @@
|
|||
"@nuxtjs/style-resources": "^1.0.0",
|
||||
"@types/airtable": "^0.8.1",
|
||||
"@types/jest": "^26.0.19",
|
||||
"@types/lodash": "^4.14.165",
|
||||
"@types/markdown-it": "^10.0.3",
|
||||
"@types/markdown-it-anchor": "^4.0.4",
|
||||
"@types/markdown-it-link-attributes": "^3.0.0",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { Component } from 'vue-property-decorator'
|
||||
import QiskitPage from '~/components/logic/QiskitPage.vue'
|
||||
|
||||
|
@ -19,21 +19,13 @@ import QiskitPage from '~/components/logic/QiskitPage.vue'
|
|||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters([
|
||||
...mapGetters('advocates', [
|
||||
'filteredAdvocates'
|
||||
])
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
fetchAdvocates: 'fetchAdvocates'
|
||||
})
|
||||
},
|
||||
|
||||
async fetch ({ store }) {
|
||||
const advocates = await store.dispatch('fetchAdvocates')
|
||||
|
||||
store.commit('setAdvocates', advocates)
|
||||
await store.dispatch('advocates/fetchAdvocates')
|
||||
}
|
||||
})
|
||||
export default class AdvocatesPage extends QiskitPage {
|
||||
|
|
|
@ -6,6 +6,8 @@ import learningResources from './modules/learning-resources'
|
|||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default () => new Vuex.Store({
|
||||
export const storeOptions = {
|
||||
modules: { events, advocates, learningResources }
|
||||
})
|
||||
}
|
||||
|
||||
export default () => new Vuex.Store(storeOptions)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { ActionTree, GetterTree, MutationTree } from 'vuex'
|
||||
|
||||
const ADVOCATES_WORLD_REGIONS = Object.freeze({
|
||||
northAmerica: 'North America',
|
||||
southAmerica: 'South America',
|
||||
|
@ -9,14 +11,18 @@ const ADVOCATES_WORLD_REGIONS = Object.freeze({
|
|||
|
||||
type AdvocatesWorldRegion = typeof ADVOCATES_WORLD_REGIONS[keyof typeof ADVOCATES_WORLD_REGIONS]
|
||||
|
||||
type Advocate = {
|
||||
name: string,
|
||||
image: string,
|
||||
city: string,
|
||||
country: string,
|
||||
region: AdvocatesWorldRegion,
|
||||
slackId: string,
|
||||
slackUsername: string
|
||||
/**
|
||||
* Interface for a Qiskit advocate.
|
||||
*/
|
||||
interface Advocate {
|
||||
city: string
|
||||
country: string
|
||||
image: string
|
||||
location?: string
|
||||
name: string
|
||||
region: AdvocatesWorldRegion
|
||||
slackId?: string
|
||||
slackUsername?: string
|
||||
}
|
||||
|
||||
const ADVOCATES_WORLD_REGION_OPTIONS = Object.freeze([
|
||||
|
@ -35,28 +41,48 @@ export {
|
|||
Advocate
|
||||
}
|
||||
|
||||
export default {
|
||||
state () {
|
||||
return {
|
||||
advocates: []
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
filteredAdvocates (state: any) {
|
||||
const { advocates } = state
|
||||
export class State {
|
||||
advocates: Advocate[] = []
|
||||
regionFilters: string[] = []
|
||||
}
|
||||
|
||||
const getters = <GetterTree<State, any>> {
|
||||
/**
|
||||
* List of advocates filtered by selected regions.
|
||||
*/
|
||||
filteredAdvocates ({ advocates, regionFilters }): Advocate[] {
|
||||
const noRegionFilters = regionFilters.length === 0
|
||||
|
||||
if (noRegionFilters) {
|
||||
return advocates
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
setAdvocates (state: any, payload: any) {
|
||||
state.advocates = payload
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async fetchAdvocates () {
|
||||
const advocatesModule = await import('~/content/advocates/advocates.json')
|
||||
return advocatesModule.default || []
|
||||
}
|
||||
|
||||
return advocates.filter(advocate => regionFilters.includes(advocate.region))
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = <MutationTree<State>> {
|
||||
setAdvocates (state, advocates: Advocate[]) {
|
||||
state.advocates = advocates
|
||||
},
|
||||
|
||||
setRegionFilters (state, regionFilters: string[]) {
|
||||
state.regionFilters = regionFilters
|
||||
}
|
||||
}
|
||||
|
||||
const actions = <ActionTree<State, any>> {
|
||||
async fetchAdvocates ({ commit }): Promise<void> {
|
||||
const advocatesModule = await import('~/content/advocates/advocates.json')
|
||||
const advocates = advocatesModule.default || []
|
||||
commit('setAdvocates', advocates)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: new State(),
|
||||
actions,
|
||||
mutations,
|
||||
getters
|
||||
}
|
||||
|
|
|
@ -43,12 +43,6 @@ type CommunityEvent = {
|
|||
to: string
|
||||
}
|
||||
|
||||
type EventMultiSelectOption = {
|
||||
label: string,
|
||||
value: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
type EventPayload = {
|
||||
events: string,
|
||||
eventsSet: CommunityEvent[]
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
import Vuex, { Store } from 'vuex'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import { storeOptions } from '~/store'
|
||||
|
||||
let store: Store<any>
|
||||
|
||||
const mockAdvocate1 = () => ({
|
||||
city: 'Lima',
|
||||
country: 'Peru',
|
||||
image: 'https://example.com/img/1.jpg',
|
||||
name: 'John Doe',
|
||||
region: 'South America'
|
||||
})
|
||||
const mockAdvocate2 = () => ({
|
||||
city: 'Munich',
|
||||
country: 'Germany',
|
||||
image: 'https://example.com/img/2.jpg',
|
||||
name: 'Max Mustermann',
|
||||
region: 'Europe'
|
||||
})
|
||||
|
||||
/**
|
||||
* GETTERS
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
describe('filteredAdvocates', () => {
|
||||
const getter = 'advocates/filteredAdvocates'
|
||||
const mockMatchingRegionFilter1 = () => 'South America'
|
||||
const mockMatchingRegionFilter2 = () => 'Europe'
|
||||
const mockNonMatchingRegionFilter = () => 'Moon'
|
||||
|
||||
beforeEach(() => {
|
||||
const initialStoreOptions = cloneDeep(storeOptions)
|
||||
store = new Vuex.Store(initialStoreOptions)
|
||||
store.commit('advocates/setAdvocates', [mockAdvocate1(), mockAdvocate2()])
|
||||
})
|
||||
|
||||
it('returns a filtered list of advocates for 1 matching filter', () => {
|
||||
store.commit('advocates/setRegionFilters', [mockMatchingRegionFilter1()])
|
||||
expect(store.getters[getter]).toEqual([mockAdvocate1()])
|
||||
})
|
||||
|
||||
it('returns a filtered list of advocates for 2 matching filters', () => {
|
||||
store.commit('advocates/setRegionFilters', [mockMatchingRegionFilter1(), mockMatchingRegionFilter2()])
|
||||
expect(store.getters[getter]).toEqual([mockAdvocate1(), mockAdvocate2()])
|
||||
})
|
||||
|
||||
it('returns an empty filtered list of advocates for no matching filters', () => {
|
||||
store.commit('advocates/setRegionFilters', [mockNonMatchingRegionFilter()])
|
||||
expect(store.getters[getter]).toEqual([])
|
||||
})
|
||||
|
||||
it('returns the complete list of advocates when there are no filters', () => {
|
||||
store.commit('advocates/setRegionFilters', [])
|
||||
expect(store.getters[getter]).toEqual([mockAdvocate1(), mockAdvocate2()])
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* MUTATIONS
|
||||
* -----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
describe('setAdvocates', () => {
|
||||
const mutationType = 'advocates/setAdvocates'
|
||||
|
||||
beforeEach(() => {
|
||||
const initialStoreOptions = cloneDeep(storeOptions)
|
||||
store = new Vuex.Store(initialStoreOptions)
|
||||
})
|
||||
|
||||
it('sets the list of advocates with one advocate', () => {
|
||||
store.commit(mutationType, [mockAdvocate1()])
|
||||
expect(store.state.advocates.advocates).toEqual([mockAdvocate1()])
|
||||
})
|
||||
|
||||
it('sets the list of advocates twice and keeps only the latest list', () => {
|
||||
store.commit(mutationType, [mockAdvocate1()])
|
||||
store.commit(mutationType, [mockAdvocate2()])
|
||||
expect(store.state.advocates.advocates).toEqual([mockAdvocate2()])
|
||||
})
|
||||
|
||||
it('sets the list of advocates with multiple advocates', () => {
|
||||
store.commit(mutationType, [mockAdvocate1(), mockAdvocate2()])
|
||||
expect(store.state.advocates.advocates).toEqual([mockAdvocate1(), mockAdvocate2()])
|
||||
})
|
||||
|
||||
it('unsets the list of advocates', () => {
|
||||
store.commit(mutationType, [])
|
||||
expect(store.state.advocates.advocates).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('setRegionFilters', () => {
|
||||
const mutationType = 'advocates/setRegionFilters'
|
||||
const mockRegionFilter1 = 'South America'
|
||||
const mockRegionFilter2 = 'Europe'
|
||||
|
||||
beforeEach(() => {
|
||||
const initialStoreOptions = cloneDeep(storeOptions)
|
||||
store = new Vuex.Store(initialStoreOptions)
|
||||
})
|
||||
|
||||
it('sets the region filters with one filter', () => {
|
||||
store.commit(mutationType, [mockRegionFilter1])
|
||||
expect(store.state.advocates.regionFilters).toEqual([mockRegionFilter1])
|
||||
})
|
||||
|
||||
it('sets the region filters twice and keeps only the latest filters', () => {
|
||||
store.commit(mutationType, [mockRegionFilter1])
|
||||
store.commit(mutationType, [mockRegionFilter2])
|
||||
expect(store.state.advocates.regionFilters).toEqual([mockRegionFilter2])
|
||||
})
|
||||
|
||||
it('sets the region filters with multiple filters', () => {
|
||||
store.commit(mutationType, [mockRegionFilter1, mockRegionFilter2])
|
||||
expect(store.state.advocates.regionFilters).toEqual([mockRegionFilter1, mockRegionFilter2])
|
||||
})
|
||||
|
||||
it('unsets the region filters', () => {
|
||||
store.commit(mutationType, [])
|
||||
expect(store.state.advocates.regionFilters).toEqual([])
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue