Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-12-01 12:10:10 +00:00
parent 17a47c3e30
commit 79c94e595b
383 changed files with 2003 additions and 5035 deletions

View File

@ -5,7 +5,7 @@ workflow:
name: $PIPELINE_NAME
include:
- component: "gitlab.com/gitlab-org/quality/pipeline-common/allure-report@7.13.1"
- component: "gitlab.com/gitlab-org/quality/pipeline-common/allure-report@7.13.2"
inputs:
job_name: "e2e-test-report"
job_stage: "report"

View File

@ -2427,7 +2427,6 @@ RSpec/FeatureCategory:
- 'spec/helpers/invite_members_helper_spec.rb'
- 'spec/helpers/issuables_description_templates_helper_spec.rb'
- 'spec/helpers/issues_helper_spec.rb'
- 'spec/helpers/json_helper_spec.rb'
- 'spec/helpers/keyset_helper_spec.rb'
- 'spec/helpers/labels_helper_spec.rb'
- 'spec/helpers/lazy_image_tag_helper_spec.rb'

View File

@ -1612,7 +1612,6 @@ RSpec/NamedSubject:
- 'spec/helpers/hooks_helper_spec.rb'
- 'spec/helpers/integrations_helper_spec.rb'
- 'spec/helpers/jira_connect_helper_spec.rb'
- 'spec/helpers/json_helper_spec.rb'
- 'spec/helpers/labels_helper_spec.rb'
- 'spec/helpers/listbox_helper_spec.rb'
- 'spec/helpers/markup_helper_spec.rb'

View File

@ -1 +1 @@
6cb3173f9a1468c93d5c3fa9327e9b560de1329b
38a79230add5dc453d8fafa48f7f3e218a70f0be

View File

