diff options
Diffstat (limited to 'app/assets/javascripts')
19 files changed, 267 insertions, 133 deletions
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 080841ee82b..1edaf971afd 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -3,7 +3,7 @@ import { __ } from '~/locale'; import ListLabel from './label'; import ListAssignee from './assignee'; -import ListIssue from './issue'; +import ListIssue from 'ee_else_ce/boards/models/issue'; import { urlParamsToObject } from '~/lib/utils/common_utils'; import boardsStore from '../stores/boards_store'; import ListMilestone from './milestone'; diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 1f213d5aaf2..28850710f80 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -39,6 +39,7 @@ export default class Clusters { updateKnativePath, installPrometheusPath, managePrometheusPath, + clusterEnvironmentsPath, hasRbac, clusterType, clusterStatus, @@ -79,6 +80,7 @@ export default class Clusters { installJupyterEndpoint: installJupyterPath, installKnativeEndpoint: installKnativePath, updateKnativeEndpoint: updateKnativePath, + clusterEnvironmentsEndpoint: clusterEnvironmentsPath, }); this.installApplication = this.installApplication.bind(this); @@ -109,6 +111,10 @@ export default class Clusters { this.initApplications(clusterType); this.initEnvironments(); + if (clusterEnvironmentsPath) { + this.fetchEnvironments(); + } + this.updateContainer(null, this.store.state.status, this.store.state.statusReason); this.addListeners(); @@ -162,6 +168,7 @@ export default class Clusters { render(createElement) { return createElement(Environments, { props: { + isFetching: this.state.fetchingEnvironments, environments: this.state.environments, environmentsHelpPath: this.state.environmentsHelpPath, clustersHelpPath: this.state.clustersHelpPath, @@ -172,6 +179,18 @@ export default class Clusters { }); } + fetchEnvironments() { + this.store.toggleFetchEnvironments(true); + + this.service + .fetchClusterEnvironments() + .then(data => { + this.store.toggleFetchEnvironments(false); + this.store.updateEnvironments(data.data); + }) + .catch(() => Clusters.handleError()); + } + static initDismissableCallout() { const callout = document.querySelector('.js-cluster-security-warning'); PersistentUserCallout.factory(callout); diff --git a/app/assets/javascripts/clusters/services/clusters_service.js b/app/assets/javascripts/clusters/services/clusters_service.js index 01f3732de7e..9139e0beafb 100644 --- a/app/assets/javascripts/clusters/services/clusters_service.js +++ b/app/assets/javascripts/clusters/services/clusters_service.js @@ -33,6 +33,10 @@ export default class ClusterService { return axios.delete(this.appInstallEndpointMap[appId], params); } + fetchClusterEnvironments() { + return axios.get(this.options.clusterEnvironmentsEndpoint); + } + static updateCluster(endpoint, data) { return axios.put(endpoint, data); } diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 83533c88f69..a032f589ee4 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -84,6 +84,7 @@ export default class ClusterStore { }, }, environments: [], + fetchingEnvironments: false, }; } @@ -206,6 +207,10 @@ export default class ClusterStore { }); } + toggleFetchEnvironments(isFetching) { + this.state.fetchingEnvironments = isFetching; + } + updateEnvironments(environments = []) { this.state.environments = environments.map(environment => ({ name: environment.name, @@ -215,7 +220,7 @@ export default class ClusterStore { rolloutStatus: { instances: environment.rollout_status ? environment.rollout_status.instances : [], }, - updatedAt: environment.updatedAt, + updatedAt: environment.updated_at, })); } } diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index bdb50606a53..515402fc506 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -7,6 +7,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; import axios from './lib/utils/axios_utils'; import { visitUrl } from './lib/utils/url_utility'; import { isObject } from './lib/utils/type_utility'; +import renderItem from './gl_dropdown/render'; var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, GitLabDropdownInput; @@ -521,8 +522,8 @@ GitLabDropdown = (function() { html.push( this.renderItem( { - header: name, - // Add header for each group + content: name, + type: 'header', }, name, ), @@ -542,16 +543,7 @@ GitLabDropdown = (function() { }; GitLabDropdown.prototype.renderData = function(data, group) { - if (group == null) { - group = false; - } - return data.map( - (function(_this) { - return function(obj, index) { - return _this.renderItem(obj, group, index); - }; - })(this), - ); + return data.map((obj, index) => this.renderItem(obj, group || false, index)); }; GitLabDropdown.prototype.shouldPropagate = function(e) { @@ -688,104 +680,25 @@ GitLabDropdown = (function() { }; GitLabDropdown.prototype.renderItem = function(data, group, index) { - var field, html, selected, text, url, value, rowHidden; - - if (!this.options.renderRow) { - value = this.options.id ? this.options.id(data) : data.id; - - if (value) { - value = value.toString().replace(/'/g, "\\'"); - } - } - - // Hide element - if (this.options.hideRow && this.options.hideRow(value)) { - rowHidden = true; - } - if (group == null) { - group = false; - } - if (index == null) { - // Render the row - index = false; - } - html = document.createElement('li'); - - if (rowHidden) { - html.style.display = 'none'; - } - - if (data === 'divider' || data === 'separator') { - html.className = data; - return html; - } - // Header - if (data.header != null) { - html.className = 'dropdown-header'; - html.innerHTML = data.header; - return html; - } - if (this.options.renderRow) { - // Call the render function - html = this.options.renderRow.call(this.options, data, this); - } else { - if (!selected) { - const { fieldName } = this.options; - - if (value) { - field = this.dropdown.parent().find(`input[name='${fieldName}'][value='${value}']`); - if (field.length) { - selected = true; - } - } else { - field = this.dropdown.parent().find(`input[name='${fieldName}']`); - selected = !field.length; - } - } - // Set URL - if (this.options.url != null) { - url = this.options.url(data); - } else { - url = data.url != null ? data.url : '#'; - } - // Set Text - if (this.options.text != null) { - text = this.options.text(data); - } else { - text = data.text != null ? data.text : ''; - } - if (this.highlight) { - text = data.template - ? this.highlightTemplate(text, data.template) - : this.highlightTextMatches(text, this.filterInput.val()); - } - // Create the list item & the link - var link = document.createElement('a'); - - link.href = url; - - if (this.icon) { - text = `<span>${text}</span>`; - link.classList.add('d-flex', 'align-items-center'); - link.innerHTML = data.icon ? data.icon + text : text; - } else if (this.highlight) { - link.innerHTML = text; - } else { - link.textContent = text; - } - - if (selected) { - link.classList.add('is-active'); - } - - if (group) { - link.dataset.group = group; - link.dataset.index = index; - } - - html.appendChild(link); - } - return html; + let parent; + + if (this.dropdown && this.dropdown[0]) { + parent = this.dropdown[0].parentNode; + } + + return renderItem({ + instance: this, + options: Object.assign({}, this.options, { + icon: this.icon, + highlight: this.highlight, + highlightText: text => this.highlightTextMatches(text, this.filterInput.val()), + highlightTemplate: this.highlightTemplate.bind(this), + parent, + }), + data, + group, + index, + }); }; GitLabDropdown.prototype.highlightTemplate = function(text, template) { @@ -809,7 +722,6 @@ GitLabDropdown = (function() { }; GitLabDropdown.prototype.noResults = function() { - var html; return '<li class="dropdown-menu-empty-item"><a>No matching results</a></li>'; }; diff --git a/app/assets/javascripts/gl_dropdown/render.js b/app/assets/javascripts/gl_dropdown/render.js new file mode 100644 index 00000000000..66546aa834f --- /dev/null +++ b/app/assets/javascripts/gl_dropdown/render.js @@ -0,0 +1,158 @@ +const renderersByType = { + divider(element) { + element.classList.add('divider'); + + return element; + }, + separator(element) { + element.classList.add('separator'); + + return element; + }, + header(element, data) { + element.classList.add('dropdown-header'); + element.innerHTML = data.content; + + return element; + }, +}; + +function getPropertyWithDefault(data, options, property, defaultValue = '') { + let result; + + if (options[property] != null) { + result = options[property](data); + } else { + result = data[property] != null ? data[property] : defaultValue; + } + + return result; +} + +function getHighlightTextBuilder(text, data, options) { + if (options.highlight) { + return data.template + ? options.highlightTemplate(text, data.template) + : options.highlightText(text); + } + + return text; +} + +function getIconTextBuilder(text, data, options) { + if (options.icon) { + const wrappedText = `<span>${text}</span>`; + return data.icon ? `${data.icon}${wrappedText}` : wrappedText; + } + + return text; +} + +function getLinkText(data, options) { + const text = getPropertyWithDefault(data, options, 'text'); + + return [getHighlightTextBuilder, getIconTextBuilder].reduce( + (acc, fn) => fn(acc, data, options), + text, + ); +} + +function escape(text) { + return text ? String(text).replace(/'/g, "\\'") : text; +} + +function getOptionValue(data, options) { + if (options.renderRow) { + return undefined; + } + + return escape(options.id ? options.id(data) : data.id); +} + +function shouldHide(data, { options }) { + const value = getOptionValue(data, options); + + return options.hideRow && options.hideRow(value); +} + +function hideElement(element) { + element.style.display = 'none'; + + return element; +} + +function checkSelected(data, options) { + const value = getOptionValue(data, options); + + if (!options.parent) { + return !data.id; + } else if (value) { + return ( + options.parent.querySelector(`input[name='${options.fieldName}'][value='${value}']`) != null + ); + } + + return options.parent.querySelector(`input[name='${options.fieldName}']`) == null; +} + +function createLink(url, selected, options) { + const link = document.createElement('a'); + + link.href = url; + + if (options.icon) { + link.classList.add('d-flex', 'align-items-center'); + } + + link.classList.toggle('is-active', selected); + + return link; +} + +function assignTextToLink(el, data, options) { + const text = getLinkText(data, options); + + if (options.icon || options.highlight) { + el.innerHTML = text; + } else { + el.textContent = text; + } + + return el; +} + +function renderLink(row, data, { options, group, index }) { + const selected = checkSelected(data, options); + const url = getPropertyWithDefault(data, options, 'url', '#'); + const link = createLink(url, selected, options); + + assignTextToLink(link, data, options); + + if (group) { + link.dataset.group = group; + link.dataset.index = index; + } + + row.appendChild(link); + + return row; +} + +function getOptionRenderer({ options, instance }) { + return options.renderRow && ((li, data) => options.renderRow(data, instance)); +} + +function getRenderer(data, params) { + return renderersByType[data.type] || getOptionRenderer(params) || renderLink; +} + +export default function item({ data, ...params }) { + const renderer = getRenderer(data, params); + const li = document.createElement('li'); + + if (shouldHide(data, params)) { + hideElement(li); + } + + return renderer(li, data, params); +} diff --git a/app/assets/javascripts/groups/transfer_dropdown.js b/app/assets/javascripts/groups/transfer_dropdown.js index cec824a529c..e94b163dfb1 100644 --- a/app/assets/javascripts/groups/transfer_dropdown.js +++ b/app/assets/javascripts/groups/transfer_dropdown.js @@ -14,7 +14,7 @@ export default class TransferDropdown { } buildDropdown() { - const extraOptions = [{ id: '-1', text: __('No parent group') }, 'divider']; + const extraOptions = [{ id: '-1', text: __('No parent group') }, { type: 'divider' }]; this.groupDropdown.glDropdown({ selectable: true, diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue index 132e50e5715..8cda7dac51f 100644 --- a/app/assets/javascripts/jobs/components/environments_block.vue +++ b/app/assets/javascripts/jobs/components/environments_block.vue @@ -79,7 +79,9 @@ export default { default: break; } - return environmentText; + return environmentText && this.hasCluster + ? `${environmentText} ${this.clusterText}` + : environmentText; }, environmentLink() { if (this.hasEnvironment) { @@ -109,6 +111,37 @@ export default { ? this.lastDeployment.deployable.build_path : ''; }, + hasCluster() { + return this.hasLastDeployment && this.lastDeployment.cluster; + }, + clusterNameOrLink() { + if (!this.hasCluster) { + return ''; + } + + const { name, path } = this.lastDeployment.cluster; + const escapedName = _.escape(name); + const escapedPath = _.escape(path); + + if (!escapedPath) { + return escapedName; + } + + return sprintf( + '%{startLink}%{name}%{endLink}', + { + startLink: `<a href="${escapedPath}" class="js-job-cluster-link">`, + name: escapedName, + endLink: '</a>', + }, + false, + ); + }, + clusterText() { + return this.hasCluster + ? sprintf(__('Cluster %{cluster} was used.'), { cluster: this.clusterNameOrLink }, false) + : ''; + }, }, methods: { deploymentLink(name) { diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index f50a6e3b19d..177aa02b8e0 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -231,7 +231,7 @@ export default class LabelsSelect { }); } if (extraData.length) { - extraData.push('divider'); + extraData.push({ type: 'divider' }); data = extraData.concat(data); } } @@ -243,7 +243,7 @@ export default class LabelsSelect { }) .catch(() => flash(__('Error fetching labels.'))); }, - renderRow: function(label, instance) { + renderRow: function(label) { var linkEl, listItemEl, color, diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 43949d5cc86..8f077685b07 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -100,7 +100,7 @@ export default class MilestoneSelect { }); } if (extraOptions.length) { - extraOptions.push('divider'); + extraOptions.push({ type: 'divider' }); } callback(extraOptions.concat(data)); diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 03d349ac714..2ef081837e6 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -34,7 +34,7 @@ export default class NamespaceSelect { id: null, }; namespaces.unshift(anyNamespace); - namespaces.splice(1, 0, 'divider'); + namespaces.splice(1, 0, { type: 'divider' }); } return dataCallback(namespaces); }); diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index 0d377eb9c68..95936c2d1db 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', () => { const skippable = parseBoolean(twoFactorNode.dataset.twoFactorSkippable); if (skippable) { - const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; + const button = `<a class="btn btn-sm btn-warning float-right" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; const flashAlert = document.querySelector('.flash-alert'); if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button); } diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index ea867d30ce8..89cac42abae 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -215,6 +215,7 @@ export default { :disabled="!canChangeVisibilityLevel" name="project[visibility_level]" class="form-control select-control" + data-qa-selector="project_visibility_dropdown" > <option :value="visibilityOptions.PRIVATE" diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js index 86ec78e1df8..8f6c48ab065 100644 --- a/app/assets/javascripts/pages/search/show/search.js +++ b/app/assets/javascripts/pages/search/show/search.js @@ -30,7 +30,7 @@ export default class Search { data.unshift({ full_name: __('Any'), }); - data.splice(1, 0, 'divider'); + data.splice(1, 0, { type: 'divider' }); return callback(data); }); }, @@ -57,7 +57,7 @@ export default class Search { data.unshift({ name_with_namespace: __('Any'), }); - data.splice(1, 0, 'divider'); + data.splice(1, 0, { type: 'divider' }); return data; }) diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 842fb5e5b4f..510a2441924 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -191,13 +191,14 @@ export class SearchAutocomplete { // Add group header before list each group if (lastCategory !== suggestion.category) { if (!firstCategory) { - data.push('separator'); + data.push({ type: 'separator' }); } if (firstCategory) { firstCategory = false; } data.push({ - header: suggestion.category, + type: 'header', + content: suggestion.category, }); lastCategory = suggestion.category; } @@ -221,7 +222,7 @@ export class SearchAutocomplete { template = s__('SearchAutocomplete|in this group'); } - data.unshift('separator'); + data.unshift({ type: 'separator' }); data.unshift({ icon, text: term, @@ -271,7 +272,8 @@ export class SearchAutocomplete { if (name) { baseItems.push({ - header: `${name}`, + type: 'header', + content: `${name}`, }); } diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 12c939aa70f..57efde7f027 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -333,7 +333,7 @@ function UsersSelect(currentUser, els, options = {}) { } if (showDivider) { - users.splice(showDivider, 0, 'divider'); + users.splice(showDivider, 0, { type: 'divider' }); } if ($dropdown.hasClass('js-multiselect')) { @@ -343,7 +343,8 @@ function UsersSelect(currentUser, els, options = {}) { if ($dropdown.data('dropdownHeader')) { showDivider += 1; users.splice(showDivider, 0, { - header: $dropdown.data('dropdownHeader'), + type: 'header', + content: $dropdown.data('dropdownHeader'), }); } @@ -358,7 +359,7 @@ function UsersSelect(currentUser, els, options = {}) { users.splice(showDivider, 0, selectedUser); }); - users.splice(showDivider + 1, 0, 'divider'); + users.splice(showDivider + 1, 0, { type: 'divider' }); } } } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 4b5201bbca7..52acd1de666 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -95,7 +95,6 @@ export default { }, }; </script> - <template> <div class="ci-widget media js-ci-widget"> <template v-if="!hasPipeline || hasCIError"> @@ -157,6 +156,7 @@ export default { </div> </template> </span> + <linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" /> </span> </div> diff --git a/app/assets/javascripts/vue_shared/components/gl_countdown.vue b/app/assets/javascripts/vue_shared/components/gl_countdown.vue index c1aace31fb2..4aae3549601 100644 --- a/app/assets/javascripts/vue_shared/components/gl_countdown.vue +++ b/app/assets/javascripts/vue_shared/components/gl_countdown.vue @@ -9,6 +9,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + props: { endDateString: { type: String, diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue index 4dcc121496c..c1f3d86335a 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue @@ -77,12 +77,11 @@ export default { </script> <template> - <div class="block sidebar-grouped-item"> + <div class="block sidebar-grouped-item gl-cursor-pointer" role="button" @click="toggleSidebar"> <collapsed-calendar-icon v-if="showMinDateBlock" :container-class="iconClass" :tooltip-text="tooltipText('min')" - @click="toggleSidebar" > <span class="sidebar-collapsed-value"> <span v-if="showFromText">{{ __('From') }}</span> <span>{{ dateText('min') }}</span> @@ -93,7 +92,6 @@ export default { v-if="maxDate" :container-class="iconClass" :tooltip-text="tooltipText('max')" - @click="toggleSidebar" > <span class="sidebar-collapsed-value"> <span v-if="!minDate">{{ __('Until') }}</span> <span>{{ dateText('max') }}</span> |