diff options
Diffstat (limited to 'app')
61 files changed, 348 insertions, 533 deletions
diff --git a/app/assets/javascripts/organizations/profile/preferences/index.js b/app/assets/javascripts/organizations/profile/preferences/index.js index 0b0dd313cd8..11686b62eca 100644 --- a/app/assets/javascripts/organizations/profile/preferences/index.js +++ b/app/assets/javascripts/organizations/profile/preferences/index.js @@ -30,8 +30,8 @@ export const initHomeOrganizationSetting = () => { block: true, label: s__('Organization|Home organization'), description: s__('Organization|Choose what organization you want to see by default.'), - inputName: 'home_organization', - inputId: 'home_organization', + inputName: 'user[home_organization_id]', + inputId: 'user_home_organization_id', initialSelection, toggleClass: 'gl-form-input-xl', }, diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js index 71dc8c3d020..007b1454138 100644 --- a/app/assets/javascripts/persistent_user_callout.js +++ b/app/assets/javascripts/persistent_user_callout.js @@ -22,12 +22,17 @@ export default class PersistentUserCallout { init() { const followLink = this.container.querySelector('.js-follow-link'); + const closeAndFollowLink = this.container.querySelector('.js-close-and-follow-link'); if (this.closeButtons.length) { this.handleCloseButtonCallout(); } else if (followLink) { this.handleFollowLinkCallout(followLink); } + + if (closeAndFollowLink) { + this.handleFollowLinkCallout(closeAndFollowLink); + } } handleCloseButtonCallout() { diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js index 39c6d3bd14d..7420542a065 100644 --- a/app/assets/javascripts/persistent_user_callouts.js +++ b/app/assets/javascripts/persistent_user_callouts.js @@ -4,7 +4,6 @@ const PERSISTENT_USER_CALLOUTS = [ '.js-recovery-settings-callout', '.js-users-over-license-callout', '.js-admin-licensed-user-count-threshold', - '.js-buy-pipeline-minutes-notification-callout', '.js-token-expiry-callout', '.js-registration-enabled-callout', '.js-new-user-signups-cap-reached', diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue index 69ea29edc14..a9dca81b9f4 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue @@ -53,6 +53,9 @@ export default { projectKey: { default: '', }, + reopenIssueOnExternalParticipantNote: { + default: false, + }, addExternalParticipantsFromCc: { default: false, }, @@ -115,6 +118,7 @@ export default { fileTemplateProjectId, outgoingName, projectKey, + reopenIssueOnExternalParticipantNote, addExternalParticipantsFromCc, }) { this.isTemplateSaving = true; @@ -123,6 +127,7 @@ export default { issue_template_key: selectedTemplate, outgoing_name: outgoingName, project_key: projectKey, + reopen_issue_on_external_participant_note: reopenIssueOnExternalParticipantNote, add_external_participants_from_cc: addExternalParticipantsFromCc, service_desk_enabled: this.isEnabled, file_template_project_id: fileTemplateProjectId, @@ -195,6 +200,7 @@ export default { :initial-selected-file-template-project-id="selectedFileTemplateProjectId" :initial-outgoing-name="outgoingName" :initial-project-key="projectKey" + :initial-reopen-issue-on-external-participant-note="reopenIssueOnExternalParticipantNote" :initial-add-external-participants-from-cc="addExternalParticipantsFromCc" :templates="templates" :is-template-saving="isTemplateSaving" diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue index 891ad53dd10..2853a7d8d72 100644 --- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue +++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue @@ -23,6 +23,12 @@ export default { issueTrackerEnableMessage: __( 'To use Service Desk in this project, you must %{linkStart}activate the issue tracker%{linkEnd}.', ), + reopenIssueOnExternalParticipantNote: { + label: s__('ServiceDesk|Reopen issues when an external participant comments'), + help: s__( + 'ServiceDesk|This also adds an internal comment that mentions the assignees of the issue.', + ), + }, addExternalParticipantsFromCc: { label: s__('ServiceDesk|Add external participants from the %{codeStart}Cc%{codeEnd} header'), help: s__( @@ -91,6 +97,11 @@ export default { required: false, default: '', }, + initialReopenIssueOnExternalParticipantNote: { + type: Boolean, + required: false, + default: false, + }, initialAddExternalParticipantsFromCc: { type: Boolean, required: false, @@ -113,6 +124,7 @@ export default { selectedFileTemplateProjectId: this.initialSelectedFileTemplateProjectId, outgoingName: this.initialOutgoingName || __('GitLab Support Bot'), projectKey: this.initialProjectKey, + reopenIssueOnExternalParticipantNote: this.initialReopenIssueOnExternalParticipantNote, addExternalParticipantsFromCc: this.initialAddExternalParticipantsFromCc, searchTerm: '', projectKeyError: null, @@ -156,6 +168,7 @@ export default { selectedTemplate: this.selectedTemplate, outgoingName: this.outgoingName, projectKey: this.projectKey, + reopenIssueOnExternalParticipantNote: this.reopenIssueOnExternalParticipantNote, addExternalParticipantsFromCc: this.addExternalParticipantsFromCc, fileTemplateProjectId: this.selectedFileTemplateProjectId, }); @@ -323,9 +336,22 @@ export default { </gl-form-group> <gl-form-checkbox + v-model="reopenIssueOnExternalParticipantNote" + :disabled="!isIssueTrackerEnabled" + data-testid="reopen-issue-on-external-participant-note" + > + {{ $options.i18n.reopenIssueOnExternalParticipantNote.label }} + + <template #help> + {{ $options.i18n.reopenIssueOnExternalParticipantNote.help }} + </template> + </gl-form-checkbox> + + <gl-form-checkbox v-if="showAddExternalParticipantsFromCC" v-model="addExternalParticipantsFromCc" :disabled="!isIssueTrackerEnabled" + data-testid="add-external-participants-from-cc" > <gl-sprintf :message="$options.i18n.addExternalParticipantsFromCc.label"> <template #code="{ content }"> diff --git a/app/assets/javascripts/projects/settings_service_desk/index.js b/app/assets/javascripts/projects/settings_service_desk/index.js index ce223b349bf..a3c310ec501 100644 --- a/app/assets/javascripts/projects/settings_service_desk/index.js +++ b/app/assets/javascripts/projects/settings_service_desk/index.js @@ -21,6 +21,7 @@ export default () => { incomingEmail, outgoingName, projectKey, + reopenIssueOnExternalParticipantNote, addExternalParticipantsFromCc, selectedTemplate, selectedFileTemplateProjectId, @@ -40,6 +41,7 @@ export default () => { isIssueTrackerEnabled: parseBoolean(issueTrackerEnabled), outgoingName, projectKey, + reopenIssueOnExternalParticipantNote: parseBoolean(reopenIssueOnExternalParticipantNote), addExternalParticipantsFromCc: parseBoolean(addExternalParticipantsFromCc), selectedTemplate, selectedFileTemplateProjectId: parseInt(selectedFileTemplateProjectId, 10) || null, diff --git a/app/assets/javascripts/repository/commits_service.js b/app/assets/javascripts/repository/commits_service.js index 1e0de045d39..44113788716 100644 --- a/app/assets/javascripts/repository/commits_service.js +++ b/app/assets/javascripts/repository/commits_service.js @@ -31,13 +31,14 @@ const fetchData = (projectPath, path, ref, offset, refType) => { fetchedBatches.push(offset); + const encodePathFunc = gon.features.encodingLogsTree ? encodeURI : encodeURIComponent; const url = joinPaths( gon.relative_url_root || '/', projectPath, '/-/refs/', - encodeURIComponent(ref), + encodePathFunc(ref), '/logs_tree/', - encodeURIComponent(removeLeadingSlash(path)), + encodePathFunc(removeLeadingSlash(path)), ); return axios diff --git a/app/assets/javascripts/search/index.js b/app/assets/javascripts/search/index.js index 5879735d0f6..0c70a3e70f2 100644 --- a/app/assets/javascripts/search/index.js +++ b/app/assets/javascripts/search/index.js @@ -1,30 +1,41 @@ import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result'; import { queryToObject } from '~/lib/utils/url_utility'; import syntaxHighlight from '~/syntax_highlight'; -import { initSidebar, sidebarInitState } from './sidebar'; +import { initSidebar } from './sidebar'; import { initSearchSort } from './sort'; import createStore from './store'; import { initTopbar } from './topbar'; import { initBlobRefSwitcher } from './under_topbar'; -const topbarInitState = () => { - const el = document.getElementById('js-search-topbar'); +const sidebarInitState = () => { + const el = document.getElementById('js-search-sidebar'); if (!el) return {}; - const { defaultBranchName } = el.dataset; - return { defaultBranchName }; + + const { navigationJson, searchType, groupInitialJson, projectInitialJson } = el.dataset; + + const navigationJsonParsed = JSON.parse(navigationJson); + const groupInitialJsonParsed = JSON.parse(groupInitialJson); + const projectInitialJsonParsed = JSON.parse(projectInitialJson); + + return { navigationJsonParsed, searchType, groupInitialJsonParsed, projectInitialJsonParsed }; }; export const initSearchApp = () => { syntaxHighlight(document.querySelectorAll('.js-search-results')); const query = queryToObject(window.location.search, { gatherArrays: true }); - const { navigationJsonParsed: navigation, searchType } = sidebarInitState() || {}; - const { defaultBranchName } = topbarInitState() || {}; + const { + navigationJsonParsed: navigation, + searchType, + groupInitialJsonParsed: groupInitialJson, + projectInitialJsonParsed: projectInitialJson, + } = sidebarInitState() || {}; const store = createStore({ query, navigation, searchType, - defaultBranchName, + groupInitialJson, + projectInitialJson, }); initTopbar(store); diff --git a/app/assets/javascripts/search/sidebar/components/all_scopes_start_filters.vue b/app/assets/javascripts/search/sidebar/components/all_scopes_start_filters.vue new file mode 100644 index 00000000000..cb017b6898b --- /dev/null +++ b/app/assets/javascripts/search/sidebar/components/all_scopes_start_filters.vue @@ -0,0 +1,19 @@ +<script> +import GroupFilter from './group_filter.vue'; +import ProjectFilter from './project_filter.vue'; + +export default { + name: 'AllScopesStartFilters', + components: { + GroupFilter, + ProjectFilter, + }, +}; +</script> + +<template> + <div class="gl-px-5 gl-pt-6"> + <group-filter class="gl-mb-5" /> + <project-filter class="gl-mb-5" /> + </div> +</template> diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue index 307be0b0aa0..bbee0e441cc 100644 --- a/app/assets/javascripts/search/sidebar/components/app.vue +++ b/app/assets/javascripts/search/sidebar/components/app.vue @@ -25,6 +25,7 @@ import NotesFilters from './notes_filters.vue'; import CommitsFilters from './commits_filters.vue'; import MilestonesFilters from './milestones_filters.vue'; import WikiBlobsFilters from './wiki_blobs_filters.vue'; +import AllScopesStartFilters from './all_scopes_start_filters.vue'; export default { name: 'GlobalSearchSidebar', @@ -40,8 +41,16 @@ export default { DomElementListener, CommitsFilters, MilestonesFilters, + AllScopesStartFilters, }, mixins: [glFeatureFlagsMixin()], + props: { + headerText: { + required: false, + type: String, + default: '', + }, + }, computed: { ...mapState(['searchType']), ...mapGetters(['currentScope']), @@ -82,6 +91,13 @@ export default { <section> <dom-element-listener selector="#js-open-mobile-filters" @click="toggleFiltersFromSidebar" /> <sidebar-portal> + <all-scopes-start-filters /> + <div + v-if="headerText" + class="gl-px-5 gl-pt-3 gl-pb-2 gl-m-0 gl-reset-line-height gl-font-weight-bold gl-font-sm super-sidebar-context-header" + > + {{ headerText }} + </div> <scope-sidebar-navigation /> <issues-filters v-if="showIssuesFilters" /> <merge-requests-filters v-if="showMergeRequestFilters" /> diff --git a/app/assets/javascripts/search/topbar/components/group_filter.vue b/app/assets/javascripts/search/sidebar/components/group_filter.vue index 7f13def8a0f..20231cdda6a 100644 --- a/app/assets/javascripts/search/topbar/components/group_filter.vue +++ b/app/assets/javascripts/search/sidebar/components/group_filter.vue @@ -2,34 +2,27 @@ import { isEmpty } from 'lodash'; // eslint-disable-next-line no-restricted-imports import { mapState, mapActions, mapGetters } from 'vuex'; +import { s__ } from '~/locale'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '../constants'; import SearchableDropdown from './searchable_dropdown.vue'; export default { name: 'GroupFilter', + i18n: { + groupFieldLabel: s__('GlobalSearch|Group'), + }, components: { SearchableDropdown, }, - props: { - groupInitialJson: { - type: Object, - required: false, - default: () => ({}), - }, - labelId: { - type: String, - required: false, - default: 'labelId', - }, - }, data() { return { search: '', + labelId: 'group-filter-dropdown-id', }; }, computed: { - ...mapState(['query', 'groups', 'fetchingGroups']), + ...mapState(['query', 'groups', 'fetchingGroups', 'groupInitialJson', 'useSidebarNavigation']), ...mapGetters(['frequentGroups', 'currentScope']), selectedGroup() { return isEmpty(this.groupInitialJson) ? ANY_OPTION : this.groupInitialJson; @@ -73,17 +66,22 @@ export default { </script> <template> - <searchable-dropdown - data-testid="group-filter" - :header-text="$options.GROUP_DATA.headerText" - :name="$options.GROUP_DATA.name" - :loading="fetchingGroups" - :selected-item="selectedGroup" - :items="groups" - :frequent-items="frequentGroups" - :search-handler="fetchGroups" - :label-id="labelId" - @first-open="firstLoad" - @change="handleGroupChange" - /> + <div> + <h5 :id="labelId" class="gl-mt-0 gl-mb-5 gl-font-sm"> + {{ $options.i18n.groupFieldLabel }} + </h5> + <searchable-dropdown + data-testid="group-filter" + :header-text="$options.GROUP_DATA.headerText" + :name="$options.GROUP_DATA.name" + :loading="fetchingGroups" + :selected-item="selectedGroup" + :items="groups" + :frequent-items="frequentGroups" + :search-handler="fetchGroups" + :label-id="labelId" + @first-open="firstLoad" + @change="handleGroupChange" + /> + </div> </template> diff --git a/app/assets/javascripts/search/topbar/components/project_filter.vue b/app/assets/javascripts/search/sidebar/components/project_filter.vue index ecd118a07ac..76983644e60 100644 --- a/app/assets/javascripts/search/topbar/components/project_filter.vue +++ b/app/assets/javascripts/search/sidebar/components/project_filter.vue @@ -2,34 +2,33 @@ import { isEmpty } from 'lodash'; // eslint-disable-next-line no-restricted-imports import { mapState, mapActions, mapGetters } from 'vuex'; +import { s__ } from '~/locale'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; -import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '../constants'; +import { ANY_OPTION, GROUP_DATA, PROJECT_DATA } from '~/search/sidebar/constants'; import SearchableDropdown from './searchable_dropdown.vue'; export default { name: 'ProjectFilter', + i18n: { + projectFieldLabel: s__('GlobalSearch|Project'), + }, components: { SearchableDropdown, }, - props: { - projectInitialJson: { - type: Object, - required: false, - default: () => null, - }, - labelId: { - type: String, - required: false, - default: '', - }, - }, data() { return { search: '', + labelId: 'projects-filter-dropdown-id', }; }, computed: { - ...mapState(['query', 'projects', 'fetchingProjects']), + ...mapState([ + 'query', + 'projects', + 'fetchingProjects', + 'projectInitialJson', + 'useSidebarNavigation', + ]), ...mapGetters(['frequentProjects', 'currentScope']), selectedProject() { return isEmpty(this.projectInitialJson) ? ANY_OPTION : this.projectInitialJson; @@ -74,17 +73,22 @@ export default { </script> <template> - <searchable-dropdown - data-testid="project-filter" - :header-text="$options.PROJECT_DATA.headerText" - :name="$options.PROJECT_DATA.name" - :loading="fetchingProjects" - :selected-item="selectedProject" - :items="projects" - :frequent-items="frequentProjects" - :search-handler="fetchProjects" - :label-id="labelId" - @first-open="firstLoad" - @change="handleProjectChange" - /> + <div> + <h5 :id="labelId" class="gl-mt-0 gl-mb-5 gl-font-sm"> + {{ $options.i18n.projectFieldLabel }} + </h5> + <searchable-dropdown + data-testid="project-filter" + :header-text="$options.PROJECT_DATA.headerText" + :name="$options.PROJECT_DATA.name" + :loading="fetchingProjects" + :selected-item="selectedProject" + :items="projects" + :frequent-items="frequentProjects" + :search-handler="fetchProjects" + :label-id="labelId" + @first-open="firstLoad" + @change="handleProjectChange" + /> + </div> </template> diff --git a/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue index f30618ad9b7..874803a720d 100644 --- a/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue +++ b/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue @@ -2,6 +2,7 @@ // eslint-disable-next-line no-restricted-imports import { mapActions, mapState, mapGetters } from 'vuex'; import { s__ } from '~/locale'; +import eventHub from '~/super_sidebar/event_hub'; import NavItem from '~/super_sidebar/components/nav_item.vue'; import { NAV_LINK_DEFAULT_CLASSES, NAV_LINK_COUNT_DEFAULT_CLASSES } from '../constants'; @@ -18,6 +19,8 @@ export default { ...mapGetters(['navigationItems']), }, created() { + eventHub.$emit('toggle-menu-header', false); + if (this.urlQuery?.search) { this.fetchSidebarCount(); } diff --git a/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue b/app/assets/javascripts/search/sidebar/components/searchable_dropdown.vue index c1f0bfc59f3..c1f0bfc59f3 100644 --- a/app/assets/javascripts/search/topbar/components/searchable_dropdown.vue +++ b/app/assets/javascripts/search/sidebar/components/searchable_dropdown.vue diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js index 95906c840d7..e3b0db670b5 100644 --- a/app/assets/javascripts/search/sidebar/constants/index.js +++ b/app/assets/javascripts/search/sidebar/constants/index.js @@ -1,3 +1,5 @@ +import { __ } from '~/locale'; + export const SCOPE_ISSUES = 'issues'; export const SCOPE_MERGE_REQUESTS = 'merge_requests'; export const SCOPE_BLOB = 'blobs'; @@ -26,3 +28,23 @@ export const TRACKING_LABEL_RESET = 'Reset Filters'; export const SEARCH_TYPE_BASIC = 'basic'; export const SEARCH_TYPE_ADVANCED = 'advanced'; export const SEARCH_TYPE_ZOEKT = 'zoekt'; + +export const ANY_OPTION = { + id: null, + name: __('Any'), + name_with_namespace: __('Any'), +}; + +export const GROUP_DATA = { + headerText: __('Filter results by group'), + queryParam: 'group_id', + name: 'name', + fullName: 'full_name', +}; + +export const PROJECT_DATA = { + headerText: __('Filter results by project'), + queryParam: 'project_id', + name: 'name', + fullName: 'name_with_namespace', +}; diff --git a/app/assets/javascripts/search/sidebar/index.js b/app/assets/javascripts/search/sidebar/index.js index 3a699355dc9..9a7472ccad3 100644 --- a/app/assets/javascripts/search/sidebar/index.js +++ b/app/assets/javascripts/search/sidebar/index.js @@ -4,27 +4,23 @@ import GlobalSearchSidebar from './components/app.vue'; Vue.use(Translate); -export const sidebarInitState = () => { - const el = document.getElementById('js-search-sidebar'); - if (!el) return {}; - - const { navigationJson, searchType } = el.dataset; - - const navigationJsonParsed = JSON.parse(navigationJson); - - return { navigationJsonParsed, searchType }; -}; - export const initSidebar = (store) => { const el = document.getElementById('js-search-sidebar'); + const hederEl = document.getElementById('super-sidebar-context-header'); + const headerText = hederEl.innerText; if (!el) return false; return new Vue({ el, + name: 'GlobalSearchSidebar', store, render(createElement) { - return createElement(GlobalSearchSidebar); + return createElement(GlobalSearchSidebar, { + props: { + headerText, + }, + }); }, }); }; diff --git a/app/assets/javascripts/search/store/state.js b/app/assets/javascripts/search/store/state.js index a0e4d8c5596..3daa6b9d98c 100644 --- a/app/assets/javascripts/search/store/state.js +++ b/app/assets/javascripts/search/store/state.js @@ -1,7 +1,7 @@ import { cloneDeep } from 'lodash'; import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants'; -const createState = ({ query, navigation, defaultBranchName, searchType }) => ({ +const createState = ({ query, navigation, searchType, groupInitialJson, projectInitialJson }) => ({ urlQuery: cloneDeep(query), query, groups: [], @@ -21,7 +21,8 @@ const createState = ({ query, navigation, defaultBranchName, searchType }) => ({ }, searchLabelString: '', searchType, - defaultBranchName, + groupInitialJson, + projectInitialJson, }); export default createState; diff --git a/app/assets/javascripts/search/topbar/components/app.vue b/app/assets/javascripts/search/topbar/components/app.vue index c604189ee24..555892659fd 100644 --- a/app/assets/javascripts/search/topbar/components/app.vue +++ b/app/assets/javascripts/search/topbar/components/app.vue @@ -3,13 +3,10 @@ import { GlSearchBoxByType, GlButton } from '@gitlab/ui'; // eslint-disable-next-line no-restricted-imports import { mapState, mapActions } from 'vuex'; import { s__ } from '~/locale'; -import { parseBoolean } from '~/lib/utils/common_utils'; import MarkdownDrawer from '~/vue_shared/components/markdown_drawer/markdown_drawer.vue'; import { ZOEKT_SEARCH_TYPE, ADVANCED_SEARCH_TYPE } from '~/search/store/constants'; import { SYNTAX_OPTIONS_ADVANCED_DOCUMENT, SYNTAX_OPTIONS_ZOEKT_DOCUMENT } from '../constants'; import SearchTypeIndicator from './search_type_indicator.vue'; -import GroupFilter from './group_filter.vue'; -import ProjectFilter from './project_filter.vue'; export default { name: 'GlobalSearchTopbar', @@ -23,22 +20,10 @@ export default { components: { GlButton, GlSearchBoxByType, - GroupFilter, - ProjectFilter, MarkdownDrawer, SearchTypeIndicator, }, props: { - groupInitialJson: { - type: Object, - required: false, - default: () => ({}), - }, - projectInitialJson: { - type: Object, - required: false, - default: () => ({}), - }, defaultBranchName: { type: String, required: false, @@ -55,9 +40,6 @@ export default { this.setQuery({ key: 'search', value }); }, }, - showFilters() { - return !parseBoolean(this.query.snippets); - }, showSyntaxOptions() { return ( (this.searchType === ZOEKT_SEARCH_TYPE || this.searchType === ADVANCED_SEARCH_TYPE) && @@ -103,31 +85,17 @@ export default { </template> <search-type-indicator /> </div> - <div class="search-page-form gl-lg-display-flex gl-flex-direction-row gl-align-items-flex-end"> - <div class="gl-flex-grow-1 gl-lg-mb-0 gl-lg-mr-2"> - <gl-search-box-by-type - id="dashboard_search" - v-model="search" - name="search" - :placeholder="$options.i18n.searchPlaceholder" - @submit="applyQuery" - @keydown.enter.stop.prevent="applyQuery" - /> - </div> - <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-3 gl-min-w-20"> - <label id="groupfilterDropdown" class="gl-display-block gl-mb-1 gl-md-pb-2">{{ - $options.i18n.groupFieldLabel - }}</label> - <group-filter label-id="groupfilterDropdown" :group-initial-json="groupInitialJson" /> - </div> - <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-ml-3 gl-min-w-20"> - <label id="projectfilterDropdown" class="gl-display-block gl-mb-1 gl-md-pb-2">{{ - $options.i18n.projectFieldLabel - }}</label> - <project-filter - label-id="projectfilterDropdown" - :project-initial-json="projectInitialJson" - /> + <div class="search-page-form gl-lg-display-flex gl-flex-direction-column"> + <div class="gl-lg-display-flex gl-flex-direction-row gl-align-items-flex-start"> + <div class="gl-flex-grow-1 gl-pb-8 gl-lg-mb-0 gl-lg-mr-2"> + <gl-search-box-by-type + id="dashboard_search" + v-model="search" + name="search" + :placeholder="$options.i18n.searchPlaceholder" + @keydown.enter.stop.prevent="applyQuery" + /> + </div> </div> </div> </section> diff --git a/app/assets/javascripts/search/topbar/constants.js b/app/assets/javascripts/search/topbar/constants.js index 279f315f89f..2bd0a4d2c66 100644 --- a/app/assets/javascripts/search/topbar/constants.js +++ b/app/assets/javascripts/search/topbar/constants.js @@ -1,25 +1,3 @@ -import { __ } from '~/locale'; - -export const ANY_OPTION = Object.freeze({ - id: null, - name: __('Any'), - name_with_namespace: __('Any'), -}); - -export const GROUP_DATA = { - headerText: __('Filter results by group'), - queryParam: 'group_id', - name: 'name', - fullName: 'full_name', -}; - -export const PROJECT_DATA = { - headerText: __('Filter results by project'), - queryParam: 'project_id', - name: 'name', - fullName: 'name_with_namespace', -}; - export const SYNTAX_OPTIONS_ADVANCED_DOCUMENT = 'drawers/drawers/advanced_search_syntax.md'; export const SYNTAX_OPTIONS_ZOEKT_DOCUMENT = 'drawers/drawers/exact_code_search_syntax.md'; diff --git a/app/assets/javascripts/search/topbar/index.js b/app/assets/javascripts/search/topbar/index.js index 95e15afd597..6aac91a40d3 100644 --- a/app/assets/javascripts/search/topbar/index.js +++ b/app/assets/javascripts/search/topbar/index.js @@ -11,19 +11,16 @@ export const initTopbar = (store) => { return false; } - const { groupInitialJson, projectInitialJson } = el.dataset; - - const groupInitialJsonParsed = JSON.parse(groupInitialJson); - const projectInitialJsonParsed = JSON.parse(projectInitialJson); + const { defaultBranchName } = el.dataset; return new Vue({ el, + name: 'GlobalSearchTopbar', store, render(createElement) { return createElement(GlobalSearchTopbar, { props: { - groupInitialJson: groupInitialJsonParsed, - projectInitialJson: projectInitialJsonParsed, + defaultBranchName, }, }); }, diff --git a/app/assets/javascripts/search/under_topbar/index.js b/app/assets/javascripts/search/under_topbar/index.js index 8e50c6655dd..0be803d68fd 100644 --- a/app/assets/javascripts/search/under_topbar/index.js +++ b/app/assets/javascripts/search/under_topbar/index.js @@ -14,6 +14,7 @@ export const initBlobRefSwitcher = () => { return new Vue({ el, + name: 'GlobalSearchUnderTopbar', render(createElement) { return createElement(RefSelector, { props: { diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue index 3beec4ccc14..f2970f05ebc 100644 --- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue +++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue @@ -7,6 +7,7 @@ import { TAB_KEY_CODE } from '~/lib/utils/keycodes'; import { keysFor, TOGGLE_SUPER_SIDEBAR } from '~/behaviors/shortcuts/keybindings'; import { __, s__ } from '~/locale'; import Tracking from '~/tracking'; +import eventHub from '../event_hub'; import { sidebarState, SUPER_SIDEBAR_PEEK_STATE_CLOSED as STATE_CLOSED, @@ -58,6 +59,7 @@ export default { showPeekHint: false, isMouseover: false, breakpoint: null, + showSuperSidebarContextHeader: true, }; }, computed: { @@ -96,6 +98,7 @@ export default { mounted() { this.setupFocusTrapListener(); Mousetrap.bind(keysFor(TOGGLE_SUPER_SIDEBAR), this.toggleSidebar); + eventHub.$on('toggle-menu-header', this.onToggleMenuHeader); }, beforeDestroy() { document.removeEventListener('keydown', this.focusTrap); @@ -166,6 +169,9 @@ export default { event.preventDefault(); } }, + onToggleMenuHeader(forceState) { + this.showSuperSidebarContextHeader = forceState; + }, }, }; </script> @@ -207,6 +213,7 @@ export default { > <scroll-scrim class="gl-flex-grow-1" data-testid="nav-container"> <div + v-if="showSuperSidebarContextHeader" id="super-sidebar-context-header" class="gl-px-5 gl-pt-3 gl-pb-2 gl-m-0 gl-reset-line-height gl-font-weight-bold gl-font-sm super-sidebar-context-header" > diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index c93ef2287a8..3512f059911 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -92,8 +92,7 @@ @include transition(background-color, border-color, color, box-shadow); } -.dropdown-menu-toggle, -.header-user-avatar { +.dropdown-menu-toggle { @include transition(border-color); } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 0ae88f579e6..e791a0dbbbd 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -810,14 +810,6 @@ .navbar-gitlab { li.dropdown { position: static; - - &.user-counter { - margin-left: 8px !important; - - > a { - padding: 0 4px !important; - } - } } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 9608e294d30..2265d425e1f 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -98,12 +98,6 @@ .container-fluid { padding: 0; - .user-counter { - svg { - margin-right: 3px; - } - } - .navbar-nav { @include media-breakpoint-down(xs) { display: flex; @@ -120,12 +114,6 @@ } .nav > li { - &.header-user { - @include media-breakpoint-down(xs) { - padding-left: 10px; - } - } - > a { will-change: color; margin: 4px 0; @@ -137,38 +125,11 @@ padding: 0; } } - - &.header-user-dropdown-toggle { - margin-left: 2px; - - .header-user-avatar { - margin-right: 0; - } - } } .header-new-dropdown-toggle { margin-right: 0; } - - .impersonated-user, - .impersonated-user:hover { - margin-right: 1px; - background-color: $white; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - .impersonation-btn, - .impersonation-btn:hover { - background-color: $white; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - - svg { - color: $orange-500; - } - } } } } @@ -223,10 +184,6 @@ top: -1px; font-size: 10px; } - - .impersonation i { - color: $red-500; - } } .caret-down, @@ -238,7 +195,6 @@ fill: currentColor; } -.header-user .dropdown-menu, .header-new .dropdown-menu { margin-top: $dropdown-vertical-offset; } @@ -304,44 +260,6 @@ } } } - - .header-user-dropdown-toggle { - text-align: center; - } - - .header-user-avatar { - float: none; - } -} - -.header-user { - &.show .dropdown-menu { - margin-top: 4px; - color: var(--gl-text-color, $gl-text-color); - left: auto; - max-height: $dropdown-max-height-lg; - - .user-status { - max-width: 240px; - } - - svg { - vertical-align: text-top; - } - - a.ci-minutes-emoji gl-emoji, - a.trial-link gl-emoji { - font-size: $gl-font-size; - vertical-align: baseline; - } - } -} - -.header-user-avatar { - float: left; - margin-right: 5px; - border-radius: 50%; - border: 1px solid $gray-normal; } .notification-dot { diff --git a/app/controllers/explore/catalog_controller.rb b/app/controllers/explore/catalog_controller.rb index 50846c21b1b..0c78c1fbdb8 100644 --- a/app/controllers/explore/catalog_controller.rb +++ b/app/controllers/explore/catalog_controller.rb @@ -3,7 +3,6 @@ module Explore class CatalogController < Explore::ApplicationController feature_category :pipeline_composition - before_action :check_feature_flag before_action :check_resource_access, only: :show def show; end @@ -14,10 +13,6 @@ module Explore private - def check_feature_flag - render_404 unless Feature.enabled?(:global_ci_catalog, current_user) - end - def check_resource_access render_404 unless catalog_resource.present? end diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 7059e2a0371..4c93c738484 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -41,6 +41,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController :color_scheme_id, :diffs_deletion_color, :diffs_addition_color, + :home_organization_id, :layout, :dashboard, :project_view, diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7371902a6bd..7851e2ac80b 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -52,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController push_frontend_feature_flag(:blob_blame_info, @project) push_frontend_feature_flag(:highlight_js_worker, @project) push_frontend_feature_flag(:explain_code_chat, current_user) + push_frontend_feature_flag(:encoding_logs_tree) push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks) end diff --git a/app/controllers/projects/service_desk_controller.rb b/app/controllers/projects/service_desk_controller.rb index 70cb439c4f3..a53e8859ee6 100644 --- a/app/controllers/projects/service_desk_controller.rb +++ b/app/controllers/projects/service_desk_controller.rb @@ -29,7 +29,13 @@ class Projects::ServiceDeskController < Projects::ApplicationController end def allowed_update_attributes - %i[issue_template_key outgoing_name project_key add_external_participants_from_cc] + %i[ + issue_template_key + outgoing_name + project_key + reopen_issue_on_external_participant_note + add_external_participants_from_cc + ] end def service_desk_attributes @@ -42,6 +48,7 @@ class Projects::ServiceDeskController < Projects::ApplicationController template_file_missing: service_desk_settings&.issue_template_missing?, outgoing_name: service_desk_settings&.outgoing_name, project_key: service_desk_settings&.project_key, + reopen_issue_on_external_participant_note: service_desk_settings&.reopen_issue_on_external_participant_note, add_external_participants_from_cc: service_desk_settings&.add_external_participants_from_cc } end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index cfcc27edf3e..1bbf272e8f9 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -22,6 +22,7 @@ class Projects::TreeController < Projects::ApplicationController push_frontend_feature_flag(:blob_blame_info, @project) push_frontend_feature_flag(:highlight_js_worker, @project) push_frontend_feature_flag(:explain_code_chat, current_user) + push_frontend_feature_flag(:encoding_logs_tree) push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks) end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index b8b79192d3f..12decbbfeee 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -43,6 +43,7 @@ class ProjectsController < Projects::ApplicationController push_frontend_feature_flag(:remove_monitor_metrics, @project) push_frontend_feature_flag(:explain_code_chat, current_user) push_frontend_feature_flag(:issue_email_participants, @project) + push_frontend_feature_flag(:encoding_logs_tree) # TODO: We need to remove the FF eventually when we rollout page_specific_styles push_frontend_feature_flag(:page_specific_styles, current_user) push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks) diff --git a/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb b/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb new file mode 100644 index 00000000000..099b509f77f --- /dev/null +++ b/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Resolvers + module Namespaces + class WorkItemStateCountsResolver < WorkItemsResolver + type Types::WorkItemStateCountsType, null: true + + def ready?(**args) + # The search filter is not supported for work times at the namespace level. + # See https://gitlab.com/gitlab-org/gitlab/-/work_items/393126 + if args[:search] + raise Gitlab::Graphql::Errors::ArgumentError, + 'Searching is not available for work items at the namespace level yet' + end + + super + end + + def resolve(**args) + return if resource_parent.nil? + + Gitlab::IssuablesCountForState.new( + finder(args), + resource_parent, + store_in_redis_cache: true + ) + end + end + end +end diff --git a/app/graphql/resolvers/work_item_state_counts_resolver.rb b/app/graphql/resolvers/work_item_state_counts_resolver.rb new file mode 100644 index 00000000000..93551a57694 --- /dev/null +++ b/app/graphql/resolvers/work_item_state_counts_resolver.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Resolvers + class WorkItemStateCountsResolver < WorkItemsResolver + type Types::WorkItemStateCountsType, null: true + + def resolve(**args) + return if resource_parent.nil? + + work_item_finder = finder(prepare_finder_params(args)) + work_item_finder.parent_param = resource_parent + + Gitlab::IssuablesCountForState.new(work_item_finder, resource_parent) + end + end +end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index a4eba3c63ae..7234948033b 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -275,6 +275,14 @@ module Types description: 'Find a work item by IID directly associated with the group. Returns `null` if the ' \ '`namespace_level_work_items` feature flag is disabled.' + field :work_item_state_counts, + Types::WorkItemStateCountsType, + null: true, + alpha: { milestone: '16.7' }, + description: 'Counts of work items by state for the namespace. Returns `null` if the ' \ + '`namespace_level_work_items` feature flag is disabled.', + resolver: Resolvers::Namespaces::WorkItemStateCountsResolver + field :autocomplete_users, null: true, resolver: Resolvers::AutocompleteUsersResolver, diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index befac90ef42..8e84605cb05 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -254,6 +254,13 @@ module Types extras: [:lookahead], resolver: Resolvers::WorkItemsResolver + field :work_item_state_counts, + Types::WorkItemStateCountsType, + null: true, + alpha: { milestone: '16.7' }, + description: 'Counts of work items by state for the project.', + resolver: Resolvers::WorkItemStateCountsResolver + field :issue_status_counts, Types::IssueStatusCountsType, null: true, diff --git a/app/graphql/types/work_item_state_counts_type.rb b/app/graphql/types/work_item_state_counts_type.rb new file mode 100644 index 00000000000..a5fdf542464 --- /dev/null +++ b/app/graphql/types/work_item_state_counts_type.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes -- Parent node applies authorization + class WorkItemStateCountsType < BaseObject + graphql_name 'WorkItemStateCountsType' + description 'Represents total number of work items for the represented states' + + field :all, + GraphQL::Types::Int, + null: true, + description: 'Number of work items for the project or group.' + + field :closed, + GraphQL::Types::Int, + null: true, + description: 'Number of work items with state CLOSED for the project or group.' + + field :opened, + GraphQL::Types::Int, + null: true, + description: 'Number of work items with state OPENED for the project or group.' + end + # rubocop: enable Graphql/AuthorizeTypes +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3c64a685ba3..49230e558a8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -315,8 +315,8 @@ module ApplicationHelper class_names << 'issue-boards-page gl-overflow-auto' if current_controller?(:boards) class_names << 'epic-boards-page gl-overflow-auto' if current_controller?(:epic_boards) class_names << 'with-performance-bar' if performance_bar_enabled? - class_names << 'with-header' if !show_super_sidebar? || !current_user - class_names << 'with-top-bar' if show_super_sidebar? && !@hide_top_bar_padding + class_names << 'with-header' unless current_user + class_names << 'with-top-bar' unless @hide_top_bar_padding class_names << system_message_class class_names diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index d19b0a9a43a..3756584e3b3 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -3,18 +3,6 @@ module DashboardHelper include IconsHelper - def assigned_issues_dashboard_path - issues_dashboard_path(assignee_username: current_user.username) - end - - def assigned_mrs_dashboard_path - merge_requests_dashboard_path(assignee_username: current_user.username) - end - - def reviewer_mrs_dashboard_path - merge_requests_dashboard_path(reviewer_username: current_user.username) - end - def has_start_trial? false end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 2cc83a936d8..e02b4fa0410 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -149,16 +149,6 @@ module IssuablesHelper end end - def assigned_open_issues_count_text - count = assigned_issuables_count(:issues) - - if count > User::MAX_LIMIT_FOR_ASSIGNEED_ISSUES_COUNT - 1 - "#{count - 1}+" - else - count.to_s - end - end - def issuable_reference(issuable) @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project) end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 4b561914f2f..cb9a270253f 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -40,14 +40,6 @@ module NavHelper end end - def user_dropdown_class - class_names = [] - class_names << 'header-user-dropdown-toggle' - class_names << 'impersonated-user' if session[:impersonator_id] - - class_names - end - def page_has_markdown? current_path?('projects/merge_requests#show') || current_path?('projects/merge_requests/conflicts#show') || diff --git a/app/helpers/organizations/organization_helper.rb b/app/helpers/organizations/organization_helper.rb index 533d5409ab7..d0dd9dc5aea 100644 --- a/app/helpers/organizations/organization_helper.rb +++ b/app/helpers/organizations/organization_helper.rb @@ -51,8 +51,7 @@ module Organizations def home_organization_setting_app_data { - # TODO: use real setting - https://gitlab.com/gitlab-org/gitlab/-/issues/428668 - initial_selection: 1 + initial_selection: current_user.home_organization_id }.to_json end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index d053aeb7bfe..fc4d69dcdbc 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -5,10 +5,6 @@ module TodosHelper @todos_pending_count ||= current_user.todos_pending_count end - def todos_count_format(count) - count > 99 ? '99+' : count.to_s - end - def todos_done_count @todos_done_count ||= current_user.todos_done_count end diff --git a/app/models/issue.rb b/app/models/issue.rb index 4d73db8caac..d589c55af02 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -758,6 +758,11 @@ class Issue < ApplicationRecord Gitlab::HookData::IssueBuilder.new(self).build end + override :gfm_reference + def gfm_reference(from = nil) + "#{work_item_type_with_default.name.underscore} #{to_reference(from)}" + end + private def project_level_readable_by?(user) diff --git a/app/models/user.rb b/app/models/user.rb index fd15ee035ea..d7880579092 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -400,6 +400,7 @@ class User < MainClusterwide::ApplicationRecord :pinned_nav_items, :pinned_nav_items=, :achievements_enabled, :achievements_enabled=, :enabled_following, :enabled_following=, + :home_organization, :home_organization_id, :home_organization_id=, to: :user_preference delegate :path, to: :namespace, allow_nil: true, prefix: true @@ -613,6 +614,10 @@ class User < MainClusterwide::ApplicationRecord strip_attributes! :name + def user_belongs_to_organization?(organization) + organization_users.exists?(organization: organization) + end + def preferred_language read_attribute('preferred_language').presence || Gitlab::CurrentSettings.default_preferred_language end diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb index aef221623e9..b8c4c8597a4 100644 --- a/app/models/user_preference.rb +++ b/app/models/user_preference.rb @@ -10,6 +10,7 @@ class UserPreference < MainClusterwide::ApplicationRecord TIME_DISPLAY_FORMATS = { system: 0, non_iso_format: 1, iso_format: 2 }.freeze belongs_to :user + belongs_to :home_organization, class_name: "Organizations::Organization", optional: true scope :with_user, -> { joins(:user) } scope :gitpod_enabled, -> { where(gitpod_enabled: true) } @@ -30,6 +31,8 @@ class UserPreference < MainClusterwide::ApplicationRecord validates :time_display_format, inclusion: { in: TIME_DISPLAY_FORMATS.values }, presence: true + validate :user_belongs_to_home_organization, if: :home_organization_changed? + # 2023-06-22 is after 16.1 release and during 16.2 release https://docs.gitlab.com/ee/development/database/avoiding_downtime_in_migrations.html#ignoring-the-column-release-m ignore_columns :use_legacy_web_ide, remove_with: '16.2', remove_after: '2023-06-22' @@ -52,6 +55,16 @@ class UserPreference < MainClusterwide::ApplicationRecord end end + def user_belongs_to_home_organization + # If we don't ignore the default organization id below then all users need to have their corresponding entry + # with default organization id as organization id in the `organization_users` table. + # Otherwise, the user won't be able to the default organization as the home organization. + return if home_organization_id == Organizations::Organization::DEFAULT_ORGANIZATION_ID + return if user.user_belongs_to_organization?(home_organization_id) + + errors.add(:user, _("is not part of the given organization")) + end + def set_notes_filter(filter_id, issuable) # No need to update the column if the value is already set. if filter_id && NOTES_FILTERS.value?(filter_id) diff --git a/app/services/groups/participants_service.rb b/app/services/groups/participants_service.rb index a2238264295..7b68b435f14 100644 --- a/app/services/groups/participants_service.rb +++ b/app/services/groups/participants_service.rb @@ -12,8 +12,8 @@ module Groups noteable_owner + participants_in_noteable + all_members + - groups + - group_hierarchy_users + group_hierarchy_users + + groups render_participants_as_hash(participants.uniq) end diff --git a/app/services/merge_requests/create_ref_service.rb b/app/services/merge_requests/create_ref_service.rb index eae6845335a..1e5e127072e 100644 --- a/app/services/merge_requests/create_ref_service.rb +++ b/app/services/merge_requests/create_ref_service.rb @@ -35,8 +35,6 @@ module MergeRequests result = maybe_rebase!(**result) result = maybe_merge!(**result) - update_merge_request!(merge_request, result) - ServiceResponse.success(payload: result) rescue CreateRefError => error ServiceResponse.error(message: error.message) @@ -118,10 +116,6 @@ module MergeRequests ).compact end - def update_merge_request!(merge_request, result) - # overridden in EE - end - def safe_gitaly_operation yield rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError, ArgumentError => error diff --git a/app/services/namespaces/in_product_marketing_emails_service.rb b/app/services/namespaces/in_product_marketing_emails_service.rb deleted file mode 100644 index 14e670126c6..00000000000 --- a/app/services/namespaces/in_product_marketing_emails_service.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module Namespaces - class InProductMarketingEmailsService - TRACKS = { - create: { - interval_days: [1, 5, 10], - completed_actions: [:created], - incomplete_actions: [:git_write] - }, - team_short: { - interval_days: [1], - completed_actions: [:git_write], - incomplete_actions: [:user_added] - }, - trial_short: { - interval_days: [2], - completed_actions: [:git_write], - incomplete_actions: [:trial_started] - }, - admin_verify: { - interval_days: [3], - completed_actions: [:git_write], - incomplete_actions: [:pipeline_created] - }, - verify: { - interval_days: [4, 8, 13], - completed_actions: [:git_write], - incomplete_actions: [:pipeline_created] - }, - trial: { - interval_days: [1, 5, 10], - completed_actions: [:git_write, :pipeline_created], - incomplete_actions: [:trial_started] - }, - team: { - interval_days: [1, 5, 10], - completed_actions: [:git_write, :pipeline_created, :trial_started], - incomplete_actions: [:user_added] - } - }.freeze - - def self.email_count_for_track(track) - interval_days = TRACKS.dig(track.to_sym, :interval_days) - interval_days&.count || 0 - end - end -end - -Namespaces::InProductMarketingEmailsService.prepend_mod diff --git a/app/services/users/in_product_marketing_email_records.rb b/app/services/users/in_product_marketing_email_records.rb deleted file mode 100644 index fcb252536b3..00000000000 --- a/app/services/users/in_product_marketing_email_records.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module Users - class InProductMarketingEmailRecords - attr_reader :records - - def initialize - @records = [] - end - - def save! - Users::InProductMarketingEmail.bulk_insert!(@records) - @records = [] - end - - def add(user, track: nil, series: nil) - @records << Users::InProductMarketingEmail.new( - user: user, - track: track, - series: series, - created_at: Time.zone.now, - updated_at: Time.zone.now - ) - end - end -end diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 2c97df90110..5f038ac467d 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -62,7 +62,7 @@ = yield :page_specific_javascripts - = webpack_bundle_tag 'super_sidebar' if show_super_sidebar? + = webpack_bundle_tag 'super_sidebar' = webpack_controller_bundle_tags diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml deleted file mode 100644 index 75de13d4862..00000000000 --- a/app/views/layouts/header/_current_user_dropdown.html.haml +++ /dev/null @@ -1,51 +0,0 @@ -- return unless current_user - -%ul - %li.current-user - - if current_user_menu?(:profile) - = link_to current_user, class: 'gl-line-height-20!', data: { user: current_user.username, testid: 'user-profile-link', track_action: "click_link", track_label: "user_profile", track_property: "navigation_top" } do - = render 'layouts/header/current_user_dropdown_item' - - else - .gl-py-3.gl-px-4 - = render 'layouts/header/current_user_dropdown_item' - %li.divider - - if can?(current_user, :update_user_status, current_user) - %li - = render Pajamas::ButtonComponent.new(button_options: { class: 'menu-item js-set-status-modal-trigger' }) do - - if current_user.status&.busy? || current_user.status&.customized? - = s_('SetStatusModal|Edit status') - - else - = s_('SetStatusModal|Set status') - = dispensable_render_if_exists 'layouts/header/start_trial' - - if current_user_menu?(:settings) - %li - = link_to s_("CurrentUser|Edit profile"), profile_path, data: { testid: 'edit_profile_link', track_action: "click_link", track_label: "user_edit_profile", track_property: "navigation_top" } - %li - = link_to s_("CurrentUser|Preferences"), profile_preferences_path, data: { track_action: "click_link", track_label: "user_preferences", track_property: "navigation_top" } - = render_if_exists 'layouts/header/buy_pipeline_minutes', project: @project, namespace: @group - - - if current_user_menu?(:help) - %li.divider.d-md-none - %li.d-md-none - = link_to _("Help"), help_path, data: {track_action: 'click_link', track_label: 'help', track_property: 'navigation_top'} - %li.d-md-none - = link_to _("Support"), support_url, data: {track_action: 'click_link', track_label: 'support', track_property: 'navigation_top'} - %li.d-md-none - = render 'shared/help_dropdown_forum_link' - %li.d-md-none - = link_to _("Submit feedback"), Gitlab::Utils.append_path(promo_url, "submit-feedback"), data: {track_action: 'click_link', track_label: 'submit_feedback', track_property: 'navigation_top'} - - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile) - %li.d-md-none - = render 'shared/user_dropdown_contributing_link' - = render 'shared/user_dropdown_instance_review' - - if Gitlab.com_but_not_canary? - %li.d-md-none - = link_to _("Switch to GitLab Next"), Gitlab::Saas.canary_toggle_com_url, data: { track_action: "click_link", track_label: "switch_to_canary", track_property: "navigation_top" } - - %li.divider - .js-new-nav-toggle{ data: { enabled: show_super_sidebar?.to_s, endpoint: profile_preferences_path} } - - - if current_user_menu?(:sign_out) - %li.divider - %li - = link_to _("Sign out"), destroy_user_session_path, method: :post, class: "sign-out-link", data: { testid: 'sign_out_link', track_action: "click_link", track_label: "user_sign_out", track_property: "navigation_top" } diff --git a/app/views/layouts/header/_current_user_dropdown_item.html.haml b/app/views/layouts/header/_current_user_dropdown_item.html.haml deleted file mode 100644 index fa0a6364a15..00000000000 --- a/app/views/layouts/header/_current_user_dropdown_item.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -.gl-font-weight-bold - = current_user.name - - if current_user.status&.busy? - = render Pajamas::BadgeComponent.new(s_('UserProfile|Busy'), size: 'sm', variant: 'warning') -= current_user.to_reference -- if current_user.status - .user-status.d-flex.align-items-center.gl-mt-2.gl-mr-0.gl-font-sm.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } } - - if current_user.status.customized? - .user-status-emoji.d-flex.align-items-center - = emoji_icon current_user.status.emoji - %span.user-status-message.str-truncated - = current_user.status.message_html.html_safe diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index dbfd8a99fbb..7f7758d4081 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,6 +1,3 @@ -- has_impersonation_link = header_link?(:admin_impersonation) -- user_status_data = user_status_properties(current_user) - %header.navbar.navbar-gitlab.navbar-expand-sm.js-navbar.legacy-top-bar{ data: { testid: 'navbar' } } %a.gl-sr-only.gl-accessibility{ href: "#content-body" } Skip to content .container-fluid @@ -12,64 +9,6 @@ %ul.nav.navbar-nav.gl-w-full.gl-align-items-center.gl-justify-content-end - if current_user = render 'layouts/header/new_dropdown', class: 'gl-display-none gl-sm-display-block gl-white-space-nowrap gl-text-right' - - if header_link?(:issues) - = nav_link(path: 'dashboard#issues', html_options: { class: "user-counter" }) do - = link_to assigned_issues_dashboard_path, title: _('Issues'), class: 'dashboard-shortcuts-issues js-prefetch-document', aria: { label: _('Issues') }, - data: { testid: 'issues_shortcut_button', toggle: 'tooltip', placement: 'bottom', - track_label: 'main_navigation', - track_action: 'click_issues_link', - track_property: 'navigation_top', - container: 'body' } do - = sprite_icon('issues') - - issues_count = assigned_issuables_count(:issues) - = gl_badge_tag({ size: :sm, variant: :success }, { class: "gl-ml-n2 #{'gl-display-none' if issues_count == 0}", "aria-label": n_("%d assigned issue", "%d assigned issues", issues_count) % issues_count }) do - = assigned_open_issues_count_text - - if header_link?(:merge_requests) - = nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter dropdown" }) do - - top_level_link = assigned_mrs_dashboard_path - = link_to top_level_link, class: 'dashboard-shortcuts-merge_requests has-tooltip', title: _('Merge requests'), aria: { label: _('Merge requests') }, - data: { testid: 'merge_requests_shortcut_button', - toggle: "dropdown", - placement: 'bottom', - track_label: 'merge_requests_menu', - track_action: 'click_dropdown', - track_property: 'navigation_top', - container: 'body' } do - = sprite_icon('git-merge') - = gl_badge_tag({ size: :sm, variant: :warning }, { class: "js-merge-requests-count gl-ml-n2 #{'gl-display-none' if user_merge_requests_counts[:total] == 0}", "aria-label": n_("%d merge request", "%d merge requests", user_merge_requests_counts[:total]) % user_merge_requests_counts[:total] }) do - = number_with_delimiter(user_merge_requests_counts[:total]) - = sprite_icon('chevron-down', css_class: 'caret-down gl-mx-0!') - .dropdown-menu.dropdown-menu-right - %ul - %li.dropdown-header - = _('Merge requests') - %li - = link_to assigned_mrs_dashboard_path, - class: 'gl-display-flex! gl-align-items-center js-prefetch-document', - data: {track_action: 'click_link', track_label: 'merge_requests_assigned', track_property: 'navigation_top'} do - = _('Assigned') - = gl_badge_tag({ variant: :neutral, size: :sm }, { class: "js-assigned-mr-count gl-ml-auto" }) do - = user_merge_requests_counts[:assigned] - %li - = link_to reviewer_mrs_dashboard_path, - class: 'dashboard-shortcuts-review_requests gl-display-flex! gl-align-items-center js-prefetch-document', - data: {track_action: 'click_link', track_label: 'merge_requests_to_review', track_property: 'navigation_top'} do - = _('Review requests') - = gl_badge_tag({ variant: :neutral, size: :sm }, { class: "js-reviewer-mr-count gl-ml-auto" }) do - = user_merge_requests_counts[:review_requested] - - if header_link?(:todos) - = nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do - = link_to dashboard_todos_path, title: _('To-Do List'), aria: { label: _('To-Do List') }, class: 'shortcuts-todos js-prefetch-document', - data: { testid: 'todos-shortcut-button', toggle: 'tooltip', placement: 'bottom', - track_label: 'main_navigation', - track_action: 'click_to_do_link', - track_property: 'navigation_top', - container: 'body' } do - = sprite_icon('todo-done') - -# The todos' counter badge's visibility is being toggled by adding or removing the .hidden class in Js. - -# We'll eventually migrate to .gl-display-none: https://gitlab.com/gitlab-org/gitlab/-/issues/351792. - = gl_badge_tag({ size: :sm, variant: :info }, { class: "js-todos-count gl-ml-n2 #{'hidden' if todos_pending_count == 0}", "aria-label": _("Todos count") }) do - = todos_count_format(todos_pending_count) %li.nav-item.header-help.dropdown.d-none.d-md-block = link_to help_path, class: 'header-help-dropdown-toggle gl-relative', data: { toggle: "dropdown", track_action: 'click_question_mark_link', track_label: 'main_navigation', track_property: 'navigation_top' } do %span.gl-sr-only @@ -79,27 +18,6 @@ = sprite_icon('chevron-down', css_class: 'caret-down') .dropdown-menu.dropdown-menu-right = render 'layouts/header/help_dropdown' - - if header_link?(:user_dropdown) - %li.nav-item.header-user.js-nav-user-dropdown.dropdown{ data: { testid: 'user-dropdown' }, class: ('mr-0' if has_impersonation_link) } - = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown", track_label: "profile_dropdown", track_action: "click_dropdown", track_property: "navigation_top" } do - = render Pajamas::AvatarComponent.new(current_user, size: 24, class: 'header-user-avatar', avatar_options: { data: { testid: 'user-avatar-content' } }) - = render_if_exists 'layouts/header/user_notification_dot', project: project, namespace: group - = sprite_icon('chevron-down', css_class: 'caret-down') - .dropdown-menu.dropdown-menu-right - = render 'layouts/header/current_user_dropdown' - - if has_impersonation_link - %li.nav-item.impersonation.ml-0 - = render Pajamas::ButtonComponent.new(href: admin_impersonation_path, icon: 'incognito', button_options: { title: _('Stop impersonation'), class: 'impersonation-btn', aria: { label: _('Stop impersonation') }, data: { method: :delete, toggle: 'tooltip', placement: 'bottom', container: 'body', testid: 'stop_impersonation_btn' } }) - - if header_link?(:sign_in) - - if allow_signup? - %li.nav-item - = render Pajamas::ButtonComponent.new(href: new_user_registration_path) do - = _('Register') - %li.nav-item{ class: 'gl-flex-grow-0! gl-flex-basis-half!' } - = link_to _('Sign in'), new_session_path(:user, redirect_to_referer: 'yes') - if display_whats_new? #whats-new-app{ data: { version_digest: whats_new_version_digest } } - -- if can?(current_user, :update_user_status, current_user) - .js-set-status-modal-wrapper{ data: user_status_data } diff --git a/app/views/layouts/help.html.haml b/app/views/layouts/help.html.haml index 68426f71879..89467efcc6e 100644 --- a/app/views/layouts/help.html.haml +++ b/app/views/layouts/help.html.haml @@ -1,7 +1,6 @@ - @breadcrumb_title = _("Help") - page_title _("Help") - header_title _("Help"), help_path -- if show_super_sidebar? - - @force_desktop_expanded_sidebar = true +- @force_desktop_expanded_sidebar = true = render template: "layouts/application" diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml index 885fda10744..06192469833 100644 --- a/app/views/layouts/search.html.haml +++ b/app/views/layouts/search.html.haml @@ -1,7 +1,6 @@ - page_title _("Search") - header_title _("Search"), search_path - add_page_specific_style 'page_bundles/search' -- if show_super_sidebar? - - @force_desktop_expanded_sidebar = true +- @force_desktop_expanded_sidebar = true = render template: "layouts/application" diff --git a/app/views/projects/_service_desk_settings.html.haml b/app/views/projects/_service_desk_settings.html.haml index 115788e7764..d6b28e94645 100644 --- a/app/views/projects/_service_desk_settings.html.haml +++ b/app/views/projects/_service_desk_settings.html.haml @@ -18,6 +18,7 @@ selected_file_template_project_id: "#{@project.service_desk_setting&.file_template_project_id}", outgoing_name: "#{@project.service_desk_setting&.outgoing_name}", project_key: "#{@project.service_desk_setting&.project_key}", + reopen_issue_on_external_participant_note: "#{@project.service_desk_setting&.reopen_issue_on_external_participant_note}", add_external_participants_from_cc: "#{@project.service_desk_setting&.add_external_participants_from_cc}", templates: available_service_desk_templates_for(@project), public_project: "#{@project.public?}", diff --git a/app/views/search/_results_list.html.haml b/app/views/search/_results_list.html.haml index fb96672cf99..dad352e376b 100644 --- a/app/views/search/_results_list.html.haml +++ b/app/views/search/_results_list.html.haml @@ -8,9 +8,7 @@ - elsif @search_objects.blank? = render partial: "search/results/empty" - else - - statusBarClass = !show_super_sidebar? ? 'gl-lg-pl-5' : '' - - .section{ class: statusBarClass } + .section - if @scope == 'commits' %ul.content-list.commit-list = render partial: "search/results/commit", collection: @search_objects diff --git a/app/views/search/_results_status.html.haml b/app/views/search/_results_status.html.haml index 8417b66eb34..3c42126e480 100644 --- a/app/views/search/_results_status.html.haml +++ b/app/views/search/_results_status.html.haml @@ -1,7 +1,4 @@ -- statusBarClass = !show_super_sidebar? ? 'gl-lg-pl-5' : '' -- statusBarClass = statusBarClass + ' gl-lg-display-none' if @search_objects.to_a.empty? - -.section{ class: statusBarClass } +.section{ class: ('gl-lg-display-none' if @search_objects.to_a.empty?) } .search-results-status .gl-display-flex.gl-flex-direction-column .gl-p-5.gl-display-flex.gl-flex-wrap diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 4ddeca64c7d..af3177e4422 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -16,8 +16,8 @@ - page_description(_("%{count} %{scope} for term '%{term}'") % { count: @search_results.formatted_count(@scope), scope: @scope, term: @search_term }) - page_card_attributes("Namespace" => @group&.full_path, "Project" => @project&.full_path) -#js-search-topbar{ data: { "group-initial-json": group_attributes.to_json, "project-initial-json": project_attributes.to_json, "default-branch-name": @project&.default_branch } } +#js-search-topbar{ data: { "default-branch-name": @project&.default_branch } } .results.gl-lg-display-flex.gl-mt-0 - #js-search-sidebar{ data: { navigation_json: search_navigation_json, search_type: search_service.search_type } } + #js-search-sidebar{ data: { navigation_json: search_navigation_json, search_type: search_service.search_type, group_initial_json: group_attributes.to_json, project_initial_json: project_attributes.to_json, } } - if @search_term = render 'search/results' diff --git a/app/views/shared/_ci_catalog_badge.html.haml b/app/views/shared/_ci_catalog_badge.html.haml index 18e0cb37d7d..7f8f4f6143b 100644 --- a/app/views/shared/_ci_catalog_badge.html.haml +++ b/app/views/shared/_ci_catalog_badge.html.haml @@ -1,2 +1 @@ -- current_href = Feature.enabled?(:global_ci_catalog, @user) ? href : nil -= render Pajamas::BadgeComponent.new(s_('CiCatalog|CI/CD catalog resource'), variant: 'info', icon: 'catalog-checkmark', class: css_class, href: current_href) += render Pajamas::BadgeComponent.new(s_('CiCatalog|CI/CD catalog resource'), variant: 'info', icon: 'catalog-checkmark', class: css_class, href: href) diff --git a/app/views/shared/runners/_runner_description.html.haml b/app/views/shared/runners/_runner_description.html.haml index dc689303f77..6f918ae8103 100644 --- a/app/views/shared/runners/_runner_description.html.haml +++ b/app/views/shared/runners/_runner_description.html.haml @@ -19,4 +19,4 @@ %p = s_("Runners|Tags control which type of jobs a runner can handle. By tagging a runner, you make sure shared runners only handle the jobs they are equipped to run.") - = link_to _("Learn more."), help_page_path("ci/runners/configure_runners", anchor: "use-tags-to-control-which-jobs-a-runner-can-run"), target: '_blank' + = link_to _("Learn more."), help_page_path("ci/runners/configure_runners", anchor: "how-the-runner-uses-tags"), target: '_blank' |