@ -20,23 +20,11 @@ export default {
},
},
computed: {
...mapState([
'jobLogEndpoint',
'jobLog',
'isJobLogComplete',
'isScrolledToBottomBeforeReceivingJobLog',
]),
...mapState(['jobLogEndpoint', 'jobLog', 'isJobLogComplete']),
highlightedLines() {
return this.searchResults.map((result) => result.lineNumber);
},
},
updated() {
this.$nextTick(() => {
if (!window.location.hash) {
this.handleScrollDown();
}
});
},
mounted() {
if (window.location.hash) {
const lineNumber = getLocationHash();
@ -57,20 +45,6 @@ export default {
handleOnClickCollapsibleLine(section) {
this.toggleCollapsibleLine(section);
},
/**
* The job log is sent in HTML, which means we need to use `v-html` to render it
* Using the updated hook with $nextTick is not enough to wait for the DOM to be updated
* in this case because it runs before `v-html` has finished running, since there's no
* Vue binding.
* In order to scroll the page down after `v-html` has finished, we need to use setTimeout
*/
handleScrollDown() {
if (this.isScrolledToBottomBeforeReceivingJobLog) {
setTimeout(() => {
this.scrollBottom();
}, 0);
}
},
isHighlighted({ lineNumber }) {
return this.highlightedLines.includes(lineNumber);
},

View File

@ -88,7 +88,6 @@ export default {
'isJobLogSizeVisible',
'isScrollBottomDisabled',
'isScrollTopDisabled',
'isScrolledToBottomBeforeReceivingJobLog',
'hasError',
'selectedStage',
]),

View File

@ -150,40 +150,47 @@ export const enableScrollTop = ({ commit }) => commit(types.ENABLE_SCROLL_TOP);
export const toggleScrollAnimation = ({ commit }, toggle) =>
commit(types.TOGGLE_SCROLL_ANIMATION, toggle);
/**
* Responsible to handle automatic scroll
*/
export const toggleScrollisInBottom = ({ commit }, toggle) => {
commit(types.TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_JOB_LOG, toggle);
};
export const requestJobLog = ({ commit }) => commit(types.REQUEST_JOB_LOG);
export const fetchJobLog = ({ dispatch, state }) =>
// update trace endpoint once BE compeletes trace re-naming in #340626
axios
.get(`${state.jobLogEndpoint}/trace.json`, {
params: { state: state.jobLogState },
})
.then(({ data }) => {
dispatch('toggleScrollisInBottom', isScrolledToBottom());
dispatch('receiveJobLogSuccess', data);
export const fetchJobLog = ({ commit, dispatch, state }) => {
let isScrolledToBottomBeforeReceivingJobLog;
if (data.complete) {
dispatch('stopPollingJobLog');
dispatch('requestTestSummary');
} else if (!state.jobLogTimeout) {
dispatch('startPollingJobLog');
}
})
.catch((e) => {
if (e.response?.status === HTTP_STATUS_FORBIDDEN) {
dispatch('receiveJobLogUnauthorizedError');
} else {
reportToSentry('job_actions', e);
dispatch('receiveJobLogError');
}
});
// update trace endpoint once BE completes trace re-naming in #340626
return (
axios
.get(`${state.jobLogEndpoint}/trace.json`, {
params: { state: state.jobLogState },
})
.then(({ data }) => {
isScrolledToBottomBeforeReceivingJobLog = isScrolledToBottom();
commit(types.RECEIVE_JOB_LOG_SUCCESS, data);
if (data.complete) {
dispatch('stopPollingJobLog');
dispatch('requestTestSummary');
} else if (!state.jobLogTimeout) {
dispatch('startPollingJobLog');
}
})
// place `scrollBottom` in a separate `then()` block
// to wait on related components to update
// after the RECEIVE_JOB_LOG_SUCCESS commit
.then(() => {
if (isScrolledToBottomBeforeReceivingJobLog) {
dispatch('scrollBottom');
}
})
.catch((e) => {
if (e.response?.status === HTTP_STATUS_FORBIDDEN) {
dispatch('receiveJobLogUnauthorizedError');
} else {
reportToSentry('job_actions', e);
dispatch('receiveJobLogError');
}
})
);
};
export const startPollingJobLog = ({ dispatch, commit }) => {
const jobLogTimeout = setTimeout(() => {
@ -200,8 +207,6 @@ export const stopPollingJobLog = ({ state, commit }) => {
commit(types.STOP_POLLING_JOB_LOG);
};
export const receiveJobLogSuccess = ({ commit }, log) => commit(types.RECEIVE_JOB_LOG_SUCCESS, log);
export const receiveJobLogError = ({ dispatch }) => {
dispatch('stopPollingJobLog');
createAlert({

View File

@ -11,8 +11,6 @@ export const ENABLE_SCROLL_BOTTOM = 'ENABLE_SCROLL_BOTTOM';
export const ENABLE_SCROLL_TOP = 'ENABLE_SCROLL_TOP';
export const TOGGLE_SCROLL_ANIMATION = 'TOGGLE_SCROLL_ANIMATION';
export const TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_JOB_LOG = 'TOGGLE_IS_SCROLL_IN_BOTTOM';
export const REQUEST_JOB = 'REQUEST_JOB';
export const RECEIVE_JOB_SUCCESS = 'RECEIVE_JOB_SUCCESS';
export const RECEIVE_JOB_ERROR = 'RECEIVE_JOB_ERROR';

View File

@ -111,11 +111,6 @@ export default {
[types.TOGGLE_SCROLL_ANIMATION](state, toggle) {
state.isScrollingDown = toggle;
},
[types.TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_JOB_LOG](state, toggle) {
state.isScrolledToBottomBeforeReceivingJobLog = toggle;
},
[types.REQUEST_JOBS_FOR_STAGE](state, stage = {}) {
state.isLoadingJobs = true;
state.selectedStage = stage.name;

View File

@ -16,9 +16,6 @@ export default () => ({
isScrollBottomDisabled: true,
isScrollTopDisabled: true,
// Used to check if we should keep the automatic scroll
isScrolledToBottomBeforeReceivingJobLog: true,
jobLog: [],
isJobLogComplete: false,
jobLogSize: 0,

View File

@ -1,183 +0,0 @@
<script>
import { GlLoadingIcon, GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import AccessorUtilities from '~/lib/utils/accessor';
import {
mapVuexModuleState,
mapVuexModuleActions,
mapVuexModuleGetters,
} from '~/lib/utils/vuex_module_mappers';
import Tracking from '~/tracking';
import { FREQUENT_ITEMS, STORAGE_KEY } from '../constants';
import eventHub from '../event_hub';
import { isMobile, updateExistingFrequentItem, sanitizeItem } from '../utils';
import FrequentItemsList from './frequent_items_list.vue';
import frequentItemsMixin from './frequent_items_mixin';
import FrequentItemsSearchInput from './frequent_items_search_input.vue';
const trackingMixin = Tracking.mixin();
export default {
components: {
FrequentItemsSearchInput,
FrequentItemsList,
GlLoadingIcon,
GlButton,
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [frequentItemsMixin, trackingMixin],
inject: ['vuexModule'],
props: {
currentUserName: {
type: String,
required: true,
},
currentItem: {
type: Object,
required: true,
},
searchClass: {
type: String,
required: false,
default: '',
},
},
computed: {
...mapVuexModuleState((vm) => vm.vuexModule, [
'searchQuery',
'isLoadingItems',
'isItemsListEditable',
'isFetchFailed',
'isItemRemovalFailed',
'items',
]),
...mapVuexModuleGetters((vm) => vm.vuexModule, ['hasSearchQuery']),
translations() {
return this.getTranslations(['loadingMessage', 'header', 'headerEditToggle']);
},
},
created() {
const { namespace, currentUserName, currentItem } = this;
const storageKey = `${currentUserName}/${STORAGE_KEY[namespace]}`;
this.setNamespace(namespace);
this.setStorageKey(storageKey);
if (currentItem.id) {
this.logItemAccess(storageKey, currentItem);
}
eventHub.$on(`${this.namespace}-dropdownOpen`, this.dropdownOpenHandler);
// As we init it through requestIdleCallback it could be that the dropdown is already open
const namespaceDropdown = document.getElementById(`nav-${this.namespace}-dropdown`);
if (namespaceDropdown && namespaceDropdown.classList.contains('show')) {
this.dropdownOpenHandler();
}
},
beforeDestroy() {
eventHub.$off(`${this.namespace}-dropdownOpen`, this.dropdownOpenHandler);
},
methods: {
...mapVuexModuleActions((vm) => vm.vuexModule, [
'setNamespace',
'setStorageKey',
'toggleItemsListEditablity',
'fetchFrequentItems',
]),
toggleItemsListEditablityTracked() {
this.track('click_button', {
label: 'toggle_edit_frequent_items',
property: 'navigation_top',
});
this.toggleItemsListEditablity();
},
dropdownOpenHandler() {
if (this.searchQuery === '' || isMobile()) {
this.fetchFrequentItems();
}
},
logItemAccess(storageKey, unsanitizedItem) {
const item = sanitizeItem(unsanitizedItem);
if (!AccessorUtilities.canUseLocalStorage()) {
return false;
}
// Check if there's any frequent items list set
const storedRawItems = localStorage.getItem(storageKey);
const storedFrequentItems = storedRawItems
? JSON.parse(storedRawItems)
: [{ ...item, frequency: 1 }]; // No frequent items list set, set one up.
// Check if item already exists in list
const itemMatchIndex = storedFrequentItems.findIndex(
(frequentItem) => frequentItem.id === item.id,
);
if (itemMatchIndex > -1) {
storedFrequentItems[itemMatchIndex] = updateExistingFrequentItem(
storedFrequentItems[itemMatchIndex],
item,
);
} else {
if (storedFrequentItems.length === FREQUENT_ITEMS.MAX_COUNT) {
storedFrequentItems.shift();
}
storedFrequentItems.push({ ...item, frequency: 1 });
}
return localStorage.setItem(storageKey, JSON.stringify(storedFrequentItems));
},
},
};
</script>
<template>
<div class="gl-display-flex gl-flex-direction-column gl-flex-align-items-stretch gl-h-full">
<frequent-items-search-input
:namespace="namespace"
:class="searchClass"
data-testid="frequent-items-search-input"
/>
<gl-loading-icon
v-if="isLoadingItems"
:label="translations.loadingMessage"
size="lg"
class="loading-animation prepend-top-20"
data-testid="loading"
/>
<div
v-if="!isLoadingItems && !hasSearchQuery"
class="section-header gl-display-flex"
data-testid="header"
>
<span class="gl-flex-grow-1">{{ translations.header }}</span>
<gl-button
v-if="items.length"
v-gl-tooltip.left
size="small"
category="tertiary"
:aria-label="translations.headerEditToggle"
:title="translations.headerEditToggle"
:class="{ 'gl-bg-gray-100!': isItemsListEditable }"
class="gl-p-2!"
@click="toggleItemsListEditablityTracked"
>
<gl-icon name="pencil" :class="{ 'gl-text-gray-900!': isItemsListEditable }" />
</gl-button>
</div>
<frequent-items-list
v-if="!isLoadingItems"
:items="items"
:namespace="namespace"
:has-search-query="hasSearchQuery"
:is-fetch-failed="isFetchFailed"
:is-item-removal-failed="isItemRemovalFailed"
:matcher="searchQuery"
/>
</div>
</template>

View File

@ -1,90 +0,0 @@
<script>
import { sanitizeItem } from '../utils';
import FrequentItemsListItem from './frequent_items_list_item.vue';
import frequentItemsMixin from './frequent_items_mixin';
export default {
components: {
FrequentItemsListItem,
},
mixins: [frequentItemsMixin],
props: {
items: {
type: Array,
required: true,
},
hasSearchQuery: {
type: Boolean,
required: true,
},
isFetchFailed: {
type: Boolean,
required: true,
},
isItemRemovalFailed: {
type: Boolean,
required: true,
},
matcher: {
type: String,
required: true,
},
},
computed: {
translations() {
return this.getTranslations([
'itemListEmptyMessage',
'itemListErrorMessage',
'searchListEmptyMessage',
'searchListErrorMessage',
]);
},
isListEmpty() {
return this.items.length === 0;
},
showListEmptyMessage() {
return this.isListEmpty || this.isItemRemovalFailed;
},
listEmptyMessage() {
if (this.hasSearchQuery) {
return this.isFetchFailed
? this.translations.searchListErrorMessage
: this.translations.searchListEmptyMessage;
}
return this.isFetchFailed || this.isItemRemovalFailed
? this.translations.itemListErrorMessage
: this.translations.itemListEmptyMessage;
},
sanitizedItems() {
return this.items.map(sanitizeItem);
},
},
};
</script>
<template>
<div class="frequent-items-list-container">
<ul data-testid="frequent-items-list" class="list-unstyled">
<li
v-if="showListEmptyMessage"
:class="{ 'section-failure': isFetchFailed }"
class="section-empty gl-mb-3"
data-testid="frequent-items-list-empty"
>
{{ listEmptyMessage }}
</li>
<frequent-items-list-item
v-for="item in sanitizedItems"
v-else
:key="item.id"
:item-id="item.id"
:item-name="item.name"
:namespace="item.namespace"
:web-url="item.webUrl"
:avatar-url="item.avatarUrl"
:matcher="matcher"
/>
</ul>
</div>
</template>

View File

@ -1,132 +0,0 @@
<script>
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import SafeHtml from '~/vue_shared/directives/safe_html';
import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility';
import { mapVuexModuleState, mapVuexModuleActions } from '~/lib/utils/vuex_module_mappers';
import Tracking from '~/tracking';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
const trackingMixin = Tracking.mixin();
export default {
components: {
GlIcon,
GlButton,
ProjectAvatar,
},
directives: {
SafeHtml,
GlTooltip: GlTooltipDirective,
},
mixins: [trackingMixin],
inject: ['vuexModule'],
props: {
matcher: {
type: String,
required: false,
default: '',
},
itemId: {
type: Number,
required: true,
},
itemName: {
type: String,
required: true,
},
namespace: {
type: String,
required: false,
default: '',
},
webUrl: {
type: String,
required: true,
},
avatarUrl: {
required: true,
validator(value) {
return value === null || typeof value === 'string';
},
},
},
computed: {
...mapVuexModuleState((vm) => vm.vuexModule, ['dropdownType', 'isItemsListEditable']),
truncatedNamespace() {
return truncateNamespace(this.namespace);
},
highlightedItemName() {
return highlight(this.itemName, this.matcher);
},
itemTrackingLabel() {
return `${this.dropdownType}_dropdown_frequent_items_list_item`;
},
},
methods: {
removeFrequentItemTracked(item) {
this.track('click_button', {
label: `${this.dropdownType}_dropdown_remove_frequent_item`,
property: 'navigation_top',
});
this.removeFrequentItem(item);
},
...mapVuexModuleActions((vm) => vm.vuexModule, ['removeFrequentItem']),
},
};
</script>
<template>
<li class="frequent-items-list-item-container gl-relative">
<gl-button
category="tertiary"
:href="webUrl"
class="gl-text-left gl-w-full"
button-text-classes="gl-display-flex gl-w-full"
data-testid="frequent-item-link"
@click="track('click_link', { label: itemTrackingLabel, property: 'navigation_top' })"
>
<div class="gl-flex-grow-1">
<project-avatar
class="gl-float-left gl-mr-3"
:project-avatar-url="avatarUrl"
:project-id="itemId"
:project-name="itemName"
aria-hidden="true"
/>
<div
data-testid="frequent-items-item-metadata-container"
class="frequent-items-item-metadata-container"
>
<div
v-safe-html="highlightedItemName"
data-testid="frequent-items-item-title"
:title="itemName"
class="frequent-items-item-title"
></div>
<div
v-if="namespace"
data-testid="frequent-items-item-namespace"
:title="namespace"
class="frequent-items-item-namespace"
>
{{ truncatedNamespace }}
</div>
</div>
</div>
</gl-button>
<gl-button
v-if="isItemsListEditable"
v-gl-tooltip.left
size="small"
category="tertiary"
:aria-label="__('Remove')"
:title="__('Remove')"
class="gl-align-self-center gl-p-1! gl-absolute! gl-w-auto! gl-right-4 gl-top-half gl-translate-y-n50"
data-testid="item-remove"
@click.stop.prevent="removeFrequentItemTracked(itemId)"
>
<gl-icon name="close" />
</gl-button>
</li>
</template>

View File

@ -1,23 +0,0 @@
import { TRANSLATION_KEYS } from '../constants';
export default {
props: {
namespace: {
type: String,
required: true,
},
},
methods: {
getTranslations(keys) {
const translationStrings = keys.reduce(
(acc, key) => ({
...acc,
[key]: TRANSLATION_KEYS[this.namespace][key],
}),
{},
);
return translationStrings;
},
},
};

View File

@ -1,63 +0,0 @@
<script>
import { GlSearchBoxByType } from '@gitlab/ui';
import { debounce } from 'lodash';
import { mapVuexModuleActions, mapVuexModuleState } from '~/lib/utils/vuex_module_mappers';
import Tracking from '~/tracking';
import frequentItemsMixin from './frequent_items_mixin';
const trackingMixin = Tracking.mixin();
export default {
components: {
GlSearchBoxByType,
},
mixins: [frequentItemsMixin, trackingMixin],
inject: ['vuexModule'],
data() {
return {
searchQuery: '',
};
},
computed: {
...mapVuexModuleState((vm) => vm.vuexModule, ['dropdownType']),
translations() {
return this.getTranslations(['searchInputPlaceholder']);
},
},
watch: {
searchQuery: debounce(function debounceSearchQuery() {
this.track('type_search_query', {
label: `${this.dropdownType}_dropdown_frequent_items_search_input`,
property: 'navigation_top',
});
this.setSearchQuery(this.searchQuery);
}, 500),
},
methods: {
...mapVuexModuleActions((vm) => vm.vuexModule, ['setSearchQuery']),
trackFocus() {
this.track('focus_input', {
label: `${this.dropdownType}_dropdown_frequent_items_search_input`,
property: 'navigation_top',
});
},
trackBlur() {
this.track('blur_input', {
label: `${this.dropdownType}_dropdown_frequent_items_search_input`,
property: 'navigation_top',
});
},
},
};
</script>
<template>
<div class="search-input-container">
<gl-search-box-by-type
v-model="searchQuery"
:placeholder="translations.searchInputPlaceholder"
@focus="trackFocus"
@blur="trackBlur"
/>
</div>
</template>

View File

@ -1,54 +0,0 @@
import { s__ } from '~/locale';
export const FREQUENT_ITEMS = {
MAX_COUNT: 20,
LIST_COUNT_DESKTOP: 5,
LIST_COUNT_MOBILE: 3,
ELIGIBLE_FREQUENCY: 3,
};
export const FIFTEEN_MINUTES_IN_MS = 900000;
export const STORAGE_KEY = {
projects: 'frequent-projects',
groups: 'frequent-groups',
};
export const TRANSLATION_KEYS = {
projects: {
loadingMessage: s__('ProjectsDropdown|Loading projects'),
header: s__('ProjectsDropdown|Frequently visited'),
headerEditToggle: s__('ProjectsDropdown|Toggle edit mode'),
itemListErrorMessage: s__(
'ProjectsDropdown|This feature requires browser localStorage support',
),
itemListEmptyMessage: s__('ProjectsDropdown|Projects you visit often will appear here'),
searchListErrorMessage: s__('ProjectsDropdown|Something went wrong on our end.'),
searchListEmptyMessage: s__('ProjectsDropdown|Sorry, no projects matched your search'),
searchInputPlaceholder: s__('ProjectsDropdown|Search your projects'),
},
groups: {
loadingMessage: s__('GroupsDropdown|Loading groups'),
header: s__('GroupsDropdown|Frequently visited'),
headerEditToggle: s__('GroupsDropdown|Toggle edit mode'),
itemListErrorMessage: s__('GroupsDropdown|This feature requires browser localStorage support'),
itemListEmptyMessage: s__('GroupsDropdown|Groups you visit often will appear here'),
searchListErrorMessage: s__('GroupsDropdown|Something went wrong on our end.'),
searchListEmptyMessage: s__('GroupsDropdown|Sorry, no groups matched your search'),
searchInputPlaceholder: s__('GroupsDropdown|Search your groups'),
},
};
export const FREQUENT_ITEMS_PROJECTS = {
namespace: 'projects',
key: 'project',
vuexModule: 'frequentProjects',
};
export const FREQUENT_ITEMS_GROUPS = {
namespace: 'groups',
key: 'group',
vuexModule: 'frequentGroups',
};
export const FREQUENT_ITEMS_DROPDOWNS = [FREQUENT_ITEMS_PROJECTS, FREQUENT_ITEMS_GROUPS];

View File

@ -1,3 +0,0 @@
import createEventHub from '~/helpers/event_hub_factory';
export default createEventHub();

View File

@ -1,112 +0,0 @@
import AccessorUtilities from '~/lib/utils/accessor';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { getGroups, getProjects } from '~/rest_api';
import { getTopFrequentItems } from '../utils';
import * as types from './mutation_types';
export const setNamespace = ({ commit }, namespace) => {
commit(types.SET_NAMESPACE, namespace);
};
export const setStorageKey = ({ commit }, key) => {
commit(types.SET_STORAGE_KEY, key);
};
export const toggleItemsListEditablity = ({ commit }) => {
commit(types.TOGGLE_ITEMS_LIST_EDITABILITY);
};
export const requestFrequentItems = ({ commit }) => {
commit(types.REQUEST_FREQUENT_ITEMS);
};
export const receiveFrequentItemsSuccess = ({ commit }, data) => {
commit(types.RECEIVE_FREQUENT_ITEMS_SUCCESS, data);
};
export const receiveFrequentItemsError = ({ commit }) => {
commit(types.RECEIVE_FREQUENT_ITEMS_ERROR);
};
export const fetchFrequentItems = ({ state, dispatch }) => {
dispatch('requestFrequentItems');
if (AccessorUtilities.canUseLocalStorage()) {
const storedFrequentItems = JSON.parse(localStorage.getItem(state.storageKey));
dispatch(
'receiveFrequentItemsSuccess',
!storedFrequentItems ? [] : getTopFrequentItems(storedFrequentItems),
);
} else {
dispatch('receiveFrequentItemsError');
}
};
export const requestSearchedItems = ({ commit }) => {
commit(types.REQUEST_SEARCHED_ITEMS);
};
export const receiveSearchedItemsSuccess = ({ commit }, data) => {
commit(types.RECEIVE_SEARCHED_ITEMS_SUCCESS, data);
};
export const receiveSearchedItemsError = ({ commit }) => {
commit(types.RECEIVE_SEARCHED_ITEMS_ERROR);
};
export const fetchSearchedItems = ({ state, dispatch }, searchQuery) => {
dispatch('requestSearchedItems');
const params = {
simple: true,
per_page: 20,
membership: isLoggedIn(),
};
let searchFunction;
if (state.namespace === 'projects') {
searchFunction = getProjects;
params.order_by = 'last_activity_at';
} else {
searchFunction = getGroups;
}
return searchFunction(searchQuery, params)
.then((results) => {
dispatch('receiveSearchedItemsSuccess', results);
})
.catch(() => {
dispatch('receiveSearchedItemsError');
});
};
export const setSearchQuery = ({ commit, dispatch }, query) => {
commit(types.SET_SEARCH_QUERY, query);
if (query) {
dispatch('fetchSearchedItems', query);
} else {
dispatch('fetchFrequentItems');
}
};
export const removeFrequentItemSuccess = ({ commit }, itemId) => {
commit(types.RECEIVE_REMOVE_FREQUENT_ITEM_SUCCESS, itemId);
};
export const removeFrequentItemError = ({ commit }) => {
commit(types.RECEIVE_REMOVE_FREQUENT_ITEM_ERROR);
};
export const removeFrequentItem = ({ state, dispatch }, itemId) => {
if (AccessorUtilities.canUseLocalStorage()) {
try {
const storedRawItems = JSON.parse(localStorage.getItem(state.storageKey));
localStorage.setItem(
state.storageKey,
JSON.stringify(storedRawItems.filter((item) => item.id !== itemId)),
);
dispatch('removeFrequentItemSuccess', itemId);
} catch {
dispatch('removeFrequentItemError');
}
} else {
dispatch('removeFrequentItemError');
}
};

View File

@ -1 +0,0 @@
export const hasSearchQuery = (state) => state.searchQuery !== '';

View File

@ -1,29 +0,0 @@
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import { FREQUENT_ITEMS_DROPDOWNS } from '../constants';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
export const createFrequentItemsModule = (initState = {}) => ({
namespaced: true,
actions,
getters,
mutations,
state: state(initState),
});
export const createStoreOptions = () => ({
modules: FREQUENT_ITEMS_DROPDOWNS.reduce(
(acc, { namespace, vuexModule }) =>
Object.assign(acc, {
[vuexModule]: createFrequentItemsModule({ dropdownType: namespace }),
}),
{},
),
});
export const createStore = () => {
return new Vuex.Store(createStoreOptions());
};

View File

@ -1,12 +0,0 @@
export const SET_NAMESPACE = 'SET_NAMESPACE';
export const SET_STORAGE_KEY = 'SET_STORAGE_KEY';
export const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY';
export const TOGGLE_ITEMS_LIST_EDITABILITY = 'TOGGLE_ITEMS_LIST_EDITABILITY';
export const REQUEST_FREQUENT_ITEMS = 'REQUEST_FREQUENT_ITEMS';
export const RECEIVE_FREQUENT_ITEMS_SUCCESS = 'RECEIVE_FREQUENT_ITEMS_SUCCESS';
export const RECEIVE_FREQUENT_ITEMS_ERROR = 'RECEIVE_FREQUENT_ITEMS_ERROR';
export const REQUEST_SEARCHED_ITEMS = 'REQUEST_SEARCHED_ITEMS';
export const RECEIVE_SEARCHED_ITEMS_SUCCESS = 'RECEIVE_SEARCHED_ITEMS_SUCCESS';
export const RECEIVE_SEARCHED_ITEMS_ERROR = 'RECEIVE_SEARCHED_ITEMS_ERROR';
export const RECEIVE_REMOVE_FREQUENT_ITEM_SUCCESS = 'RECEIVE_REMOVE_FREQUENT_ITEM_SUCCESS';
export const RECEIVE_REMOVE_FREQUENT_ITEM_ERROR = 'RECEIVE_REMOVE_FREQUENT_ITEM_ERROR';

View File

@ -1,88 +0,0 @@
import * as types from './mutation_types';
export default {
[types.SET_NAMESPACE](state, namespace) {
Object.assign(state, {
namespace,
});
},
[types.SET_STORAGE_KEY](state, storageKey) {
Object.assign(state, {
storageKey,
});
},
[types.SET_SEARCH_QUERY](state, searchQuery) {
const hasSearchQuery = searchQuery !== '';
Object.assign(state, {
searchQuery,
isLoadingItems: true,
hasSearchQuery,
});
},
[types.TOGGLE_ITEMS_LIST_EDITABILITY](state) {
Object.assign(state, {
isItemsListEditable: !state.isItemsListEditable,
});
},
[types.REQUEST_FREQUENT_ITEMS](state) {
Object.assign(state, {
isLoadingItems: true,
hasSearchQuery: false,
});
},
[types.RECEIVE_FREQUENT_ITEMS_SUCCESS](state, rawItems) {
Object.assign(state, {
items: rawItems,
isLoadingItems: false,
hasSearchQuery: false,
isFetchFailed: false,
});
},
[types.RECEIVE_FREQUENT_ITEMS_ERROR](state) {
Object.assign(state, {
isLoadingItems: false,
hasSearchQuery: false,
isFetchFailed: true,
});
},
[types.REQUEST_SEARCHED_ITEMS](state) {
Object.assign(state, {
isLoadingItems: true,
hasSearchQuery: true,
});
},
[types.RECEIVE_SEARCHED_ITEMS_SUCCESS](state, results) {
const rawItems = results.data;
Object.assign(state, {
items: rawItems.map((rawItem) => ({
id: rawItem.id,
name: rawItem.name,
namespace: rawItem.name_with_namespace || rawItem.full_name,
webUrl: rawItem.web_url,
avatarUrl: rawItem.avatar_url,
})),
isLoadingItems: false,
hasSearchQuery: true,
isFetchFailed: false,
});
},
[types.RECEIVE_SEARCHED_ITEMS_ERROR](state) {
Object.assign(state, {
isLoadingItems: false,
hasSearchQuery: true,
isFetchFailed: true,
});
},
[types.RECEIVE_REMOVE_FREQUENT_ITEM_SUCCESS](state, itemId) {
Object.assign(state, {
items: state.items.filter((item) => item.id !== itemId),
isItemRemovalFailed: false,
});
},
[types.RECEIVE_REMOVE_FREQUENT_ITEM_ERROR](state) {
Object.assign(state, {
isItemRemovalFailed: true,
});
},
};

View File

@ -1,11 +0,0 @@
export default ({ dropdownType = '' } = {}) => ({
namespace: '',
dropdownType,
storageKey: '',
searchQuery: '',
isLoadingItems: false,
isFetchFailed: false,
isItemsListEditable: false,
isItemRemovalFailed: false,
items: [],
});

View File

@ -1,67 +0,0 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { take } from 'lodash';
import { sanitize } from '~/lib/dompurify';
import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from './constants';
export const isMobile = () => ['md', 'sm', 'xs'].includes(bp.getBreakpointSize());
export const getTopFrequentItems = (items) => {
if (!items) {
return [];
}
const frequentItemsCount = isMobile()
? FREQUENT_ITEMS.LIST_COUNT_MOBILE
: FREQUENT_ITEMS.LIST_COUNT_DESKTOP;
const frequentItems = items.filter((item) => item.frequency >= FREQUENT_ITEMS.ELIGIBLE_FREQUENCY);
if (!frequentItems || frequentItems.length === 0) {
return [];
}
frequentItems.sort((itemA, itemB) => {
// Sort all frequent items in decending order of frequency
// and then by lastAccessedOn with recent most first
if (itemA.frequency !== itemB.frequency) {
return itemB.frequency - itemA.frequency;
}
if (itemA.lastAccessedOn !== itemB.lastAccessedOn) {
return itemB.lastAccessedOn - itemA.lastAccessedOn;
}
return 0;
});
return take(frequentItems, frequentItemsCount);
};
export const updateExistingFrequentItem = (frequentItem, item) => {
// `frequentItem` comes from localStorage and it's possible it doesn't have a `lastAccessedOn`
const neverAccessed = !frequentItem.lastAccessedOn;
const shouldUpdate =
neverAccessed ||
Math.abs(item.lastAccessedOn - frequentItem.lastAccessedOn) / FIFTEEN_MINUTES_IN_MS > 1;
return {
...item,
frequency: shouldUpdate ? frequentItem.frequency + 1 : frequentItem.frequency,
lastAccessedOn: shouldUpdate ? Date.now() : frequentItem.lastAccessedOn,
};
};
export const sanitizeItem = (item) => {
// Only sanitize if the key exists on the item
const maybeSanitize = (key) => {
if (!Object.prototype.hasOwnProperty.call(item, key)) {
return {};
}
return { [key]: sanitize(item[key].toString(), { ALLOWED_TAGS: [] }) };
};
return {
...item,
...maybeSanitize('name'),
...maybeSanitize('namespace'),
};
};

View File

@ -1,10 +1,14 @@
<script>
import { __ } from '~/locale';
import { sprintf, s__, __ } from '~/locale';
import { getParameterValues } from '~/lib/utils/url_utility';
import ImportDetailsTable from '~/import/details/components/import_details_table.vue';
export default {
name: 'BulkImportDetailsApp',
components: { ImportDetailsTable },
components: {
ImportDetailsTable,
},
fields: [
{
@ -28,12 +32,25 @@ export default {
],
LOCAL_STORAGE_KEY: 'gl-bulk-import-details-page-size',
gitlabLogo: window.gon.gitlab_logo,
computed: {
title() {
const id = getParameterValues('entity_id')[0];
return sprintf(s__('BulkImport|Items that failed to be imported for %{id}'), { id });
},
},
};
</script>
<template>
<div>
<h1>{{ s__('Import|GitLab Migration details') }}</h1>
<h1 class="gl-font-size-h1 gl-my-0 gl-py-4 gl-display-flex gl-align-items-center gl-gap-3">
<img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6" />
<span>{{ title }}</span>
</h1>
<import-details-table
bulk-import

View File

@ -140,7 +140,7 @@ export default {
<template>
<div>
<gl-table :fields="fields" :items="items" class="gl-mt-5" :busy="loading" show-empty>
<gl-table :fields="fields" :items="items" :busy="loading" show-empty>
<template #table-busy>
<gl-loading-icon size="lg" class="gl-my-5" />
</template>

View File

@ -591,11 +591,11 @@ export default {
<template>
<div>
<div
class="gl-display-flex gl-align-items-center gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1"
class="gl-display-flex gl-align-items-center gl-border-solid gl-border-gray-100 gl-border-0 gl-border-b-1"
>
<h1 class="gl-my-0 gl-py-4 gl-font-size-h1gl-display-flex">
<img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" />
{{ s__('BulkImport|Import groups from GitLab') }}
<h1 class="gl-font-size-h1 gl-my-0 gl-py-4 gl-display-flex gl-align-items-center gl-gap-3">
<img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6" />
<span>{{ s__('BulkImport|Import groups by direct transfer') }}</span>
</h1>
<gl-link :href="historyPath" class="gl-ml-auto">{{ s__('BulkImport|History') }}</gl-link>
</div>

View File

@ -320,7 +320,7 @@ export default {
:loading="isToggleStateButtonLoading"
placement="right"
>
<template v-if="showMovedSidebarOptions">
<template v-if="showMovedSidebarOptions && !glFeatures.notificationsTodosButtons">
<sidebar-subscriptions-widget
:iid="String(iid)"
:full-path="fullPath"

View File

@ -1,92 +0,0 @@
import { mapValues, isString } from 'lodash';
// eslint-disable-next-line no-restricted-imports
import { mapState, mapActions } from 'vuex';
export const REQUIRE_STRING_ERROR_MESSAGE =
'`vuex_module_mappers` can only be used with an array of strings, or an object with string values. Consider using the regular `vuex` map helpers instead.';
const normalizeFieldsToObject = (fields) => {
return Array.isArray(fields)
? fields.reduce((acc, key) => Object.assign(acc, { [key]: key }), {})
: fields;
};
const mapVuexModuleFields = ({ namespaceSelector, fields, vuexHelper, selector } = {}) => {
// The `vuexHelper` needs an object which maps keys to field selector functions.
const map = mapValues(normalizeFieldsToObject(fields), (value) => {
if (!isString(value)) {
throw new Error(REQUIRE_STRING_ERROR_MESSAGE);
}
// We need to use a good ol' function to capture the right "this".
return function mappedFieldSelector(...args) {
const namespace = namespaceSelector(this);
return selector(namespace, value, ...args);
};
});
return vuexHelper(map);
};
/**
* Like `mapState`, but takes a function in the first param for selecting a namespace.
*
* ```
* computed: {
* ...mapVuexModuleState(vm => vm.vuexModule, ['foo']),
* }
* ```
*
* @param {Function} namespaceSelector
* @param {Array|Object} fields
*/
export const mapVuexModuleState = (namespaceSelector, fields) =>
mapVuexModuleFields({
namespaceSelector,
fields,
vuexHelper: mapState,
selector: (namespace, value, state) => state[namespace][value],
});
/**
* Like `mapActions`, but takes a function in the first param for selecting a namespace.
*
* ```
* methods: {
* ...mapVuexModuleActions(vm => vm.vuexModule, ['fetchFoos']),
* }
* ```
*
* @param {Function} namespaceSelector
* @param {Array|Object} fields
*/
export const mapVuexModuleActions = (namespaceSelector, fields) =>
mapVuexModuleFields({
namespaceSelector,
fields,
vuexHelper: mapActions,
selector: (namespace, value, dispatch, ...args) => dispatch(`${namespace}/${value}`, ...args),
});
/**
* Like `mapGetters`, but takes a function in the first param for selecting a namespace.
*
* ```
* computed: {
* ...mapGetters(vm => vm.vuexModule, ['hasSearchInfo']),
* }
* ```
*
* @param {Function} namespaceSelector
* @param {Array|Object} fields
*/
export const mapVuexModuleGetters = (namespaceSelector, fields) =>
mapVuexModuleFields({
namespaceSelector,
fields,
// `mapGetters` does not let us pass an object which maps to functions. Thankfully `mapState` does
// and gives us access to the getters.
vuexHelper: mapState,
selector: (namespace, value, state, getters) => getters[`${namespace}/${value}`],
});

View File

@ -233,14 +233,11 @@ export default {
<template>
<div>
<div
class="gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex gl-align-items-center"
>
<h1 class="gl-my-0 gl-py-4 gl-font-size-h1">
<img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" />
{{ s__('BulkImport|GitLab Migration history') }}
</h1>
</div>
<h1 class="gl-font-size-h1 gl-my-0 gl-py-4 gl-display-flex gl-align-items-center gl-gap-3">
<img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6" />
<span>{{ s__('BulkImport|Direct transfer history') }}</span>
</h1>
<gl-loading-icon v-if="loading" size="lg" class="gl-mt-5" />
<gl-empty-state
v-else-if="!hasHistoryItems"

View File

@ -154,9 +154,7 @@ export default {
</gl-link>
<span v-else>{{ item.import_url }}</span>
</template>
<span v-else>{{
s__('BulkImport|Template / File-based import / GitLab Migration')
}}</span>
<span v-else>{{ s__('BulkImport|Template / File-based import / Direct transfer') }}</span>
</template>
<template #cell(destination)="{ item }">
<gl-link :href="item.http_url_to_repo">

View File

@ -9,7 +9,7 @@ import ServiceDeskSetting from './service_desk_setting.vue';
const CustomEmailWrapper = () => import('./custom_email_wrapper.vue');
export default {
serviceDeskEmailHelpPath: helpPagePath('/user/project/service_desk.html', {
serviceDeskEmailHelpPath: helpPagePath('/user/project/service_desk/configure.html', {
anchor: 'use-an-additional-service-desk-alias-email',
}),
components: {

View File

@ -132,12 +132,12 @@ export default {
return this.serviceDeskEmail && this.serviceDeskEmail !== this.incomingEmail;
},
emailSuffixHelpUrl() {
return helpPagePath('user/project/service_desk.html', {
return helpPagePath('user/project/service_desk/configure.html', {
anchor: 'configure-a-suffix-for-service-desk-alias-email',
});
},
serviceDeskEmailAddressHelpUrl() {
return helpPagePath('user/project/service_desk.html', {
return helpPagePath('user/project/service_desk/configure.html', {
anchor: 'use-an-additional-service-desk-alias-email',
});
},

View File

@ -57,3 +57,15 @@ export const DROPDOWN_Y_OFFSET = 4;
export const NAV_ITEM_LINK_ACTIVE_CLASS = 'gl-bg-t-gray-a-08';
export const IMPERSONATING_OFFSET = 34;
// Frequent items constants
export const FREQUENT_ITEMS = {
MAX_COUNT: 20,
ELIGIBLE_FREQUENCY: 3,
};
export const FIFTEEN_MINUTES_IN_MS = 900000;
export const STORAGE_KEY = {
projects: 'frequent-projects',
};

View File

@ -1,6 +1,6 @@
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import AccessorUtilities from '~/lib/utils/accessor';
import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants';
import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/super_sidebar/constants';
import axios from '~/lib/utils/axios_utils';
/**

View File

@ -88,7 +88,7 @@ export default {
placement="right"
searchable
size="small"
class="comment-template-dropdown gl-mr-3"
class="comment-template-dropdown gl-mr-2"
positioning-strategy="fixed"
:searching="$apollo.queries.savedReplies.loading"
@shown="fetchCommentTemplates"

View File

@ -21,6 +21,7 @@ import { updateText } from '~/lib/utils/text_markdown';
import ToolbarButton from './toolbar_button.vue';
import DrawioToolbarButton from './drawio_toolbar_button.vue';
import CommentTemplatesDropdown from './comment_templates_dropdown.vue';
import HeaderDivider from './header_divider.vue';
export default {
components: {
@ -30,6 +31,7 @@ export default {
DrawioToolbarButton,
CommentTemplatesDropdown,
AiActionsDropdown: () => import('ee_component/ai/components/ai_actions_dropdown.vue'),
HeaderDivider,
},
directives: {
GlTooltip: GlTooltipDirective,
@ -261,249 +263,271 @@ export default {
<div class="gl-display-flex gl-align-items-center gl-flex-wrap">
<div
data-testid="md-header-toolbar"
class="md-header-toolbar gl-display-flex gl-py-3 gl-flex-wrap gl-row-gap-3"
class="md-header-toolbar gl-display-flex gl-py-3 gl-row-gap-2 gl-flex-grow-1 gl-align-items-flex-start"
>
<gl-button
v-if="enablePreview"
data-testid="preview-toggle"
:value="previewMarkdown ? 'preview' : 'edit'"
:label="$options.i18n.previewTabTitle"
class="js-md-preview-button gl-flex-direction-row-reverse gl-align-items-center gl-font-weight-normal! gl-mr-2"
size="small"
category="tertiary"
@click="switchPreview"
>{{ previewMarkdown ? $options.i18n.hidePreview : $options.i18n.preview }}</gl-button
>
<template v-if="!previewMarkdown && canSuggest">
<toolbar-button
ref="suggestButton"
:tag="mdSuggestion"
:prepend="true"
:button-title="__('Insert suggestion')"
:cursor-offset="4"
:tag-content="lineContent"
tracking-property="codeSuggestion"
icon="doc-code"
data-testid="suggestion-button"
class="js-suggestion-btn"
@click="handleSuggestDismissed"
/>
<gl-popover
v-if="suggestPopoverVisible"
:target="$refs.suggestButton.$el"
:css-classes="['diff-suggest-popover']"
placement="bottom"
:show="suggestPopoverVisible"
triggers=""
<div class="gl-display-flex gl-flex-wrap gl-row-gap-2">
<gl-button
v-if="enablePreview"
data-testid="preview-toggle"
:value="previewMarkdown ? 'preview' : 'edit'"
:label="$options.i18n.previewTabTitle"
class="js-md-preview-button gl-flex-direction-row-reverse gl-align-items-center gl-font-weight-normal!"
size="small"
category="tertiary"
@click="switchPreview"
>{{ previewMarkdown ? $options.i18n.hidePreview : $options.i18n.preview }}</gl-button
>
<strong>{{ __('New! Suggest changes directly') }}</strong>
<p class="mb-2">
{{
__(
'Suggest code changes which can be immediately applied in one click. Try it out!',
)
}}
</p>
<gl-button
variant="confirm"
category="primary"
size="small"
data-testid="dismiss-suggestion-popover-button"
@click="handleSuggestDismissed"
<template v-if="!previewMarkdown && canSuggest">
<div class="gl-display-flex gl-row-gap-2">
<header-divider :preview-markdown="previewMarkdown" />
<toolbar-button
ref="suggestButton"
:tag="mdSuggestion"
:prepend="true"
:button-title="__('Insert suggestion')"
:cursor-offset="4"
:tag-content="lineContent"
tracking-property="codeSuggestion"
icon="doc-code"
data-testid="suggestion-button"
class="js-suggestion-btn"
@click="handleSuggestDismissed"
/>
<gl-popover
v-if="suggestPopoverVisible"
:target="$refs.suggestButton.$el"
:css-classes="['diff-suggest-popover']"
placement="bottom"
:show="suggestPopoverVisible"
triggers=""
>
<strong>{{ __('New! Suggest changes directly') }}</strong>
<p class="mb-2">
{{
__(
'Suggest code changes which can be immediately applied in one click. Try it out!',
)
}}
</p>
<gl-button
variant="confirm"
category="primary"
size="small"
data-testid="dismiss-suggestion-popover-button"
@click="handleSuggestDismissed"
>
{{ __('Got it') }}
</gl-button>
</gl-popover>
</div>
</template>
<div class="gl-display-flex gl-row-gap-2">
<div
v-if="!previewMarkdown && editorAiActions.length"
class="gl-display-flex gl-row-gap-2"
>
{{ __('Got it') }}
</gl-button>
</gl-popover>
</template>
<ai-actions-dropdown
v-if="!previewMarkdown && editorAiActions.length"
:actions="editorAiActions"
@input="insertAIAction"
@replace="replaceTextarea"
/>
<toolbar-button
v-show="!previewMarkdown"
tag="**"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), {
modifierKey,
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.bold"
icon="bold"
tracking-property="bold"
/>
<toolbar-button
v-show="!previewMarkdown"
tag="_"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), {
modifierKey,
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.italic"
icon="italic"
tracking-property="italic"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('strikethrough')"
v-show="!previewMarkdown"
tag="~~"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}%{shiftKey}X)'), {
modifierKey,
shiftKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
})
"
:shortcuts="$options.shortcuts.strikethrough"
icon="strikethrough"
tracking-property="strike"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('quote')"
v-show="!previewMarkdown"
:prepend="true"
:tag="tag"
:button-title="__('Insert a quote')"
icon="quote"
tracking-property="blockquote"
@click="handleQuote"
/>
<toolbar-button
v-show="!previewMarkdown"
tag="`"
tag-block="```"
:button-title="__('Insert code')"
icon="code"
tracking-property="code"
/>
<toolbar-button
v-show="!previewMarkdown"
tag="[{text}](url)"
tag-select="url"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), {
modifierKey,
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.link"
icon="link"
tracking-property="link"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('bullet-list')"
v-show="!previewMarkdown"
:prepend="true"
tag="- "
:button-title="__('Add a bullet list')"
icon="list-bulleted"
tracking-property="bulletList"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('numbered-list')"
v-show="!previewMarkdown"
:prepend="true"
tag="1. "
:button-title="__('Add a numbered list')"
icon="list-numbered"
tracking-property="orderedList"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('task-list')"
v-show="!previewMarkdown"
:prepend="true"
tag="- [ ] "
:button-title="__('Add a checklist')"
icon="list-task"
tracking-property="taskList"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('indent')"
v-show="!previewMarkdown"
class="gl-display-none"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Indent line (%{modifierKey}])'), {
modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
})
"
:shortcuts="$options.shortcuts.indent"
command="indentLines"
icon="list-indent"
tracking-property="indent"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('outdent')"
v-show="!previewMarkdown"
class="gl-display-none"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Outdent line (%{modifierKey}[)'), {
modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
})
"
:shortcuts="$options.shortcuts.outdent"
command="outdentLines"
icon="list-outdent"
tracking-property="outdent"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('collapsible-section')"
v-show="!previewMarkdown"
:tag="mdCollapsibleSection"
:prepend="true"
tag-select="Click to expand"
:button-title="__('Add a collapsible section')"
icon="details-block"
tracking-property="details"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('table')"
v-show="!previewMarkdown"
:tag="mdTable"
:prepend="true"
:button-title="__('Add a table')"
icon="table"
tracking-property="table"
/>
<toolbar-button
v-if="!previewMarkdown && !restrictedToolBarItems.includes('attach-file')"
data-testid="button-attach-file"
data-button-type="attach-file"
:button-title="__('Attach a file or image')"
icon="paperclip"
class="gl-mr-3"
tracking-property="upload"
@click="handleAttachFile"
/>
<drawio-toolbar-button
v-if="!previewMarkdown && drawioEnabled"
:uploads-path="uploadsPath"
:markdown-preview-path="markdownPreviewPath"
/>
<!-- TODO Add icon and trigger functionality from here -->
<toolbar-button
v-if="supportsQuickActions"
v-show="!previewMarkdown"
:prepend="true"
tag="/"
:button-title="__('Add a quick action')"
icon="quick-actions"
tracking-property="quickAction"
/>
<comment-templates-dropdown
v-if="!previewMarkdown && newCommentTemplatePath"
:new-comment-template-path="newCommentTemplatePath"
@select="insertSavedReply"
/>
<div v-if="!previewMarkdown" class="full-screen">
<header-divider :preview-markdown="previewMarkdown" />
<ai-actions-dropdown
:actions="editorAiActions"
@input="insertAIAction"
@replace="replaceTextarea"
/>
</div>
<header-divider :preview-markdown="previewMarkdown" />
</div>
<toolbar-button
v-show="!previewMarkdown"
tag="**"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), {
modifierKey,
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.bold"
icon="bold"
tracking-property="bold"
/>
<toolbar-button
v-show="!previewMarkdown"
tag="_"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), {
modifierKey,
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.italic"
icon="italic"
tracking-property="italic"
/>
<div class="gl-display-flex gl-row-gap-2">
<toolbar-button
v-if="!restrictedToolBarItems.includes('strikethrough')"
v-show="!previewMarkdown"
tag="~~"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}%{shiftKey}X)'), {
modifierKey,
shiftKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
})
"
:shortcuts="$options.shortcuts.strikethrough"
icon="strikethrough"
tracking-property="strike"
/>
<header-divider :preview-markdown="previewMarkdown" />
</div>
<toolbar-button
v-if="!restrictedToolBarItems.includes('quote')"
v-show="!previewMarkdown"
:prepend="true"
:tag="tag"
:button-title="__('Insert a quote')"
icon="quote"
tracking-property="blockquote"
@click="handleQuote"
/>
<toolbar-button
v-show="!previewMarkdown"
tag="`"
tag-block="```"
:button-title="__('Insert code')"
icon="code"
tracking-property="code"
/>
<toolbar-button
v-show="!previewMarkdown"
tag="[{text}](url)"
tag-select="url"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), {
modifierKey,
}) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */
"
:shortcuts="$options.shortcuts.link"
icon="link"
tracking-property="link"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('bullet-list')"
v-show="!previewMarkdown"
:prepend="true"
tag="- "
:button-title="__('Add a bullet list')"
icon="list-bulleted"
tracking-property="bulletList"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('numbered-list')"
v-show="!previewMarkdown"
:prepend="true"
tag="1. "
:button-title="__('Add a numbered list')"
icon="list-numbered"
tracking-property="orderedList"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('task-list')"
v-show="!previewMarkdown"
:prepend="true"
tag="- [ ] "
:button-title="__('Add a checklist')"
icon="list-task"
tracking-property="taskList"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('indent')"
v-show="!previewMarkdown"
class="gl-display-none"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Indent line (%{modifierKey}])'), {
modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
})
"
:shortcuts="$options.shortcuts.indent"
command="indentLines"
icon="list-indent"
tracking-property="indent"
/>
<toolbar-button
v-if="!restrictedToolBarItems.includes('outdent')"
v-show="!previewMarkdown"
class="gl-display-none"
:button-title="
/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */
sprintf(s__('MarkdownEditor|Outdent line (%{modifierKey}[)'), {
modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */,
})
"
:shortcuts="$options.shortcuts.outdent"
command="outdentLines"
icon="list-outdent"
tracking-property="outdent"
/>
<div class="gl-display-flex gl-row-gap-2">
<toolbar-button
v-if="!restrictedToolBarItems.includes('collapsible-section')"
v-show="!previewMarkdown"
:tag="mdCollapsibleSection"
:prepend="true"
tag-select="Click to expand"
:button-title="__('Add a collapsible section')"
icon="details-block"
tracking-property="details"
/>
<header-divider :preview-markdown="previewMarkdown" />
</div>
<toolbar-button
v-if="!restrictedToolBarItems.includes('table')"
v-show="!previewMarkdown"
:tag="mdTable"
:prepend="true"
:button-title="__('Add a table')"
icon="table"
tracking-property="table"
/>
<toolbar-button
v-if="!previewMarkdown && !restrictedToolBarItems.includes('attach-file')"
data-testid="button-attach-file"
data-button-type="attach-file"
:button-title="__('Attach a file or image')"
icon="paperclip"
class="gl-mr-2"
tracking-property="upload"
@click="handleAttachFile"
/>
<drawio-toolbar-button
v-if="!previewMarkdown && drawioEnabled"
:uploads-path="uploadsPath"
:markdown-preview-path="markdownPreviewPath"
/>
<!-- TODO Add icon and trigger functionality from here -->
<toolbar-button
v-if="supportsQuickActions"
v-show="!previewMarkdown"
:prepend="true"
tag="/"
:button-title="__('Add a quick action')"
icon="quick-actions"
tracking-property="quickAction"
/>
<comment-templates-dropdown
v-if="!previewMarkdown && newCommentTemplatePath"
:new-comment-template-path="newCommentTemplatePath"
@select="insertSavedReply"
/>
</div>
<div
v-if="!previewMarkdown"
class="full-screen gl-flex-grow-1 gl-justify-content-end gl-display-flex"
>
<toolbar-button
v-if="!restrictedToolBarItems.includes('full-screen')"
class="js-zen-enter"
class="js-zen-enter gl-mr-0!"
icon="maximize"
:button-title="__('Go full screen')"
:prepend="true"

View File

@ -0,0 +1,16 @@
<script>
export default {
props: {
previewMarkdown: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<div v-if="!previewMarkdown" class="md-toolbar-divider gl-display-flex gl-py-2">
<div class="gl-border-l gl-pl-3 gl-ml-2"></div>
</div>
</template>

View File

@ -111,7 +111,7 @@ export default {
type="button"
category="tertiary"
size="small"
class="js-md gl-mr-3"
class="js-md gl-mr-2"
data-container="body"
@click="$emit('click', $event)"
/>

View File

@ -5,7 +5,6 @@
@import './pages/hierarchy';
@import './pages/issues';
@import './pages/note_form';
@import './pages/notes';
@import './pages/pipelines';
@import './pages/profile';
@import './pages/registry';

View File

@ -836,95 +836,6 @@
}
}
.frequent-items-dropdown-container {
display: flex;
flex-direction: row;
height: $grid-size * 40;
.frequent-items-dropdown-content {
@include gl-pt-3;
}
.loading-animation {
color: $gray-950;
}
.frequent-items-dropdown-content {
position: relative;
width: 70%;
}
.section-header,
.frequent-items-list-container li.section-empty {
color: $gl-text-color-secondary;
font-size: $gl-font-size;
}
.frequent-items-list-container {
padding: 8px 0;
overflow-y: auto;
li.section-empty.section-failure {
color: $red-700;
}
.frequent-items-list-item-container .gl-button {
&:active,
&:focus,
&:focus:active,
&.is-focused {
@include gl-focus($inset: true);
}
}
}
.section-header {
font-weight: 700;
margin-top: 8px;
}
}
.frequent-items-list-item-container {
.frequent-items-item-metadata-container {
display: flex;
flex-shrink: 0;
flex-direction: column;
justify-content: center;
}
.frequent-items-item-title,
.frequent-items-item-namespace {
max-width: 220px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.frequent-items-item-title {
font-size: $gl-font-size;
font-weight: 400;
line-height: 16px;
}
.frequent-items-item-namespace {
margin-top: 4px;
font-size: 12px;
line-height: 12px;
color: $gl-text-color-secondary;
}
@include media-breakpoint-down(xs) {
.frequent-items-item-metadata-container {
float: none;
}
.frequent-items-item-title,
.frequent-items-item-namespace {
max-width: 250px;
}
}
}
.dropdown-content-faded-mask {
position: relative;

View File

@ -249,10 +249,6 @@
font-size: 10px;
}
.frequent-items-item-select-holder {
display: inline;
}
.impersonation i {
color: $red-500;
}
@ -523,16 +519,6 @@
}
}
.top-nav-container-view {
.gl-dropdown & .gl-search-box-by-type {
@include gl-m-0;
}
.frequent-items-list-item-container > a:hover {
background-color: $nav-active-bg !important;
}
}
.top-nav-toggle {
.dropdown-chevron {
top: 0;

View File

@ -1,3 +1,7 @@
@import 'mixins_and_variables_and_functions';
@import 'framework/notes';
@import 'framework/buttons';
$avatar-icon-size: 2rem;
$avatar-m-top: 0.5rem;
$avatar-m-ratio: 2;
@ -985,7 +989,7 @@ $system-note-icon-m-left: $avatar-m-left + $icon-size-diff / $avatar-m-ratio;
.disabled-comment {
background-color: $gray-light;
border-radius: $border-radius-base;
border-radius: $gl-border-radius-base;
border: 1px solid $border-gray-normal;
color: $note-disabled-comment-color;
padding: $gl-padding-8 0;

View File

@ -13,6 +13,21 @@ module Packages
project.packages.installable
end
# /!\ This function doesn't check user permissions
# at the package level.
def packages_for(user, within_group:)
return ::Packages::Package.none unless within_group
return ::Packages::Package.none unless Ability.allowed?(user, :read_group, within_group)
projects = if user.is_a?(DeployToken)
user.accessible_projects
else
within_group.all_projects
end
::Packages::Package.for_projects(projects).installable
end
def packages_visible_to_user(user, within_group:, with_package_registry_enabled: false)
return ::Packages::Package.none unless within_group
return ::Packages::Package.none unless Ability.allowed?(user, :read_group, within_group)

View File

@ -3,6 +3,8 @@
module Packages
module Maven
class PackageFinder < ::Packages::GroupOrProjectPackageFinder
extend ::Gitlab::Utils::Override
def execute
packages
end
@ -15,6 +17,15 @@ module Packages
matching_packages
end
override :group_packages
def group_packages
if Feature.enabled?(:maven_remove_permissions_check_from_finder, @project_or_group)
packages_for(@current_user, within_group: @project_or_group)
else
super
end
end
end
end
end

View File

@ -1,14 +0,0 @@
# frozen_string_literal: true
module JsonHelper
# These two JSON helpers are short-form wrappers for the Gitlab::Json
# class, which should be used in place of .to_json calls or calls to
# the JSON class.
def json_generate(...)
Gitlab::Json.generate(...)
end
def json_parse(...)
Gitlab::Json.parse(...)
end
end

View File

@ -1,4 +1,5 @@
- page_title s_('WorkItem|Work items')
- add_page_specific_style 'page_bundles/issuable_list'
- add_page_specific_style 'page_bundles/notes'
.js-work-items-list-root{ data: work_items_list_data(@group) }

View File

@ -1,5 +1,8 @@
- add_to_breadcrumbs _('New group'), new_group_path
- add_to_breadcrumbs _('Import group'), new_group_path(anchor: 'import-group-pane')
- page_title s_('Import|GitLab Migration details')
- add_to_breadcrumbs s_('BulkImport|Direct transfer history'), history_import_bulk_imports_path
- if params[:id].present?
- add_to_breadcrumbs params[:id], history_import_bulk_imports_path(bulk_import_id: params[:id])
- page_title format(s_('Import|Failures for %{id}'), id: params[:entity_id])
.js-bulk-import-details

View File

@ -1,6 +1,9 @@
- add_to_breadcrumbs _('New group'), new_group_path
- add_to_breadcrumbs _('Import group'), new_group_path(anchor: 'import-group-pane')
- if params[:bulk_import_id].present?
- add_to_breadcrumbs s_('BulkImport|Direct transfer history'), history_import_bulk_imports_path
- breadcrumb_title params[:bulk_import_id]
- page_title s_('BulkImport|Direct transfer history')
- add_page_specific_style 'page_bundles/import'
- page_title _('Import history')
#import-history-mount-element{ data: { details_path: details_import_bulk_imports_path, realtime_changes_path: realtime_changes_import_bulk_imports_path(format: :json) } }

View File

@ -1,6 +1,8 @@
- add_to_breadcrumbs _('New group'), new_group_path
- add_to_breadcrumbs _('Import group'), new_group_path(anchor: 'import-group-pane')
- breadcrumb_title s_('BulkImport|Direct transfer')
- page_title s_('BulkImport|Import groups by direct transfer')
- add_page_specific_style 'page_bundles/import'
- page_title _('Import groups')
#import-groups-mount-element{ data: { status_path: status_import_bulk_imports_path(format: :json),
default_target_namespace: @namespace&.id,

View File

@ -2,6 +2,10 @@
- site_name = _('GitLab')
- omit_og = sign_in_with_redirect?
-# This is a temporary place for the page specific style migrations to be included on all pages like page_specific_files
- if Feature.disabled?(:page_specific_styles, current_user)
- add_page_specific_style('page_bundles/notes')
%head{ omit_og ? { } : { prefix: "og: http://ogp.me/ns#" } }
%meta{ charset: "utf-8" }
%meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' }

View File

@ -4,7 +4,7 @@
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Service Desk')
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
= expanded ? _('Collapse') : _('Expand')
- link_start = "<a href='#{help_page_path('user/project/service_desk')}' target='_blank' rel='noopener noreferrer'>".html_safe
- link_start = "<a href='#{help_page_path('user/project/service_desk/index')}' target='_blank' rel='noopener noreferrer'>".html_safe
%p.gl-text-secondary= _('Enable and disable Service Desk. Some additional configuration might be required. %{link_start}Learn more%{link_end}.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.settings-content
- if ::Gitlab::ServiceDesk.supported?

View File

@ -6,6 +6,7 @@
- page_title "#{@commit.title} (#{@commit.short_id})", _('Commits')
- page_description @commit.description
- add_page_specific_style 'page_bundles/pipelines'
- add_page_specific_style 'page_bundles/notes'
.container-fluid{ class: [container_class] }
= render "commit_box"

View File

@ -2,6 +2,7 @@
- add_page_specific_style 'page_bundles/tree'
- add_page_specific_style 'page_bundles/merge_request'
- add_page_specific_style 'page_bundles/projects'
- add_page_specific_style 'page_bundles/notes'
- page_title _("Commits"), @ref
= content_for :meta_tags do

View File

@ -1,5 +1,5 @@
- return unless show_moved_service_desk_issue_warning?(issue)
- service_desk_link_url = help_page_path('user/project/service_desk')
- service_desk_link_url = help_page_path('user/project/service_desk/index')
- service_desk_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: service_desk_link_url }
= render Pajamas::AlertComponent.new(variant: :warning,

View File

@ -17,7 +17,7 @@
%code= @project.service_desk_address
%span= s_("ServiceDesk|Issues created from Service Desk emails will appear here. Each comment becomes part of the email conversation.")
= link_to _('Learn more.'), help_page_path('user/project/service_desk')
= link_to _('Learn more.'), help_page_path('user/project/service_desk/index')
- if can_edit_project_settings && !service_desk_enabled
.text-center

View File

@ -17,7 +17,7 @@
%code= @project.service_desk_address
%span= s_("ServiceDesk|Issues created from Service Desk emails will appear here. Each comment becomes part of the email conversation.")
= link_to _('Learn more.'), help_page_path('user/project/service_desk')
= link_to _('Learn more.'), help_page_path('user/project/service_desk/index')
- if can_edit_project_settings && !service_desk_enabled
.gl-mt-3

View File

@ -12,6 +12,7 @@
- add_page_specific_style 'page_bundles/issuable'
- add_page_specific_style 'page_bundles/issues_show'
- add_page_specific_style 'page_bundles/work_items'
- add_page_specific_style 'page_bundles/notes'
- @content_class = "limit-container-width" unless fluid_layout

View File

@ -4,6 +4,7 @@
- add_page_specific_style 'page_bundles/pipelines'
- add_page_specific_style 'page_bundles/ci_status'
- add_page_specific_style 'page_bundles/merge_request'
- add_page_specific_style 'page_bundles/notes'
- conflicting_mr = @merge_request.existing_mrs_targeting_same_branch.first

View File

@ -1,3 +1,4 @@
- add_page_specific_style 'page_bundles/merge_request'
- add_page_specific_style 'page_bundles/notes'
= render 'page'

View File

@ -7,6 +7,7 @@
- new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request')
- add_page_specific_style 'page_bundles/issuable_list'
- add_page_specific_style 'page_bundles/merge_request'
- add_page_specific_style 'page_bundles/notes'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} merge requests")

View File

@ -1,3 +1,4 @@
- add_page_specific_style 'page_bundles/merge_request'
- add_page_specific_style 'page_bundles/notes'
= render 'page'

View File

@ -1,6 +1,7 @@
- page_title "##{request.params['iid']}"
- add_to_breadcrumbs _("Issues"), project_issues_path(@project)
- add_page_specific_style 'page_bundles/work_items'
- add_page_specific_style 'page_bundles/notes'
- @gfm_form = true
- @noteable_type = 'WorkItem'

View File

@ -1,5 +1,6 @@
- breadcrumb_title _("Edit Snippet")
- page_title _("Edit"), "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
- add_page_specific_style 'page_bundles/notes'
- content_for :prefetch_asset_tags do
- webpack_preload_asset_tag('monaco')

View File

@ -1,4 +1,5 @@
- page_title _("New Snippet")
- add_page_specific_style 'page_bundles/notes'
.page-title-holder.d-flex.align-items-center
%h1.page-title.gl-font-size-h-display= _('New Snippet')

View File

@ -9,6 +9,7 @@
- add_to_breadcrumbs _("Snippets"), explore_snippets_path
- breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
- add_page_specific_style 'page_bundles/notes'
- content_for :prefetch_asset_tags do
- webpack_preload_asset_tag('monaco', prefetch: true)

View File

@ -362,6 +362,7 @@ module Gitlab
config.assets.precompile << "page_bundles/work_items.css"
config.assets.precompile << "page_bundles/xterm.css"
config.assets.precompile << "page_bundles/labels.css"
config.assets.precompile << "page_bundles/notes.css"
config.assets.precompile << "lazy_bundles/cropper.css"
config.assets.precompile << "lazy_bundles/gridstack.css"
config.assets.precompile << "performance_bar.css"

View File

@ -1,20 +0,0 @@
description: "Open frequent items dropdown"
category: default
action: click_link
label_description: "`[dropdown_type]_dropdown_frequent_items_list_item`"
property_description: ""
value_description: ""
extra_properties:
identifiers:
product_section: dev
product_stage: create
product_group: group::editor
milestone: "13.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47589
distributions:
- ce
- ee
tiers:
- free
- premium
- ultimate

View File

@ -0,0 +1,8 @@
---
name: maven_remove_permissions_check_from_finder
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135494
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430267
milestone: '16.7'
type: development
group: group::package registry
default_enabled: false

View File

@ -181,6 +181,7 @@ options:
- p_ci_templates_terraform_module_base
- p_ci_templates_terraform_module
- p_ci_templates_pages_zola
- p_ci_templates_diffblue_cover
distribution:
- ce
- ee

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_diffblue_cover_monthly
description: Count of pipelines using the Diffblue Cover template
product_section: ci
product_stage: pipeline_authoring
product_group: pipeline_authoring
value_type: number
status: active
milestone: "16.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137047
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- p_ci_templates_diffblue_cover

View File

@ -182,6 +182,7 @@ options:
- p_ci_templates_terraform_module_base
- p_ci_templates_terraform_module
- p_ci_templates_pages_zola
- p_ci_templates_diffblue_cover
distribution:
- ce
- ee

View File

@ -0,0 +1,24 @@
---
key_path: redis_hll_counters.ci_templates.p_ci_templates_diffblue_cover_weekly
description: Count of pipelines using the Diffblue Cover template
product_section: ci
product_stage: pipeline_authoring
product_group: pipeline_authoring
value_type: number
status: active
milestone: "16.7"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137047
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
options:
events:
- p_ci_templates_diffblue_cover

View File

@ -22,8 +22,7 @@ You can administer all projects in the GitLab instance from the Admin Area's Pro
To access the Projects page:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Projects**.
1. Select the **All**, **Private**, **Internal**, or **Public** tab to list only
projects of that criteria.
@ -74,8 +73,7 @@ You can combine the filter options. For example, to list only public projects wi
You can administer all users in the GitLab instance from the Admin Area's Users page:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Users**.
To list users matching a specific criteria, select one of the following tabs on the **Users** page:
@ -138,8 +136,7 @@ By default, impersonation is enabled. GitLab can be configured to [disable imper
When using authentication providers, administrators can see the identities for a user:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Users**.
1. From the list of users, select a user.
1. Select **Identities**.
@ -185,8 +182,7 @@ GitLab billing is based on the number of [**Billable users**](../subscriptions/s
You must be an administrator to manually add emails to users:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Users**.
1. Locate the user and select them.
1. Select **Edit**.
@ -202,8 +198,7 @@ The [Cohorts](user_cohorts.md) tab displays the monthly cohorts of new users and
By default, users can create top level groups. To prevent a user from creating a top level group:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Users**.
1. Locate the user and select them.
1. Select **Edit**.
@ -218,8 +213,7 @@ You can administer all groups in the GitLab instance from the Admin Area's Group
To access the Groups page:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Groups**.
For each group, the page displays their name, description, size, number of projects in the group,
@ -244,16 +238,14 @@ To [Create a new group](../user/group/index.md#create-a-group) select **New grou
To view all topics in the GitLab instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Topics**.
For each topic, the page displays its name and the number of projects labeled with the topic.
### Search for topics
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Topics**.
1. In the search box, enter your search criteria.
The topic search is case-insensitive and applies partial matching.
@ -262,8 +254,7 @@ For each topic, the page displays its name and the number of projects labeled wi
To create a topic:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Topics**.
1. Select **New topic**.
1. Enter the **Topic slug (name)** and **Topic title**.
@ -282,8 +273,7 @@ Do not include sensitive information in the name of a topic.
You can edit a topic's name, title, description, and avatar at any time.
To edit a topic:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Topics**.
1. Select **Edit** in that topic's row.
1. Edit the topic slug (name), title, description, or avatar.
@ -294,8 +284,7 @@ To edit a topic:
If you no longer need a topic, you can permanently remove it.
To remove a topic:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Topics**.
1. To remove a topic, select **Remove** in that topic's row.
@ -307,8 +296,7 @@ After a merged topic is deleted, you cannot restore it.
To merge topics:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Topics**.
1. Select **Merge topics**.
1. From the **Source topic** dropdown list, select the topic you want to merge and remove.
@ -322,8 +310,7 @@ page. For more details, see [Gitaly](gitaly/index.md).
To access the **Gitaly Servers** page:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Gitaly Servers**.
For each Gitaly server, the following details are listed:
@ -347,8 +334,7 @@ You can administer all runners in the GitLab instance from the Admin Area's **Ru
To access the **Runners** page:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Runners**.
#### Search and filter runners
@ -374,8 +360,7 @@ You can also filter runners by status, type, and tag. To filter:
You can delete multiple runners at the same time.
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Runners**.
1. To the left of the runners you want to delete, select the checkbox.
To select all of the runners on the page, select the checkbox above
@ -405,8 +390,7 @@ You can administer all jobs in the GitLab instance from the Admin Area's Jobs pa
To access the Jobs page:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **CI/CD > Jobs**. All jobs are listed, in descending order of job ID.
1. Select the **All** tab to list all jobs. Select the **Pending**, **Running**, or **Finished**
tab to list only jobs of that status.

View File

@ -12,8 +12,7 @@ from planning to monitoring.
To see DevOps Reports:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Analytics > DevOps Reports**.
## DevOps Score

View File

@ -18,8 +18,7 @@ Prerequisites:
To view instance-level analytics:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Analytics**, then one of the available analytics:
- [DevOps Reports](dev_ops_reports.md): Provides an overview of your entire instance's feature usage.

View File

@ -19,8 +19,7 @@ Usage Trends data refreshes daily.
To view Usage Trends:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Analytics > Usage Trends**.
## Total counts

View File

@ -9,8 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Several options are available for customizing the appearance of a self-managed instance
of GitLab. To access these settings:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Settings > Appearance**.
## Navigation bar
@ -83,8 +82,7 @@ description, and icon.
To configure the PWA settings:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Settings > Appearance**.
1. Scroll to the **Progressive Web App (PWA)** section.
1. Complete the fields.

View File

@ -357,8 +357,7 @@ Prerequisites:
To add a streaming destination for an instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select **Streams** tab.
1. Select **Add streaming destination** and select **HTTP endpoint** to show the section for adding destinations.
@ -377,8 +376,7 @@ Prerequisites:
To list the streaming destinations for an instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select **Streams** tab.
1. Select the stream to expand it and see all the custom HTTP headers.
@ -391,8 +389,7 @@ Prerequisites:
To update a instance streaming destination's name:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select **Streams** tab.
1. Select the stream to expand.
@ -401,8 +398,7 @@ To update a instance streaming destination's name:
To update a instance streaming destination's custom HTTP headers:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select **Streams** tab.
1. Select the stream to expand.
@ -424,8 +420,7 @@ Prerequisites:
To delete the streaming destinations for an instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select the **Streams** tab.
1. Select the stream to expand.
@ -434,8 +429,7 @@ To delete the streaming destinations for an instance:
To delete only the custom HTTP headers for a streaming destination:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select the **Streams** tab.
1. To the right of the item, **Edit** (**{pencil}**).
@ -462,8 +456,7 @@ Prerequisites:
To list streaming destinations for an instance and see the verification tokens:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select the **Streams** tab.
1. View the verification token on the right side of each item.
@ -479,8 +472,7 @@ A streaming destination that has an event type filter set has a **filtered** (**
To update a streaming destination's event filters:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select the **Streams** tab.
1. Select the stream to expand.
@ -521,8 +513,7 @@ Prerequisites:
To add Google Cloud Logging streaming destinations to an instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select **Streams** tab.
1. Select **Add streaming destination** and select **Google Cloud Logging** to show the section for adding destinations.
@ -539,8 +530,7 @@ Prerequisites:
To list Google Cloud Logging streaming destinations for an instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select **Streams** tab.
1. Select the Google Cloud Logging stream to expand and see all the fields.
@ -553,8 +543,7 @@ Prerequisites:
To update Google Cloud Logging streaming destinations to an instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select **Streams** tab.
1. Select the Google Cloud Logging stream to expand.
@ -572,8 +561,7 @@ Prerequisites:
To delete Google Cloud Logging streaming destinations to an instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. On the main area, select **Streams** tab.
1. Select the Google Cloud Logging stream to expand.

View File

@ -55,8 +55,7 @@ Project audit events can also be accessed using the [Project Audit Events API](.
You can view audit events from user actions across an entire GitLab instance.
To view instance audit events:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. Filter by the following:
- Member of the project (user) who performed the action
@ -82,8 +81,7 @@ After upgrading to a paid tier, you can also see successful sign-in events on au
You can export the current view (including filters) of your instance audit events as a
CSV(comma-separated values) file. To export the instance audit events to CSV:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Audit Events**.
1. Select the available search filters.
1. Select **Export as CSV**.

View File

@ -34,8 +34,7 @@ To create a new user account with auditor access (or change an existing user):
To create a user account with auditor access:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Overview > Users**.
1. Create a new user or edit an existing one. Set **Access Level** to **Auditor**.
1. If you created a user, select **Create user**. For an existing user, select **Save changes**.

View File

@ -225,8 +225,7 @@ field contains no data:
To resolve this:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, go to **Settings > General**.
1. Expand both of the following:
- **Account and limit**.

View File

@ -501,8 +501,7 @@ When global group memberships lock is enabled:
To enable global group memberships lock:
1. [Configure LDAP](index.md#configure-ldap).
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Settings > General**.
1. Expand the **Visibility and access controls** section.
1. Ensure the **Lock memberships to LDAP synchronization** checkbox is selected.
@ -514,8 +513,7 @@ By default, group members with the Owner role can manage [LDAP group synchroniza
GitLab administrators can remove this permission from group Owners:
1. [Configure LDAP](index.md#configure-ldap).
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Settings > General**.
1. Expand **Visibility and access controls**.
1. Ensure the **Allow group owners to manage LDAP-related settings** checkbox is not checked.

View File

@ -25,208 +25,8 @@ For detailed information on restoring GitLab, see [Restore GitLab](restore_gitla
## Migrate to a new server
<!-- some details borrowed from GitLab.com move from Azure to GCP detailed at https://gitlab.com/gitlab-com/migration/-/blob/master/.gitlab/issue_templates/failover.md -->
You can use GitLab backup and restore to migrate your instance to a new server. This section outlines a typical procedure for a GitLab deployment running on a single server.
If you're running GitLab Geo, an alternative option is [Geo disaster recovery for planned failover](../geo/disaster_recovery/planned_failover.md).
WARNING:
Avoid uncoordinated data processing by both the new and old servers, where multiple
servers could connect concurrently and process the same data. For example, when using
[incoming email](../incoming_email.md), if both GitLab instances are
processing email at the same time, then both instances miss some data.
This type of problem can occur with other services as well, such as a
[non-packaged database](https://docs.gitlab.com/omnibus/settings/database.html#using-a-non-packaged-postgresql-database-management-server),
a non-packaged Redis instance, or non-packaged Sidekiq.
Prerequisites:
- Some time before your migration, consider notifying your users of upcoming
scheduled maintenance with a [broadcast message banner](../broadcast_messages.md).
- Ensure your backups are complete and current. Create a complete system-level backup, or
take a snapshot of all servers involved in the migration, in case destructive commands
(like `rm`) are run incorrectly.
### Prepare the new server
To prepare the new server:
1. Copy the
[SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079)
from the old server to avoid man-in-the-middle attack warnings.
See [Manually replicate the primary site's SSH host keys](../geo/replication/configuration.md#step-2-manually-replicate-the-primary-sites-ssh-host-keys) for example steps.
1. [Install and configure GitLab](https://about.gitlab.com/install/) except
[incoming email](../incoming_email.md):
1. Install GitLab.
1. Configure by copying `/etc/gitlab` files from the old server to the new server, and update as necessary.
Read the
[Linux package installation backup and restore instructions](https://docs.gitlab.com/omnibus/settings/backups.html) for more detail.
1. If applicable, disable [incoming email](../incoming_email.md).
1. Block new CI/CD jobs from starting upon initial startup after the backup and restore.
Edit `/etc/gitlab/gitlab.rb` and set the following:
```ruby
nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Stop GitLab to avoid any potential unnecessary and unintentional data processing:
```shell
sudo gitlab-ctl stop
```
1. Configure the new server to allow receiving the Redis database and GitLab backup files:
```shell
sudo rm -f /var/opt/gitlab/redis/dump.rdb
sudo chown <your-linux-username> /var/opt/gitlab/redis /var/opt/gitlab/backups
```
### Prepare and transfer content from the old server
1. Ensure you have an up-to-date system-level backup or snapshot of the old server.
1. Enable [maintenance mode](../maintenance_mode/index.md),
if supported by your GitLab edition.
1. Block new CI/CD jobs from starting:
1. Edit `/etc/gitlab/gitlab.rb`, and set the following:
```ruby
nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Disable periodic background jobs:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. Under the Sidekiq dashboard, select **Cron** tab and then
**Disable All**.
1. Wait for the currently running CI/CD jobs to finish, or accept that jobs that have not completed may be lost.
To view jobs currently running, on the left sidebar, select **Overviews > Jobs**,
and then select **Running**.
1. Wait for Sidekiq jobs to finish:
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. Under the Sidekiq dashboard, select **Queues** and then **Live Poll**.
Wait for **Busy** and **Enqueued** to drop to 0.
These queues contain work that has been submitted by your users;
shutting down before these jobs complete may cause the work to be lost.
Make note of the numbers shown in the Sidekiq dashboard for post-migration verification.
1. Flush the Redis database to disk, and stop GitLab other than the services needed for migration:
```shell
sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket save && sudo gitlab-ctl stop && sudo gitlab-ctl start postgresql && sudo gitlab-ctl start gitaly
```
1. Create a GitLab backup:
```shell
sudo gitlab-backup create
```
1. Disable the following GitLab services and prevent unintentional restarts by adding the following to the bottom of `/etc/gitlab/gitlab.rb`:
```ruby
alertmanager['enable'] = false
gitlab_exporter['enable'] = false
gitlab_pages['enable'] = false
gitlab_workhorse['enable'] = false
grafana['enable'] = false
logrotate['enable'] = false
gitlab_rails['incoming_email_enabled'] = false
nginx['enable'] = false
node_exporter['enable'] = false
postgres_exporter['enable'] = false
postgresql['enable'] = false
prometheus['enable'] = false
puma['enable'] = false
redis['enable'] = false
redis_exporter['enable'] = false
registry['enable'] = false
sidekiq['enable'] = false
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Verify everything is stopped, and confirm no services are running:
```shell
sudo gitlab-ctl status
```
1. Stop Redis on the **new server** before transferring the Redis database backup:
```shell
sudo gitlab-ctl stop redis
```
1. Transfer the Redis database and GitLab backups to the new server:
```shell
sudo scp /var/opt/gitlab/redis/dump.rdb <your-linux-username>@new-server:/var/opt/gitlab/redis
sudo scp /var/opt/gitlab/backups/your-backup.tar <your-linux-username>@new-server:/var/opt/gitlab/backups
```
### Restore data on the new server
1. Restore appropriate file system permissions:
```shell
sudo chown gitlab-redis /var/opt/gitlab/redis
sudo chown gitlab-redis:gitlab-redis /var/opt/gitlab/redis/dump.rdb
sudo chown git:root /var/opt/gitlab/backups
sudo chown git:git /var/opt/gitlab/backups/your-backup.tar
```
1. Start Redis:
```shell
sudo gitlab-ctl start redis
```
1. [Restore the GitLab backup](#restore-gitlab).
1. Verify that the Redis database restored correctly:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. Under the Sidekiq dashboard, verify that the numbers
match with what was shown on the old server.
1. While still under the Sidekiq dashboard, select **Cron** and then **Enable All**
to re-enable periodic background jobs.
1. Test that read-only operations on the GitLab instance work as expected. For example, browse through project repository files, merge requests, and issues.
1. Disable [Maintenance Mode](../maintenance_mode/index.md), if previously enabled.
1. Test that the GitLab instance is working as expected.
1. If applicable, re-enable [incoming email](../incoming_email.md) and test it is working as expected.
1. Update your DNS or load balancer to point at the new server.
1. Unblock new CI/CD jobs from starting by removing the custom NGINX configuration
you added previously:
```ruby
# The following line must be removed
nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Remove the scheduled maintenance [broadcast message banner](../broadcast_messages.md).
For detailed information on using back up and restore to migrate to a new server, see
[Migrate to a new server](migrate_to_new_server.md).
## Additional notes

View File

@ -0,0 +1,210 @@
---
stage: Systems
group: Geo
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Migrate to a new server
<!-- some details borrowed from GitLab.com move from Azure to GCP detailed at https://gitlab.com/gitlab-com/migration/-/blob/master/.gitlab/issue_templates/failover.md -->
You can use GitLab backup and restore to migrate your instance to a new server. This section outlines a typical procedure for a GitLab deployment running on a single server.
If you're running GitLab Geo, an alternative option is [Geo disaster recovery for planned failover](../geo/disaster_recovery/planned_failover.md).
WARNING:
Avoid uncoordinated data processing by both the new and old servers, where multiple
servers could connect concurrently and process the same data. For example, when using
[incoming email](../incoming_email.md), if both GitLab instances are
processing email at the same time, then both instances miss some data.
This type of problem can occur with other services as well, such as a
[non-packaged database](https://docs.gitlab.com/omnibus/settings/database.html#using-a-non-packaged-postgresql-database-management-server),
a non-packaged Redis instance, or non-packaged Sidekiq.
Prerequisites:
- Some time before your migration, consider notifying your users of upcoming
scheduled maintenance with a [broadcast message banner](../broadcast_messages.md).
- Ensure your backups are complete and current. Create a complete system-level backup, or
take a snapshot of all servers involved in the migration, in case destructive commands
(like `rm`) are run incorrectly.
## Prepare the new server
To prepare the new server:
1. Copy the
[SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079)
from the old server to avoid man-in-the-middle attack warnings.
See [Manually replicate the primary site's SSH host keys](../geo/replication/configuration.md#step-2-manually-replicate-the-primary-sites-ssh-host-keys) for example steps.
1. [Install and configure GitLab](https://about.gitlab.com/install/) except
[incoming email](../incoming_email.md):
1. Install GitLab.
1. Configure by copying `/etc/gitlab` files from the old server to the new server, and update as necessary.
Read the
[Linux package installation backup and restore instructions](https://docs.gitlab.com/omnibus/settings/backups.html) for more detail.
1. If applicable, disable [incoming email](../incoming_email.md).
1. Block new CI/CD jobs from starting upon initial startup after the backup and restore.
Edit `/etc/gitlab/gitlab.rb` and set the following:
```ruby
nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Stop GitLab to avoid any potential unnecessary and unintentional data processing:
```shell
sudo gitlab-ctl stop
```
1. Configure the new server to allow receiving the Redis database and GitLab backup files:
```shell
sudo rm -f /var/opt/gitlab/redis/dump.rdb
sudo chown <your-linux-username> /var/opt/gitlab/redis /var/opt/gitlab/backups
```
## Prepare and transfer content from the old server
1. Ensure you have an up-to-date system-level backup or snapshot of the old server.
1. Enable [maintenance mode](../maintenance_mode/index.md),
if supported by your GitLab edition.
1. Block new CI/CD jobs from starting:
1. Edit `/etc/gitlab/gitlab.rb`, and set the following:
```ruby
nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Disable periodic background jobs:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. Under the Sidekiq dashboard, select **Cron** tab and then
**Disable All**.
1. Wait for the currently running CI/CD jobs to finish, or accept that jobs that have not completed may be lost.
To view jobs currently running, on the left sidebar, select **Overviews > Jobs**,
and then select **Running**.
1. Wait for Sidekiq jobs to finish:
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. Under the Sidekiq dashboard, select **Queues** and then **Live Poll**.
Wait for **Busy** and **Enqueued** to drop to 0.
These queues contain work that has been submitted by your users;
shutting down before these jobs complete may cause the work to be lost.
Make note of the numbers shown in the Sidekiq dashboard for post-migration verification.
1. Flush the Redis database to disk, and stop GitLab other than the services needed for migration:
```shell
sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket save && sudo gitlab-ctl stop && sudo gitlab-ctl start postgresql && sudo gitlab-ctl start gitaly
```
1. Create a GitLab backup:
```shell
sudo gitlab-backup create
```
1. Disable the following GitLab services and prevent unintentional restarts by adding the following to the bottom of `/etc/gitlab/gitlab.rb`:
```ruby
alertmanager['enable'] = false
gitlab_exporter['enable'] = false
gitlab_pages['enable'] = false
gitlab_workhorse['enable'] = false
grafana['enable'] = false
logrotate['enable'] = false
gitlab_rails['incoming_email_enabled'] = false
nginx['enable'] = false
node_exporter['enable'] = false
postgres_exporter['enable'] = false
postgresql['enable'] = false
prometheus['enable'] = false
puma['enable'] = false
redis['enable'] = false
redis_exporter['enable'] = false
registry['enable'] = false
sidekiq['enable'] = false
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Verify everything is stopped, and confirm no services are running:
```shell
sudo gitlab-ctl status
```
1. Stop Redis on the **new server** before transferring the Redis database backup:
```shell
sudo gitlab-ctl stop redis
```
1. Transfer the Redis database and GitLab backups to the new server:
```shell
sudo scp /var/opt/gitlab/redis/dump.rdb <your-linux-username>@new-server:/var/opt/gitlab/redis
sudo scp /var/opt/gitlab/backups/your-backup.tar <your-linux-username>@new-server:/var/opt/gitlab/backups
```
## Restore data on the new server
1. Restore appropriate file system permissions:
```shell
sudo chown gitlab-redis /var/opt/gitlab/redis
sudo chown gitlab-redis:gitlab-redis /var/opt/gitlab/redis/dump.rdb
sudo chown git:root /var/opt/gitlab/backups
sudo chown git:git /var/opt/gitlab/backups/your-backup.tar
```
1. Start Redis:
```shell
sudo gitlab-ctl start redis
```
1. [Restore the GitLab backup](restore_gitlab.md).
1. Verify that the Redis database restored correctly:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. Under the Sidekiq dashboard, verify that the numbers
match with what was shown on the old server.
1. While still under the Sidekiq dashboard, select **Cron** and then **Enable All**
to re-enable periodic background jobs.
1. Test that read-only operations on the GitLab instance work as expected. For example, browse through project repository files, merge requests, and issues.
1. Disable [Maintenance Mode](../maintenance_mode/index.md), if previously enabled.
1. Test that the GitLab instance is working as expected.
1. If applicable, re-enable [incoming email](../incoming_email.md) and test it is working as expected.
1. Update your DNS or load balancer to point at the new server.
1. Unblock new CI/CD jobs from starting by removing the custom NGINX configuration
you added previously:
```ruby
# The following line must be removed
nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n }\n"
```
1. Reconfigure GitLab:
```shell
sudo gitlab-ctl reconfigure
```
1. Remove the scheduled maintenance [broadcast message banner](../broadcast_messages.md).

View File

@ -57,8 +57,7 @@ To display messages to users on your GitLab instance, add a broadcast message.
To add a broadcast message:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Messages**.
1. Select **Add new message**.
1. Add the text for the message to the **Message** field. You can style a message's content using Markdown, emoji, and the `a` and `br` HTML tags.
@ -86,8 +85,7 @@ If you must make changes to a broadcast message, you can edit it.
To edit a broadcast message:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Messages**.
1. From the list of broadcast messages, select the edit button for the message.
1. After making the required changes, select **Update broadcast message**.
@ -101,8 +99,7 @@ You can delete a broadcast message while it's active.
To delete a broadcast message:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Messages**.
1. From the list of broadcast messages, select the delete button for the message.

View File

@ -37,8 +37,7 @@ You can also [revoke](#revoke-a-users-personal-access-token), [delete](#delete-a
You can revoke a user's personal access token.
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Credentials**.
1. By the personal access token, select **Revoke**.
@ -58,8 +57,7 @@ When a PAT is revoked from the credentials inventory, the instance notifies the
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/243833) in GitLab 14.8.
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Credentials**.
1. Select the **Project Access Tokens** tab.
1. By the project access token, select **Revoke**.
@ -72,8 +70,7 @@ The project access token is revoked and a background worker is queued to delete
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225248) in GitLab 13.5.
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Credentials**.
1. Select the **SSH Keys** tab.
1. By the SSH key, select **Delete**.

View File

@ -24,8 +24,7 @@ might modify the template projects without understanding the side effects.
To select the group to manage the project templates for your instance:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Settings > Templates**.
1. Expand **Custom project templates**.
1. Select a group to use.

View File

@ -33,8 +33,7 @@ set values are presented as **Too large** are cannot be expanded in the UI.
To configure these values:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Settings > General**.
1. Expand **Diff limits**.
1. Enter a value for the diff limit.

View File

@ -25,8 +25,7 @@ You can send email notifications once every 10 minutes.
To send an email:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Overview > Users**.
1. In the upper-right corner, select **Send email to users** (**{mail}**).
1. Complete the fields. The email body supports only plain text and does not support HTML, Markdown, or other rich text formats.

View File

@ -56,8 +56,7 @@ Additionally, users can be set as external users using:
By default, new users are not set as external users. This behavior can be changed
by an administrator:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. Select **Settings > General**.
1. Expand the **Account and limit** section.

View File

@ -27,8 +27,7 @@ the site more time before scheduling a planned failover.
On the **primary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Sites**.
1. Expand **Verification information** tab for that site to view automatic checksumming
status for repositories and wikis. Successes are shown in green, pending work
@ -38,8 +37,7 @@ On the **primary** site:
On the **secondary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Sites**.
1. Expand **Verification information** tab for that site to view automatic checksumming
status for repositories and wikis. Successes are shown in green, pending work
@ -67,8 +65,7 @@ increase load and vice versa.
On the **primary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Sites**.
1. Select **Edit** for the **primary** site to customize the minimum
re-verification interval:

View File

@ -154,8 +154,7 @@ ensure these processes are close to 100% as possible during active use.
On the **secondary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Sites**.
Replicated objects (shown in green) should be close to 100%,
and there should be no failures (shown in red). If a large proportion of
@ -183,8 +182,7 @@ This [content was moved to another location](background_verification.md).
On the **primary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Messages**.
1. Add a message notifying users on the maintenance window.
You can check under **Geo > Sites** to estimate how long it
@ -197,8 +195,7 @@ To ensure that all data is replicated to a secondary site, updates (write reques
be disabled on the **primary** site:
1. Enable [maintenance mode](../../maintenance_mode/index.md) on the **primary** site.
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. On the Sidekiq dashboard, select **Cron**.
1. Select `Disable All` to disable non-Geo periodic background jobs.

View File

@ -65,8 +65,7 @@ promote a Geo replica and perform a failover.
On the **secondary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Sites** to see its status.
Replicated objects (shown in green) should be close to 100%,
and there should be no failures (shown in red). If a large proportion of

View File

@ -159,8 +159,7 @@ public URL of the primary site is used.
To update the internal URL of the primary Geo site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Sites**.
1. Select **Edit** on the primary site.
1. Change the **Internal URL**, then select **Save changes**.
@ -249,6 +248,8 @@ Pausing and resuming replication is done through a command-line tool from the no
If `postgresql` is on a standalone database node, ensure that `gitlab.rb` on that node contains the configuration line `gitlab_rails['geo_node_name'] = 'node_name'`, where `node_name` is the same as the `geo_node_name` on the application node.
Also, be aware that if PostgreSQL is restarted after pausing replication (either by restarting the VM or restarting the service with `gitlab-ctl restart postgresql`), PostgreSQL automatically resumes replication, which is something you wouldn't want during an upgrade or in a planned failover scenario.
**To Pause: (from secondary)**
```shell

View File

@ -327,8 +327,7 @@ method to be enabled. This is enabled by default, but if converting an existing
On the **primary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Settings > General**.
1. Expand **Visibility and access controls**.
1. If using Git over SSH, then:
@ -341,8 +340,7 @@ On the **primary** site:
You can sign in to the **secondary** site with the same credentials you used with
the **primary** site. After you sign in:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Sites**.
1. Verify that it's correctly identified as a **secondary** Geo site, and that
Geo is enabled.

View File

@ -166,8 +166,7 @@ For each application and Sidekiq node on the **secondary** site:
To verify container registry replication is working, on the **secondary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Nodes**.
The initial replication, or "backfill", is probably still in progress.

View File

@ -36,8 +36,7 @@ to do that.
To remove the **primary** site:
1. [Remove all secondary Geo sites](remove_geo_site.md)
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Nodes**.
1. Select **Remove** for the **primary** node.
1. Confirm by selecting **Remove** when the prompt appears.

View File

@ -43,8 +43,7 @@ whether they are stored on the local file system or in object storage.
To enable GitLab replication:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Nodes**.
1. Select **Edit** on the **secondary** site.
1. In the **Synchronization Settings** section, find the **Allow this secondary node to replicate content on Object Storage**

View File

@ -9,8 +9,7 @@ type: howto
**Secondary** sites can be removed from the Geo cluster using the Geo administration page of the **primary** site. To remove a **secondary** site:
1. On the left sidebar, select **Search or go to**.
1. Select **Admin Area**.
1. On the left sidebar, at the bottom, select **Admin Area**.
1. On the left sidebar, select **Geo > Nodes**.
1. For the **secondary** site you want to remove, select **Remove**.
1. Confirm by selecting **Remove** when the prompt appears.

Some files were not shown because too many files have changed in this diff Show More