diff options
Diffstat (limited to 'app')
48 files changed, 226 insertions, 214 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js index a303e504cc7..55c68139ded 100644 --- a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js +++ b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js @@ -1,11 +1,11 @@ import $ from 'jquery'; -import { convertPermissionToBoolean } from '~/lib/utils/common_utils'; +import { parseBoolean } from '~/lib/utils/common_utils'; import GfmAutoComplete from '~/gfm_auto_complete'; export default function initGFMInput() { $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); - const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete); + const enableGFM = parseBoolean(el.dataset.supportsAutocomplete); gfm.setup($(el), { emojis: true, diff --git a/app/assets/javascripts/behaviors/secret_values.js b/app/assets/javascripts/behaviors/secret_values.js index f6bf62d734e..5b92608d536 100644 --- a/app/assets/javascripts/behaviors/secret_values.js +++ b/app/assets/javascripts/behaviors/secret_values.js @@ -1,5 +1,5 @@ import { n__ } from '../locale'; -import { convertPermissionToBoolean } from '../lib/utils/common_utils'; +import { parseBoolean } from '../lib/utils/common_utils'; export default class SecretValues { constructor({ @@ -16,7 +16,7 @@ export default class SecretValues { this.revealButton = this.container.querySelector('.js-secret-value-reveal-button'); if (this.revealButton) { - const isRevealed = convertPermissionToBoolean(this.revealButton.dataset.secretRevealStatus); + const isRevealed = parseBoolean(this.revealButton.dataset.secretRevealStatus); this.updateDom(isRevealed); this.revealButton.addEventListener('click', this.onRevealButtonClicked.bind(this)); @@ -24,9 +24,7 @@ export default class SecretValues { } onRevealButtonClicked() { - const previousIsRevealed = convertPermissionToBoolean( - this.revealButton.dataset.secretRevealStatus, - ); + const previousIsRevealed = parseBoolean(this.revealButton.dataset.secretRevealStatus); this.updateDom(!previousIsRevealed); } diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 8b5a3c1c69d..eade1283513 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -4,6 +4,7 @@ import Mousetrap from 'mousetrap'; import axios from '../../lib/utils/axios_utils'; import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility'; import findAndFollowLink from '../../lib/utils/navigation_utility'; +import { parseBoolean } from '~/lib/utils/common_utils'; const defaultStopCallback = Mousetrap.stopCallback; Mousetrap.stopCallback = (e, element, combo) => { @@ -61,7 +62,7 @@ export default class Shortcuts { static onTogglePerfBar(e) { e.preventDefault(); const performanceBarCookieName = 'perf_bar_enabled'; - if (Cookies.get(performanceBarCookieName) === 'true') { + if (parseBoolean(Cookies.get(performanceBarCookieName))) { Cookies.set(performanceBarCookieName, 'false', { path: '/' }); } else { Cookies.set(performanceBarCookieName, 'true', { path: '/' }); diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index ec27ae8c291..9f547471170 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -16,9 +16,17 @@ export default () => { const filePath = editBlobForm.data('blobFilename'); const currentAction = $('.js-file-title').data('currentAction'); const projectId = editBlobForm.data('project-id'); + const commitButton = $('.js-commit-button'); + + commitButton.on('click', () => { + window.onbeforeunload = null; + }); new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId); new NewCommitForm(editBlobForm); + + // returning here blocks page navigation + window.onbeforeunload = () => ''; } if (uploadBlobForm.length) { diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 61a3072ac27..f88e9b55988 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -24,7 +24,7 @@ import BoardSidebar from './components/board_sidebar'; import initNewListDropdown from './components/new_list_dropdown'; import BoardAddIssuesModal from './components/modal/index.vue'; import '~/vue_shared/vue_resource_interceptor'; -import { NavigationType } from '~/lib/utils/common_utils'; +import { NavigationType, parseBoolean } from '~/lib/utils/common_utils'; let issueBoardsApp; @@ -60,7 +60,7 @@ export default () => { boardsEndpoint: $boardApp.dataset.boardsEndpoint, listsEndpoint: $boardApp.dataset.listsEndpoint, boardId: $boardApp.dataset.boardId, - disabled: $boardApp.dataset.disabled === 'true', + disabled: parseBoolean($boardApp.dataset.disabled), issueLinkBase: $boardApp.dataset.issueLinkBase, rootPath: $boardApp.dataset.rootPath, bulkUpdatePath: $boardApp.dataset.bulkUpdatePath, diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index eefe14a1d79..cf88a973d33 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -5,7 +5,7 @@ import $ from 'jquery'; import _ from 'underscore'; import Vue from 'vue'; import Cookies from 'js-cookie'; -import { getUrlParamsArray } from '~/lib/utils/common_utils'; +import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils'; const boardsStore = { disabled: false, @@ -78,7 +78,7 @@ const boardsStore = { }); }, welcomeIsHidden() { - return Cookies.get('issue_board_welcome_hidden') === 'true'; + return parseBoolean(Cookies.get('issue_board_welcome_hidden')); }, removeList(id, type = 'blank') { const list = this.findList('id', id, type); diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js index 97a1645aa51..b2c88e8c14e 100644 --- a/app/assets/javascripts/build_artifacts.js +++ b/app/assets/javascripts/build_artifacts.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import { visitUrl } from './lib/utils/url_utility'; -import { convertPermissionToBoolean } from './lib/utils/common_utils'; +import { parseBoolean } from './lib/utils/common_utils'; export default class BuildArtifacts { constructor() { @@ -22,7 +22,7 @@ export default class BuildArtifacts { // eslint-disable-next-line class-methods-use-this setupEntryClick() { return $('.tree-holder').on('click', 'tr[data-link]', function() { - visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink)); + visitUrl(this.dataset.link, parseBoolean(this.dataset.externalLink)); }); } // eslint-disable-next-line class-methods-use-this diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js index dffabbfe1b8..592e1fd1c31 100644 --- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js +++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import axios from '../lib/utils/axios_utils'; import { s__ } from '../locale'; import Flash from '../flash'; -import { convertPermissionToBoolean } from '../lib/utils/common_utils'; +import { parseBoolean } from '../lib/utils/common_utils'; import statusCodes from '../lib/utils/http_status'; import VariableList from './ci_variable_list'; @@ -101,7 +101,7 @@ export default class AjaxVariableList { // If we submitted a row that was destroyed, remove it so we don't try // to destroy it again which would cause a BE error const destroyInput = row.querySelector('.js-ci-variable-input-destroy'); - if (convertPermissionToBoolean(destroyInput.value)) { + if (parseBoolean(destroyInput.value)) { row.remove(); // Update the ID input so any future edits and `_destroy` will apply on the BE } else { diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js index 7bdc18ce03e..ee0f7cda189 100644 --- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js +++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import { convertPermissionToBoolean } from '../lib/utils/common_utils'; +import { parseBoolean } from '../lib/utils/common_utils'; import { s__ } from '../locale'; import setupToggleButtons from '../toggle_buttons'; import CreateItemDropdown from '../create_item_dropdown'; @@ -150,7 +150,7 @@ export default class VariableList { removeRow(row) { const $row = $(row); - const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted')); + const isPersisted = parseBoolean($row.attr('data-is-persisted')); if (isPersisted) { $row.hide(); diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 8354c28778c..a37cb4def28 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -168,6 +168,9 @@ export default { knativeInstalled() { return this.applications.knative.status === APPLICATION_STATUS.INSTALLED; }, + knativeExternalIp() { + return this.applications.knative.externalIp; + }, }, created() { this.helmInstallIllustration = helmInstallIllustration; @@ -408,12 +411,11 @@ export default { <div slot="description"> <p> {{ - s__(`ClusterIntegration|A Knative build extends Kubernetes - and utilizes existing Kubernetes primitives to provide you with - the ability to run on-cluster container builds from source. - For example, you can write a build that uses Kubernetes-native - resources to obtain your source code from a repository, - build it into container a image, and then run that image.`) + s__(`ClusterIntegration|Knative (pronounced kay-nay-tiv) extends + Kubernetes to provide a set of middleware components that are + essential to build modern, source-centric, and container-based + applications that can run anywhere: on premises, in the cloud, or + even in a third-party data center.`) }} </p> @@ -444,6 +446,49 @@ export default { /> </div> </template> + <template v-if="knativeInstalled"> + <div class="form-group"> + <label for="knative-ip-address"> + {{ s__('ClusterIntegration|Knative IP Address:') }} + </label> + <div v-if="knativeExternalIp" class="input-group"> + <input + id="knative-ip-address" + :value="knativeExternalIp" + type="text" + class="form-control js-ip-address" + readonly + /> + <span class="input-group-append"> + <clipboard-button + :text="knativeExternalIp" + :title="s__('ClusterIntegration|Copy Knative IP Address to clipboard')" + class="input-group-text js-clipboard-btn" + /> + </span> + </div> + <input v-else type="text" class="form-control js-ip-address" readonly value="?" /> + </div> + + <p v-if="!knativeExternalIp" class="settings-message js-no-ip-message"> + {{ + s__(`ClusterIntegration|The IP address is in + the process of being assigned. Please check your Kubernetes + cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) + }} + </p> + + <p> + {{ + s__(`ClusterIntegration|Point a wildcard DNS to this + generated IP address in order to access + your application after it has been deployed.`) + }} + <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer"> + {{ __('More information') }} + </a> + </p> + </template> </div> </application-row> </div> diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index 3678be59d24..2d69da8eaec 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -60,6 +60,7 @@ export default class ClusterStore { requestStatus: null, requestReason: null, hostname: null, + externalIp: null, }, }, }; @@ -111,6 +112,8 @@ export default class ClusterStore { } else if (appId === KNATIVE) { this.state.applications.knative.hostname = serverAppEntry.hostname || this.state.applications.knative.hostname; + this.state.applications.knative.externalIp = + serverAppEntry.external_ip || this.state.applications.knative.externalIp; } }); } diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js index dff0adba25a..10f02739ec8 100644 --- a/app/assets/javascripts/contextual_sidebar.js +++ b/app/assets/javascripts/contextual_sidebar.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import Cookies from 'js-cookie'; import _ from 'underscore'; import bp from './breakpoints'; +import { parseBoolean } from '~/lib/utils/common_utils'; export default class ContextualSidebar { constructor() { @@ -78,7 +79,7 @@ export default class ContextualSidebar { if (breakpoint === 'sm' || breakpoint === 'md') { this.toggleCollapsedSidebar(true); } else if (breakpoint === 'lg') { - const collapse = Cookies.get('sidebar_collapsed') === 'true'; + const collapse = parseBoolean(Cookies.get('sidebar_collapsed')); this.toggleCollapsedSidebar(collapse); } } diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index ec4a4aa1d6d..f40a7b25fde 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -1,7 +1,7 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; import { GlTooltipDirective } from '@gitlab/ui'; -import { convertPermissionToBoolean } from '~/lib/utils/common_utils'; +import { parseBoolean } from '~/lib/utils/common_utils'; import Icon from '~/vue_shared/components/icon.vue'; import FileRow from '~/vue_shared/components/file_row.vue'; import FileRowStats from './file_row_stats.vue'; @@ -18,8 +18,7 @@ export default { }, data() { const treeListStored = localStorage.getItem(treeListStorageKey); - const renderTreeList = - treeListStored !== null ? convertPermissionToBoolean(treeListStored) : true; + const renderTreeList = treeListStored !== null ? parseBoolean(treeListStored) : true; return { search: '', diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js index 085e255f1d3..2f59a3822f4 100644 --- a/app/assets/javascripts/diffs/store/modules/diff_state.js +++ b/app/assets/javascripts/diffs/store/modules/diff_state.js @@ -1,6 +1,7 @@ import Cookies from 'js-cookie'; import { getParameterValues } from '~/lib/utils/url_utility'; import bp from '~/breakpoints'; +import { parseBoolean } from '~/lib/utils/common_utils'; import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants'; const viewTypeFromQueryString = getParameterValues('view')[0]; @@ -22,7 +23,7 @@ export default () => ({ tree: [], treeEntries: {}, showTreeList: - storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : storedTreeShow === 'true', + storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : parseBoolean(storedTreeShow), currentDiffFileId: '', projectPath: '', commentForms: [], diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index f044d31c776..3cf6e4ad14d 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import environmentsFolderApp from './environments_folder_view.vue'; -import { convertPermissionToBoolean } from '../../lib/utils/common_utils'; +import { parseBoolean } from '../../lib/utils/common_utils'; import Translate from '../../vue_shared/translate'; Vue.use(Translate); @@ -18,8 +18,8 @@ export default () => endpoint: environmentsData.endpoint, folderName: environmentsData.folderName, cssContainerClass: environmentsData.cssClass, - canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment), - canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment), + canCreateDeployment: parseBoolean(environmentsData.canCreateDeployment), + canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment), }; }, render(createElement) { diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js index 5b6833fb15d..d366e7550b7 100644 --- a/app/assets/javascripts/environments/index.js +++ b/app/assets/javascripts/environments/index.js @@ -1,6 +1,6 @@ import Vue from 'vue'; import environmentsComponent from './components/environments_app.vue'; -import { convertPermissionToBoolean } from '../lib/utils/common_utils'; +import { parseBoolean } from '../lib/utils/common_utils'; import Translate from '../vue_shared/translate'; Vue.use(Translate); @@ -19,9 +19,9 @@ export default () => newEnvironmentPath: environmentsData.newEnvironmentPath, helpPagePath: environmentsData.helpPagePath, cssContainerClass: environmentsData.cssClass, - canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment), - canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment), - canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment), + canCreateEnvironment: parseBoolean(environmentsData.canCreateEnvironment), + canCreateDeployment: parseBoolean(environmentsData.canCreateDeployment), + canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment), }; }, render(createElement) { diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js index 0f68f05b523..928f1fe409f 100644 --- a/app/assets/javascripts/groups/index.js +++ b/app/assets/javascripts/groups/index.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; import Translate from '../vue_shared/translate'; import GroupFilterableList from './groups_filterable_list'; import GroupsStore from './store/groups_store'; @@ -38,7 +39,7 @@ export default (containerId = 'js-groups-tree', endpoint, action = '') => { }, data() { const { dataset } = dataEl || this.$options.el; - const hideProjects = dataset.hideProjects === 'true'; + const hideProjects = parseBoolean(dataset.hideProjects); const service = new GroupsService(endpoint || dataset.endpoint); const store = new GroupsStore(hideProjects); diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index 175d0b8498b..2fa7a219ea8 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -4,6 +4,7 @@ import Translate from '~/vue_shared/translate'; import { highCountTrim } from '~/lib/utils/text_utility'; import SetStatusModalTrigger from './set_status_modal/set_status_modal_trigger.vue'; import SetStatusModalWrapper from './set_status_modal/set_status_modal_wrapper.vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; /** * Updates todo counter when todos are toggled. @@ -36,7 +37,7 @@ document.addEventListener('DOMContentLoaded', () => { const { hasStatus } = this.$options.el.dataset; return { - hasStatus: hasStatus === 'true', + hasStatus: parseBoolean(hasStatus), }; }, render(createElement) { diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index 7a5a227db30..fbf944499d5 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -4,7 +4,7 @@ import Translate from '~/vue_shared/translate'; import ide from './components/ide.vue'; import store from './stores'; import router from './ide_router'; -import { convertPermissionToBoolean } from '../lib/utils/common_utils'; +import { parseBoolean } from '../lib/utils/common_utils'; Vue.use(Translate); @@ -40,7 +40,7 @@ export function initIde(el, options = {}) { webIDEHelpPagePath: el.dataset.webIdeHelpPagePath, }); this.setInitialData({ - clientsidePreviewEnabled: convertPermissionToBoolean(el.dataset.clientsidePreviewEnabled), + clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled), ...extraInitialData(el), }); }, diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index f1beb1a8ea5..1ffd5c61282 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -3,7 +3,7 @@ import _ from 'underscore'; import { __, sprintf } from './locale'; import axios from './lib/utils/axios_utils'; import flash from './flash'; -import { convertPermissionToBoolean } from './lib/utils/common_utils'; +import { parseBoolean } from './lib/utils/common_utils'; class ImporterStatus { constructor({ jobsUrl, importUrl, ciCdOnly }) { @@ -141,7 +141,7 @@ function initImporterStatus() { return new ImporterStatus({ jobsUrl: data.jobsImportPath, importUrl: data.importPath, - ciCdOnly: convertPermissionToBoolean(data.ciCdOnly), + ciCdOnly: parseBoolean(data.ciCdOnly), }); } } diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/landing.js index 8c0950ad5d5..bfb4d9ce67b 100644 --- a/app/assets/javascripts/landing.js +++ b/app/assets/javascripts/landing.js @@ -1,4 +1,5 @@ import Cookies from 'js-cookie'; +import { parseBoolean } from '~/lib/utils/common_utils'; class Landing { constructor(landingElement, dismissButton, cookieName) { @@ -30,7 +31,7 @@ class Landing { } isDismissed() { - return Cookies.get(this.cookieName) === 'true'; + return parseBoolean(Cookies.get(this.cookieName)); } } diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js index af50ea9d6c2..ee01a73a6e8 100644 --- a/app/assets/javascripts/lazy_loader.js +++ b/app/assets/javascripts/lazy_loader.js @@ -91,7 +91,9 @@ export default class LazyLoader { onIntersection = entries => { entries.forEach(entry => { - if (entry.isIntersecting) { + // We are using `intersectionRatio > 0` over `isIntersecting`, as some browsers did not ship the latter + // See: https://gitlab.com/gitlab-org/gitlab-ce/issues/54407 + if (entry.intersectionRatio > 0) { this.intersectionObserver.unobserve(entry.target); this.lazyImages.push(entry.target); } diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 3186ae9c133..040d0bc659e 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -421,12 +421,28 @@ export const historyPushState = newUrl => { }; /** + * Returns true for a String "true" and false otherwise. + * This is the opposite of Boolean(...).toString() + * + * @param {String} value + * @returns {Boolean} + */ +export const parseBoolean = value => value === 'true'; + +/** * Converts permission provided as strings to booleans. * * @param {String} string * @returns {Boolean} */ -export const convertPermissionToBoolean = permission => permission === 'true'; +export const convertPermissionToBoolean = permission => { + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line no-console + console.warn('convertPermissionToBoolean is deprecated! Please use parseBoolean instead.'); + } + + return parseBoolean(permission); +}; /** * Back Off exponential algorithm diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index d8255181574..28148319c41 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -8,7 +8,12 @@ import flash from './flash'; import BlobForkSuggestion from './blob/blob_fork_suggestion'; import initChangesDropdown from './init_changes_dropdown'; import bp from './breakpoints'; -import { parseUrlPathname, handleLocationHash, isMetaClick } from './lib/utils/common_utils'; +import { + parseUrlPathname, + handleLocationHash, + isMetaClick, + parseBoolean, +} from './lib/utils/common_utils'; import { isInVueNoteablePage } from './lib/utils/dom_utils'; import { getLocationHash } from './lib/utils/url_utility'; import Diff from './diff'; @@ -440,7 +445,7 @@ export default class MergeRequestTabs { // Expand the issuable sidebar unless the user explicitly collapsed it expandView() { - if (Cookies.get('collapsed_gutter') === 'true') { + if (parseBoolean(Cookies.get('collapsed_gutter'))) { return; } const $gutterIcon = $('.js-sidebar-toggle i:visible'); diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue index 23e1e734b37..0e141d02ead 100644 --- a/app/assets/javascripts/monitoring/components/empty_state.vue +++ b/app/assets/javascripts/monitoring/components/empty_state.vue @@ -44,9 +44,9 @@ export default { title: 'Get started with performance monitoring', description: `Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments.`, - buttonText: 'Install Prometheus on clusters', + buttonText: 'Install on clusters', buttonPath: this.clustersPath, - secondaryButtonText: 'Configure existing Prometheus', + secondaryButtonText: 'Configure existing installation', secondaryButtonPath: this.settingsPath, }, loading: { @@ -88,26 +88,32 @@ export default { </script> <template> - <div class="prometheus-state"> - <div class="state-svg svg-content"><img :src="currentState.svgUrl" /></div> - <h4 class="state-title">{{ currentState.title }}</h4> - <p class="state-description"> - {{ currentState.description }} - <a v-if="showButtonDescription" :href="settingsPath"> Prometheus server </a> - </p> - <div class="state-button"> - <a v-if="currentState.buttonPath" :href="currentState.buttonPath" class="btn btn-success"> - {{ currentState.buttonText }} - </a> + <div class="row empty-state js-empty-state"> + <div class="col-12"> + <div class="state-svg svg-content"><img :src="currentState.svgUrl" /></div> </div> - <div class="state-button"> - <a - v-if="currentState.secondaryButtonPath" - :href="currentState.secondaryButtonPath" - class="btn" - > - {{ currentState.secondaryButtonText }} - </a> + + <div class="col-12"> + <div class="text-content"> + <h4 class="state-title text-center">{{ currentState.title }}</h4> + <p class="state-description"> + {{ currentState.description }} + <a v-if="showButtonDescription" :href="settingsPath"> Prometheus server </a> + </p> + + <div class="text-center"> + <a v-if="currentState.buttonPath" :href="currentState.buttonPath" class="btn btn-success"> + {{ currentState.buttonText }} + </a> + <a + v-if="currentState.secondaryButtonPath" + :href="currentState.secondaryButtonPath" + class="btn" + > + {{ currentState.secondaryButtonText }} + </a> + </div> + </div> </div> </div> </template> diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js index 41270e015d4..9d78b5ea110 100644 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import { convertPermissionToBoolean } from '~/lib/utils/common_utils'; +import { parseBoolean } from '~/lib/utils/common_utils'; import Dashboard from './components/dashboard.vue'; export default () => { @@ -13,7 +13,7 @@ export default () => { return createElement(Dashboard, { props: { ...el.dataset, - hasMetrics: convertPermissionToBoolean(el.dataset.hasMetrics), + hasMetrics: parseBoolean(el.dataset.hasMetrics), }, }); }, diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index cba6759ebf5..ee1a5274ff7 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -3,10 +3,11 @@ import $ from 'jquery'; import Api from './api'; import { mergeUrlParams } from './lib/utils/url_utility'; +import { parseBoolean } from '~/lib/utils/common_utils'; export default class NamespaceSelect { constructor(opts) { - const isFilter = opts.dropdown.dataset.isFilter === 'true'; + const isFilter = parseBoolean(opts.dropdown.dataset.isFilter); const fieldName = opts.dropdown.dataset.fieldName || 'namespace_id'; $(opts.dropdown).glDropdown({ diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js index d9cf62db3f7..674b807edbe 100644 --- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js +++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import { truncate } from '../../../lib/utils/text_utility'; +import { parseBoolean } from '~/lib/utils/common_utils'; const MAX_MESSAGE_LENGTH = 500; const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; @@ -26,7 +27,7 @@ export default class AbuseReports { const $messageCellElement = $(this); const originalMessage = $messageCellElement.data('originalMessage'); if (!originalMessage) return; - if ($messageCellElement.data('messageTruncated') === 'true') { + if (parseBoolean($messageCellElement.data('messageTruncated'))) { $messageCellElement.data('messageTruncated', 'false'); $messageCellElement.text(originalMessage); } else { 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 417935e2ad0..10cd8ecfbc9 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -1,9 +1,10 @@ import $ from 'jquery'; import U2FRegister from '~/u2f/register'; +import { parseBoolean } from '~/lib/utils/common_utils'; document.addEventListener('DOMContentLoaded', () => { const twoFactorNode = document.querySelector('.js-two-factor-auth'); - const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true'; + 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 diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue index 1edd076604c..22512a6f12a 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue @@ -3,6 +3,7 @@ import Vue from 'vue'; import Cookies from 'js-cookie'; import Translate from '../../../../../vue_shared/translate'; import illustrationSvg from '../icons/intro_illustration.svg'; +import { parseBoolean } from '~/lib/utils/common_utils'; Vue.use(Translate); @@ -13,7 +14,7 @@ export default { data() { return { docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl, - calloutDismissed: Cookies.get(cookieKey) === 'true', + calloutDismissed: parseBoolean(Cookies.get(cookieKey)), }; }, created() { diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js index fc337a7609b..fd72d2ddbe0 100644 --- a/app/assets/javascripts/pages/projects/pipelines/index/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import PipelinesStore from '../../../../pipelines/stores/pipelines_store'; import pipelinesComponent from '../../../../pipelines/components/pipelines.vue'; import Translate from '../../../../vue_shared/translate'; -import { convertPermissionToBoolean } from '../../../../lib/utils/common_utils'; +import { parseBoolean } from '../../../../lib/utils/common_utils'; Vue.use(Translate); @@ -33,8 +33,8 @@ document.addEventListener( noPipelinesSvgPath: this.dataset.noPipelinesSvgPath, autoDevopsPath: this.dataset.helpAutoDevopsPath, newPipelinePath: this.dataset.newPipelinePath, - canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline), - hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi), + canCreatePipeline: parseBoolean(this.dataset.canCreatePipeline), + hasGitlabCi: parseBoolean(this.dataset.hasGitlabCi), ciLintPath: this.dataset.ciLintPath, resetCachePath: this.dataset.resetCachePath, }, diff --git a/app/assets/javascripts/performance_bar/services/performance_bar_service.js b/app/assets/javascripts/performance_bar/services/performance_bar_service.js index 3a496fa2ed8..d8c23c82f7f 100644 --- a/app/assets/javascripts/performance_bar/services/performance_bar_service.js +++ b/app/assets/javascripts/performance_bar/services/performance_bar_service.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import _ from 'underscore'; import axios from '../../lib/utils/axios_utils'; +import { parseBoolean } from '~/lib/utils/common_utils'; let vueResourceInterceptor; @@ -41,7 +42,8 @@ export default class PerformanceBarService { // Vue Resource. const requestUrl = (response.config || response).url; const apiRequest = requestUrl && requestUrl.match(/^\/api\//); - const cachedResponse = response.headers && response.headers['x-gitlab-from-cache'] === 'true'; + const cachedResponse = + response.headers && parseBoolean(response.headers['x-gitlab-from-cache']); const fireCallback = requestUrl !== peekUrl && requestId && !apiRequest && !cachedResponse; return [fireCallback, requestId, requestUrl]; diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index 8704a655b28..deacff5abe7 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -1,6 +1,7 @@ import $ from 'jquery'; import axios from '~/lib/utils/axios_utils'; import flash from '../flash'; +import { parseBoolean } from '~/lib/utils/common_utils'; export default class Profile { constructor({ form } = {}) { @@ -80,7 +81,7 @@ export default class Profile { setRepoRadio() { const multiEditRadios = $('input[name="user[multi_file]"]'); - if (this.newRepoActivated || this.newRepoActivated === 'true') { + if (parseBoolean(this.newRepoActivated)) { multiEditRadios.filter('[value=on]').prop('checked', true); } else { multiEditRadios.filter('[value=off]').prop('checked', true); diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js index d83ffc7e211..bcb44bf7acf 100644 --- a/app/assets/javascripts/toggle_buttons.js +++ b/app/assets/javascripts/toggle_buttons.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import Flash from './flash'; import { __ } from './locale'; -import { convertPermissionToBoolean } from './lib/utils/common_utils'; +import { parseBoolean } from './lib/utils/common_utils'; /* example HAML: @@ -18,7 +18,7 @@ function updateToggle(toggle, isOn) { } function onToggleClicked(toggle, input, clickCallback) { - const previousIsOn = convertPermissionToBoolean(input.value); + const previousIsOn = parseBoolean(input.value); // Visually change the toggle and start loading updateToggle(toggle, !previousIsOn); @@ -51,7 +51,7 @@ export default function setupToggleButtons(container, clickCallback = () => {}) toggles.forEach(toggle => { const input = toggle.querySelector('.js-project-feature-toggle-input'); - const isOn = convertPermissionToBoolean(input.value); + const isOn = parseBoolean(input.value); // Get the visible toggle in sync with the hidden input updateToggle(toggle, isOn); diff --git a/app/assets/javascripts/usage_ping_consent.js b/app/assets/javascripts/usage_ping_consent.js index 05607f09a7e..d3d745a3c11 100644 --- a/app/assets/javascripts/usage_ping_consent.js +++ b/app/assets/javascripts/usage_ping_consent.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import axios from './lib/utils/axios_utils'; import Flash, { hideFlash } from './flash'; -import { convertPermissionToBoolean } from './lib/utils/common_utils'; +import { parseBoolean } from './lib/utils/common_utils'; export default () => { $('body').on('click', '.js-usage-consent-action', e => { @@ -11,8 +11,8 @@ export default () => { const { url, checkEnabled, pingEnabled } = e.target.dataset; const data = { application_setting: { - version_check_enabled: convertPermissionToBoolean(checkEnabled), - usage_ping_enabled: convertPermissionToBoolean(pingEnabled), + version_check_enabled: parseBoolean(checkEnabled), + usage_ping_enabled: parseBoolean(pingEnabled), }, }; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 347fcad771a..75166ffcada 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -203,21 +203,6 @@ stroke: $gray-darkest; } -.prometheus-state { - max-width: 460px; - margin: 10px auto; - text-align: center; - - .state-svg { - max-width: 80vw; - margin: 0 auto; - } - - .state-button { - padding: $gl-padding / 2; - } -} - .prometheus-graphs { .environments { .dropdown-menu-toggle { diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 78a11616d4c..e2879bfdcf1 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -37,13 +37,13 @@ module TreeHelper # Using Rails `*_path` methods can be slow, especially when generating # many paths, as with a repository tree that has thousands of items. def fast_project_blob_path(project, blob_path) - Addressable::URI.escape( + ActionDispatch::Journey::Router::Utils.escape_path( File.join(relative_url_root, project.path_with_namespace, 'blob', blob_path) ) end def fast_project_tree_path(project, tree_path) - Addressable::URI.escape( + ActionDispatch::Journey::Router::Utils.escape_path( File.join(relative_url_root, project.path_with_namespace, 'tree', tree_path) ) end diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index bd0286ee3f9..8f8790585a3 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -51,6 +51,10 @@ module Clusters ClusterWaitForIngressIpAddressWorker.perform_async(name, id) end + + def ingress_service + cluster.kubeclient.get_service('ingress-nginx-ingress-controller', Gitlab::Kubernetes::Helm::NAMESPACE) + end end end end diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index c66d5ce54db..c0aaa8dce20 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -6,9 +6,7 @@ module Clusters VERSION = '0.1.3'.freeze REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze - # This is required for helm version <= 2.10.x in order to support - # Setting up CRDs - ISTIO_CRDS = 'https://storage.googleapis.com/triggermesh-charts/istio-crds.yaml'.freeze + FETCH_IP_ADDRESS_DELAY = 30.seconds self.table_name = 'clusters_applications_knative' @@ -16,6 +14,16 @@ module Clusters include ::Clusters::Concerns::ApplicationStatus include ::Clusters::Concerns::ApplicationVersion include ::Clusters::Concerns::ApplicationData + include AfterCommitQueue + + state_machine :status do + before_transition any => [:installed] do |application| + application.run_after_commit do + ClusterWaitForIngressIpAddressWorker.perform_in( + FETCH_IP_ADDRESS_DELAY, application.name, application.id) + end + end + end default_value_for :version, VERSION @@ -36,19 +44,23 @@ module Clusters rbac: cluster.platform_kubernetes_rbac?, chart: chart, files: files, - repository: REPOSITORY, - preinstall: install_script + repository: REPOSITORY ) end - def client - cluster.platform_kubernetes.kubeclient.knative_client + def schedule_status_update + return unless installed? + return if external_ip + + ClusterWaitForIngressIpAddressWorker.perform_async(name, id) end - private + def ingress_service + cluster.kubeclient.get_service('knative-ingressgateway', 'istio-system') + end - def install_script - ["/usr/bin/kubectl apply -f #{ISTIO_CRDS}"] + def client + cluster.platform_kubernetes.kubeclient.knative_client end end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 6f1beede6f9..a3029a54604 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -313,7 +313,8 @@ class MergeRequestDiff < ActiveRecord::Base # merge_request_diff_commits.reload is preferred way to reload associated # objects but it returns cached result for some reason in this case - commits = merge_request_diff_commits(true) + # we can circumvent that by specifying that we need an uncached reload + commits = self.class.uncached { merge_request_diff_commits.reload } self.commits_count = commits.size end diff --git a/app/models/project.rb b/app/models/project.rb index 4d1917b9ab2..39978d8a4c4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -88,9 +88,6 @@ class Project < ActiveRecord::Base after_create :create_project_feature, unless: :project_feature - after_create -> { SiteStatistic.track(STATISTICS_ATTRIBUTE) } - before_destroy -> { SiteStatistic.untrack(STATISTICS_ATTRIBUTE) } - after_create :create_ci_cd_settings, unless: :ci_cd_settings, if: proc { ProjectCiCdSetting.available? } @@ -1394,7 +1391,7 @@ class Project < ActiveRecord::Base def change_head(branch) if repository.branch_exists?(branch) repository.before_change_head - repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}", shell: false) + repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}") repository.copy_gitattributes(branch) repository.after_change_head reload_default_branch diff --git a/app/models/repository.rb b/app/models/repository.rb index a77fa8f2ce7..427dac99b79 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -259,7 +259,7 @@ class Repository next if kept_around?(sha) # This will still fail if the file is corrupted (e.g. 0 bytes) - raw_repository.write_ref(keep_around_ref_name(sha), sha, shell: false) + raw_repository.write_ref(keep_around_ref_name(sha), sha) rescue Gitlab::Git::CommandError => ex Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" end diff --git a/app/models/site_statistic.rb b/app/models/site_statistic.rb deleted file mode 100644 index 3a7912ed53a..00000000000 --- a/app/models/site_statistic.rb +++ /dev/null @@ -1,76 +0,0 @@ -# frozen_string_literal: true - -class SiteStatistic < ActiveRecord::Base - # prevents the creation of multiple rows - default_value_for :id, 1 - - COUNTER_ATTRIBUTES = %w(repositories_count).freeze - REQUIRED_SCHEMA_VERSION = 20180629153018 - - # Tracks specific attribute - # - # @param [String] raw_attribute must be one of the values listed in COUNTER_ATTRIBUTES - def self.track(raw_attribute) - with_statistics_available(raw_attribute) do |attribute| - SiteStatistic.update_all(["#{attribute} = #{attribute}+1"]) - end - end - - # Untracks specific attribute - # - # @param [String] raw_attribute must be one of the values listed in COUNTER_ATTRIBUTES - def self.untrack(raw_attribute) - with_statistics_available(raw_attribute) do |attribute| - SiteStatistic.update_all(["#{attribute} = #{attribute}-1 WHERE #{attribute} > 0"]) - end - end - - # Wrapper for track/untrack operations with basic validations and enforced requirements - # - # @param [String] raw_attribute must be one of the values listed in COUNTER_ATTRIBUTES - # @yield [String] attribute quoted to be used inside SQL / Arel query - def self.with_statistics_available(raw_attribute) - unless raw_attribute.in?(COUNTER_ATTRIBUTES) - raise ArgumentError, "Invalid attribute: '#{raw_attribute}' to '#{caller_locations(1, 1)[0].label}' method. " \ - "Valid attributes are: #{COUNTER_ATTRIBUTES.join(', ')}" - end - - return unless available? - - self.fetch # make sure record exists - - attribute = self.connection.quote_column_name(raw_attribute) - - # will be running on its own transaction context - yield(attribute) - end - - # Returns a site statistic record with tracked information - # - # @return [SiteStatistic] record with tracked information - def self.fetch - transaction(requires_new: true) do - SiteStatistic.first_or_create! - end - rescue ActiveRecord::RecordNotUnique - retry - end - - # Return whether required schema change is available - # - # This is needed in order to degrade gracefully when testing schema migrations - # - # @return [Boolean] whether schema is available - def self.available? - @available_flag ||= ActiveRecord::Migrator.current_version >= REQUIRED_SCHEMA_VERSION - end - - # Resets cached column information - # - # This is called during schema migration specs, in order to reset internal cache state - def self.reset_column_information - @available_flag = nil - - super - end -end diff --git a/app/models/user.rb b/app/models/user.rb index 01eba7e0426..dbd754dd25a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -363,7 +363,7 @@ class User < ActiveRecord::Base from_users = from_users.confirmed if confirmed from_emails = joins(:emails).where(emails: { email: emails }) - from_emails = from_emails.confirmed if confirmed + from_emails = from_emails.confirmed.merge(Email.confirmed) if confirmed items = [from_users, from_emails] diff --git a/app/services/clusters/applications/check_ingress_ip_address_service.rb b/app/services/clusters/applications/check_ingress_ip_address_service.rb index f32e73e8b1c..0ec06e776a7 100644 --- a/app/services/clusters/applications/check_ingress_ip_address_service.rb +++ b/app/services/clusters/applications/check_ingress_ip_address_service.rb @@ -30,7 +30,7 @@ module Clusters def service strong_memoize(:ingress_service) do - kubeclient.get_service('ingress-nginx-ingress-controller', Gitlab::Kubernetes::Helm::NAMESPACE) + app.ingress_service end end end diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index e134f416c70..5cb8aebadb3 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -7,15 +7,14 @@ - if @group&.persisted? - create_group_project = can?(current_user, :create_projects, @group) - create_group_subgroup = can?(current_user, :create_subgroup, @group) + - if create_group_project || create_group_subgroup %li.dropdown-bold-header = _('This group') - if create_group_project - %li.header-new-group-project - = link_to _('New project'), new_project_path(namespace_id: @group.id) + %li= link_to _('New project'), new_project_path(namespace_id: @group.id) - if create_group_subgroup - %li - = link_to _('New subgroup'), new_group_path(parent_id: @group.id) + %li= link_to _('New subgroup'), new_group_path(parent_id: @group.id) %li.divider %li.dropdown-bold-header GitLab @@ -23,25 +22,20 @@ - create_project_issue = show_new_issue_link?(@project) - merge_project = merge_request_source_project_for_project(@project) - create_project_snippet = can?(current_user, :create_project_snippet, @project) + - if create_project_issue || merge_project || create_project_snippet %li.dropdown-bold-header = _('This project') - if create_project_issue - %li - = link_to _('New issue'), new_project_issue_path(@project) + %li= link_to _('New issue'), new_project_issue_path(@project) - if merge_project - %li - = link_to _('New merge request'), project_new_merge_request_path(merge_project) + %li= link_to _('New merge request'), project_new_merge_request_path(merge_project) - if create_project_snippet - %li.header-new-project-snippet - = link_to _('New snippet'), new_project_snippet_path(@project) + %li= link_to _('New snippet'), new_project_snippet_path(@project) %li.divider %li.dropdown-bold-header GitLab - if current_user.can_create_project? - %li - = link_to _('New project'), new_project_path, class: 'qa-global-new-project-link' + %li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link' - if current_user.can_create_group? - %li - = link_to _('New group'), new_group_path - %li - = link_to _('New snippet'), new_snippet_path + %li= link_to _('New group'), new_group_path + %li= link_to _('New snippet'), new_snippet_path diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 8181ee9eea1..af8887b0c39 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -13,7 +13,7 @@ .form-group.row = label_tag :branch_name, nil, class: 'col-form-label col-sm-2' .col-sm-10 - = text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name' + = text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name monospace' .form-text.text-muted.text-danger.js-branch-name-error .form-group.row = label_tag :ref, 'Create from', class: 'col-form-label col-sm-2' diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 953ab95735b..c0b410472eb 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -81,9 +81,9 @@ - todos_destroyer:todos_destroyer_confidential_issue - todos_destroyer:todos_destroyer_entity_leave +- todos_destroyer:todos_destroyer_group_private - todos_destroyer:todos_destroyer_project_private - todos_destroyer:todos_destroyer_private_features -- todos_destroyer:todos_destroyer_group_private - default - mailers # ActionMailer::DeliveryJob.queue_name |