diff --git a/app/assets/v2/js/shared.js b/app/assets/v2/js/shared.js index ae3c299219e..d19968533a4 100644 --- a/app/assets/v2/js/shared.js +++ b/app/assets/v2/js/shared.js @@ -1010,6 +1010,75 @@ function shuffleArray(array) { return array; } + +const getAllUrlParams = () => { + + // get query string from url (optional) or window + var queryString = window.location.search.slice(1); + + // we'll store the parameters here + var obj = {}; + + // if query string exists + if (queryString) { + + // stuff after # is not part of query string, so get rid of it + queryString = queryString.split('#')[0]; + + // split our query string into its component parts + var arr = queryString.split('&'); + + for (var i = 0; i < arr.length; i++) { + // separate the keys and the values + var a = arr[i].split('='); + + // set parameter name and value (use 'true' if empty) + var paramName = a[0]; + var paramValue = typeof (a[1]) === 'undefined' ? true : a[1]; + + // (optional) keep case consistent + paramName = paramName.toLowerCase(); + if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase(); + + // if the paramName ends with square brackets, e.g. colors[] or colors[2] + if (paramName.match(/\[(\d+)?\]$/)) { + + // create key if it doesn't exist + var key = paramName.replace(/\[(\d+)?\]/, ''); + + if (!obj[key]) obj[key] = []; + + // if it's an indexed array e.g. colors[2] + if (paramName.match(/\[\d+\]$/)) { + // get the index value and add the entry at the appropriate position + var index = (/\[(\d+)\]/).exec(paramName)[1]; + + obj[key][index] = paramValue; + } else { + // otherwise add the value to the end of the array + obj[key].push(paramValue); + } + } else { + // we're dealing with a string + // eslint-disable-next-line no-lonely-if + if (!obj[paramName]) { + // if it doesn't exist, create property + obj[paramName] = paramValue; + } else if (obj[paramName] && typeof obj[paramName] === 'string') { + // if property does exist and it's a string, convert it to an array + obj[paramName] = [obj[paramName]]; + obj[paramName].push(paramValue); + } else { + // otherwise add the property + obj[paramName].push(paramValue); + } + } + } + } + + return obj; +}; + const getURLParams = (k) => { var p = {}; diff --git a/app/assets/v2/js/users-elastic.js b/app/assets/v2/js/users-elastic.js index 2dba731d39e..9651e7a6209 100644 --- a/app/assets/v2/js/users-elastic.js +++ b/app/assets/v2/js/users-elastic.js @@ -5,6 +5,8 @@ let usersHasNext = false; let numUsers = ''; let hackathonId = document.hasOwnProperty('hackathon_id') ? document.hackathon_id : ''; +const EventBus = new Vue(); + Vue.mixin({ methods: { messageUser: function(handle) { @@ -219,21 +221,85 @@ Vue.mixin({ }); } }, - extractURLFilters: function() { + + extractURLFilters: function(serverFilters) { let vm = this; - let params = getURLParams(); + let params = getAllUrlParams(); + let columns = serverFilters[vm.header.index]['mappings'][vm.header.type]['properties']; - vm.users = []; + if (params.body && !vm.filtersLoaded) { + let decodedBody = decodeURIComponent(params.body); - if (params) { - for (var prop in params) { - if (prop === 'skills') { - vm.$set(vm.params, prop, params[prop].split(',')); - } else { - vm.$set(vm.params, prop, params[prop]); + + let newBody = JSON.parse(decodedBody); + + this.setBody(newBody); + this.fetch(this); + + if (Object.values(newBody).length > 0) { + try { + // eslint-disable-next-line guard-for-in + let query = newBody['query']; + let activeFilters = []; + + if (query.hasOwnProperty('bool') && query['bool'].hasOwnProperty('filter') && query['bool']['filter'].hasOwnProperty('bool') && query['bool']['filter']['bool'].hasOwnProperty('should')) { + query = query['bool']['filter']['bool']['should']; + for (let x in query) { + if (!query[x]) + continue; + let terms = query[x]['term']; + + if (!terms) { + continue; + } + // eslint-disable-next-line guard-for-in + for (let prop in terms) { + let term = terms[prop]; + + let meta = columns[prop]; + + if (!meta) + continue; + let propKey = prop.replace('_exact', ''); + + if (typeof activeFilters[propKey] !== 'object') { + activeFilters[propKey] = []; + } + activeFilters[propKey].push(term); + if (columns[propKey]['selected'] !== true) { + columns[propKey]['selected'] = true; + columns[propKey]['selectedValues'] = []; + } + + + let value = Object.values(activeFilters[propKey])[0]; + + if (!value) + continue; + columns[propKey]['selectedValues'].push(value); + + let _instruction = { + fun: 'orFilter', + args: [ 'term', propKey, value ] + }; + + this.localInstructions.push(_instruction); + this.addInstruction(_instruction); + } + } + vm.params = activeFilters; + } + + + } catch (e) { + console.log(e); } } + } + vm.esColumns = columns; + + vm.filterLoaded = true; }, joinTribe: function(user, event) { event.target.disabled = true; @@ -264,257 +330,107 @@ Vue = Vue.extend({ }); -Vue.component('directory-card', { - name: 'DirectoryCard', - delimiters: [ '[[', ']]' ], - props: [ 'user', 'funderBounties' ] -}); -Vue.use(innerSearch.default); -Vue.component('autocomplete', { - props: [ 'options', 'value' ], - template: '#select2-template', - methods: { - formatMapping: function(item) { - console.log(item); - return item.name; - }, - formatMappingSelection: function(filter) { - return ''; - } - }, - mounted() { - let count = 0; - let vm = this; - let mappedFilters = {}; - let data = $.map(this.options, function(obj, key) { - - if (key.indexOf('_exact') === -1) - return; - let newKey = key.replace('_exact', ''); - - if (mappedFilters[newKey]) - return; - obj.id = count++; - obj.text = newKey; - obj.key = key; - - mappedFilters[newKey] = true; - mappedFilters[key] = true; - return obj; - }); - - - $(vm.$el).select2({ - data: data, - multiple: true, - allowClear: true, - placeholder: 'Search for another filter to add', - minimumInputLength: 1, - escapeMarkup: function(markup) { - return markup; - } - }) - .on('change', function() { - console.log('changed'); - let val = $(vm.$el).val(); - - let changeData = $.map(val, function(filter) { - return data[filter]; - }); +if (document.getElementById('gc-users-elastic')) { - vm.$emit('input', changeData); - }); + Vue.component('directory-card', { + name: 'DirectoryCard', + delimiters: [ '[[', ']]' ], + props: [ 'user', 'funderBounties' ] + }); - // fix for wrong position on select open - var select2Instance = $(vm.$el).data('select2'); - - select2Instance.on('results:message', function(params) { - this.dropdown._resizeDropdown(); - this.dropdown._positionDropdown(); - }); - }, - destroyed: function() { - $(this.$el).off().select2('destroy'); - this.$emit('destroyed'); - } -}); -Vue.component('user-directory', { - delimiters: [ '[[', ']]' ], - props: [ 'tribe', 'is_my_org' ], - data: function() { - return { - orgOwner: this.is_my_org || false, - userFilter: { - options: [ - {text: 'All', value: 'all'}, - {text: 'Tribe Owners', value: 'owners'}, - {text: 'Tribe Members', value: 'members'}, - {text: 'Tribe Hackers', value: 'hackers'} - ] + Vue.use(innerSearch.default); + Vue.component('autocomplete', { + props: [ 'options', 'value' ], + template: '#select2-template', + data: function() { + return { + selectedFilters: [] + }; + }, + methods: { + reset: function() { + $(this.$el).select2().val(null).trigger('change'); }, - tribeFilter: this.tribe || '', - users, - usersPage, - hackathonId, - usersNumPages, - usersHasNext, - numUsers, - media_url, - chatURL: document.chatURL || 'https://chat.gitcoin.co/', - searchTerm: null, - bottom: false, - params: { - 'user_filter': 'all' + formatMapping: function(item) { + console.log(item); + return item.name; }, - funderBounties: [], - currentBounty: undefined, - contributorInvite: undefined, - isFunder: false, - bountySelected: null, - userSelected: [], - showModal: false, - showFilters: true, - skills: document.keywords, - selectedSkills: [], - noResults: false, - isLoading: true, - gitcoinIssueUrl: '', - issueDetails: undefined, - errorIssueDetails: undefined, - showBanner: undefined, - persona: undefined, - hideFilterButton: !!document.getElementById('explore_tribes'), - expandFilter: true - }; - }, - - mounted() { - this.fetchUsers(); - this.tribeFilter = this.tribe; - this.$watch('params', function(newVal, oldVal) { - this.searchUsers(); - }, { - deep: true - }); - }, - created() { - if (document.contxt.github_handle && this.is_my_org) { - this.fetchBounties(); - } - this.inviteOnMount(); - this.extractURLFilters(); - }, - beforeMount() { - if (this.isMobile) { - this.showFilters = false; - } - window.addEventListener('scroll', () => { - this.bottom = this.bottomVisible(); - }, false); - }, - beforeDestroy() { - window.removeEventListener('scroll', () => { - this.bottom = this.bottomVisible(); - }); - } -}); -Vue.component('user-directory-elastic', { - delimiters: [ '[[', ']]' ], - data: function() { - return { - filters: [], - esColumns: [], - filterLoaded: false, - users, - usersPage, - usersNumPages, - usersHasNext, - numUsers, - media_url, - chatURL: document.chatURL || 'https://chat.gitcoin.co/', - searchTerm: null, - bottom: false, - params: {}, - funderBounties: [], - currentBounty: undefined, - contributorInvite: undefined, - isFunder: false, - bountySelected: null, - userSelected: [], - showModal: false, - showFilters: !document.getElementById('explore_tribes'), - skills: document.keywords, - selectedSkills: [], - noResults: false, - isLoading: true, - gitcoinIssueUrl: '', - issueDetails: undefined, - errorIssueDetails: undefined, - showBanner: undefined, - persona: undefined, - hideFilterButton: !!document.getElementById('explore_tribes') - }; - }, - methods: { - autoCompleteDestroyed: function() { - this.filters = []; - }, - autoCompleteChange: function(filters) { - this.filters = filters; + formatMappingSelection: function(filter) { + return ''; + } }, - outputToCSV: function() { - let url = '/api/v0.1/users_csv/'; - - $.get(url, this.body).then(resp => resp.json()).then(json => { - _alert(json.message); - }).catch(() => _alert('There was an issue processing your request')); + created() { + EventBus.$on('reset', () => { + this.s2.val([]).trigger('change'); + }); }, - fetchMappings: function() { + mounted() { + let count = 0; let vm = this; + let mappedFilters = {}; + let data = $.map(this.options, function(obj, key) { + + if (key.indexOf('_exact') === -1) + return; + let newKey = key.replace('_exact', ''); + + if (mappedFilters[newKey]) + return; + obj.id = count++; + obj.text = newKey; + obj.key = key; + + if (obj.selected && obj.key !== 'keywords') { + console.log(`${obj.text} is selected`); + vm.selectedFilters.push(obj.id); + } + + mappedFilters[newKey] = true; + mappedFilters[key] = true; + return obj; + }); - $.when(vm.header.client.indices.getMapping()) - .then(response => { - vm.esColumns = response[vm.header.index]['mappings'][vm.header.type]['properties']; - vm.filterLoaded = true; + vm.s2 = $(vm.$el) + .select2({ + data: data, + multiple: true, + allowClear: true, + placeholder: 'Search for another filter to add', + minimumInputLength: 1, + escapeMarkup: function(markup) { + return markup; + } + }) + .on('change', function() { + let val = $(vm.$el).val(); + let changeData = $.map(val, function(filter) { + return data[filter]; + }); + + vm.$emit('input', changeData); + EventBus.$emit('query:changed'); }); - } - }, - mounted() { - this.fetchMappings(); - // this.fetchUsers(); - this.$watch('params', function(newVal, oldVal) { - this.searchUsers(); - }, { - deep: true - }); - }, - created() { - this.setHost(document.contxt.search_url); - this.setIndex('haystack'); - this.setType('modelresult'); - this.fetchBounties(); - this.inviteOnMount(); - this.extractURLFilters(); - }, - beforeMount() { - window.addEventListener('scroll', () => { - this.bottom = this.bottomVisible(); - }, false); - }, - beforeDestroy() { - window.removeEventListener('scroll', () => { - this.bottom = this.bottomVisible(); - }); - } -}); -if (document.getElementById('gc-users-elastic')) { + vm.s2.val(vm.selectedFilters).trigger('change'); + // fix for wrong position on select open + var select2Instance = $(vm.$el).data('select2'); + + select2Instance.on('results:message', function(params) { + this.dropdown._resizeDropdown(); + this.dropdown._positionDropdown(); + }); + }, + destroyed: function() { + $(this.$el).off().select2('destroy'); + this.$emit('destroyed'); + } + }); window.UserDirectory = new Vue({ delimiters: [ '[[', ']]' ], el: '#gc-users-elastic', data: { + localInstructions: [], csrf: document.csrf, - filters: [], esColumns: [], filterLoaded: false, users, @@ -527,6 +443,7 @@ if (document.getElementById('gc-users-elastic')) { searchTerm: null, bottom: false, params: {}, + filters: [], funderBounties: [], currentBounty: undefined, contributorInvite: undefined, @@ -547,6 +464,13 @@ if (document.getElementById('gc-users-elastic')) { hideFilterButton: !!document.getElementById('explore_tribes') }, methods: { + filterSorter: function(results) { + return results; + }, + select2InputEventListener(cb, args) { + cb(args); + this.serializeBodytoShare(); + }, resetCallback: function() { this.checkedItems = []; }, @@ -556,6 +480,18 @@ if (document.getElementById('gc-users-elastic')) { autoCompleteChange: function(filters) { this.filters = filters; }, + serializeBodytoShare: function() { + const currBody = this.body; + + const jsonBody = JSON.stringify(currBody); + + const params = `body=${encodeURIComponent(jsonBody)}`; + const shareURL = `${window.location.origin}${window.location.pathname}?${params}`; + + window.history.pushState('', '', shareURL); + + console.log(shareURL); + }, outputToCSV: function() { let url = '/api/v0.1/users_csv/'; @@ -572,26 +508,27 @@ if (document.getElementById('gc-users-elastic')) { $.when(vm.header.client.indices.getMapping()) .then(response => { - vm.esColumns = response[vm.header.index]['mappings'][vm.header.type]['properties']; - vm.filterLoaded = true; + this.extractURLFilters(response); + this.fetch(this); }); } }, + updated() { + this.serializeBodytoShare(); + }, mounted() { this.fetchMappings(); - this.fetch(this); - this.$watch('params', function(newVal, oldVal) { - this.searchUsers(); - }, { - deep: true - }); }, created() { this.setHost(document.contxt.search_url); this.setIndex('haystack'); this.setType('modelresult'); - // this.extractURLFilters(); + this.bus.$on('reset', () => { + EventBus.$emit('reset'); + this.removeInstructions(); + this.fetch(this); + }); }, beforeMount() { window.addEventListener('scroll', () => { diff --git a/app/assets/v2/js/vue-components.js b/app/assets/v2/js/vue-components.js index 0edd0c4eec2..832aea6d080 100644 --- a/app/assets/v2/js/vue-components.js +++ b/app/assets/v2/js/vue-components.js @@ -106,15 +106,21 @@ Vue.component('modal', { Vue.component('select2', { - props: [ 'options', 'value', 'placeholder', 'inputlength' ], + props: [ 'options', 'value', 'placeholder', 'inputlength', 'sorter' ], template: '#select2-template', mounted: function() { let vm = this; - - $(vm.$el).select2({ + let select2Options = { data: vm.options, placeholder: vm.placeholder !== null ? vm.placeholder : 'filter here', - minimumInputLength: vm.inputlength !== null ? vm.inputlength : 1}) + minimumInputLength: vm.inputlength !== null ? vm.inputlength : 1 + }; + + if (vm.sorter) { + select2Options['sorter'] = vm.sorter; + } + + $(vm.$el).select2(select2Options) .val(vm.value) .trigger('change') .on('change', function() { diff --git a/app/dashboard/templates/dashboard/users-elastic.html b/app/dashboard/templates/dashboard/users-elastic.html index 473b3cb00ed..dfc4805fe2b 100644 --- a/app/dashboard/templates/dashboard/users-elastic.html +++ b/app/dashboard/templates/dashboard/users-elastic.html @@ -39,13 +39,13 @@