Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-11 15:09:58 +00:00
parent dcf94a7641
commit 62cd7010ef
135 changed files with 1537 additions and 1030 deletions

View File

@ -36,6 +36,23 @@
<<: *gitaly-ruby-gems-cache
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
.gitaly-binaries-cache: &gitaly-binaries-cache
key:
files:
- GITALY_SERVER_VERSION
prefix: "gitaly-binaries"
paths:
- tmp/tests/gitaly/_build/bin/
- tmp/tests/gitaly/config.toml
- tmp/tests/gitaly/gitaly2.config.toml
- tmp/tests/gitaly/internal/
- tmp/tests/gitaly/internal_gitaly2/
- tmp/tests/gitaly/internal_sockets/
- tmp/tests/gitaly/Makefile
- tmp/tests/gitaly/praefect.config.toml
- tmp/tests/gitaly/ruby/
policy: pull
.go-pkg-cache: &go-pkg-cache
key: "go-pkg-v1"
paths:
@ -97,6 +114,7 @@
cache:
- *ruby-gems-cache
- *gitaly-ruby-gems-cache
- *gitaly-binaries-cache
- *go-pkg-cache
.setup-test-env-cache-push:
@ -105,6 +123,11 @@
- *gitaly-ruby-gems-cache-push
- *go-pkg-cache-push
.gitaly-binaries-cache-push:
cache:
- <<: *gitaly-binaries-cache
policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
.rails-cache:
cache:
- *ruby-gems-cache

View File

@ -177,6 +177,14 @@ update-setup-test-env-cache:
artifacts:
paths: [] # This job's purpose is only to update the cache.
update-gitaly-binaries-cache:
extends:
- setup-test-env
- .gitaly-binaries-cache-push
- .shared:rules:update-gitaly-binaries-cache
artifacts:
paths: [] # This job's purpose is only to update the cache.
.coverage-base:
extends:
- .default-retry
@ -203,13 +211,6 @@ update-static-analysis-cache:
- .shared:rules:update-cache
stage: prepare
script:
- git log -n 1 --pretty=format:%H -- .rubocop.yml
- git log -n 1 --pretty=format:%H -- .rubocop_manual_todo.yml
- git log -n 1 --pretty=format:%H -- .rubocop_todo.yml
- git log -n 1 --pretty=format:%H -- rubocop/rubocop-migrations.yml
- git log -n 1 --pretty=format:%H -- rubocop/rubocop-usage-data.yml
- git log -n 1 --pretty=format:%H -- rubocop/rubocop-code_reuse.yml
- bundle exec scripts/debug-rubocop spec/factories/namespace/aggregation_schedules.rb
- run_timed_command "bundle exec rubocop --parallel" # For the moment we only cache `tmp/rubocop_cache` so we don't need to run all the tasks.
static-analysis:
@ -220,13 +221,6 @@ static-analysis:
parallel: 4
script:
- run_timed_command "retry yarn install --frozen-lockfile"
- git log -n 1 --pretty=format:%H -- .rubocop.yml
- git log -n 1 --pretty=format:%H -- .rubocop_manual_todo.yml
- git log -n 1 --pretty=format:%H -- .rubocop_todo.yml
- git log -n 1 --pretty=format:%H -- rubocop/rubocop-migrations.yml
- git log -n 1 --pretty=format:%H -- rubocop/rubocop-usage-data.yml
- git log -n 1 --pretty=format:%H -- rubocop/rubocop-code_reuse.yml
- bundle exec scripts/debug-rubocop spec/factories/namespace/aggregation_schedules.rb
- scripts/static-analysis
static-analysis as-if-foss:

View File

@ -111,6 +111,9 @@
- ".gitlab/ci/build-images.gitlab-ci.yml"
- ".gitlab/ci/qa.gitlab-ci.yml"
.gitaly-patterns: &gitaly-patterns
- "GITALY_SERVER_VERSION"
.workhorse-patterns: &workhorse-patterns
- "GITLAB_WORKHORSE_VERSION"
- "workhorse/**/*"
@ -310,6 +313,11 @@
- <<: *if-security-schedule
- <<: *if-merge-request-title-update-caches
.shared:rules:update-gitaly-binaries-cache:
rules:
- <<: *if-merge-request-title-update-caches
- changes: *gitaly-patterns
######################
# Build images rules #
######################

View File

@ -0,0 +1,32 @@
import axios from '~/lib/utils/axios_utils';
import { buildApiUrl } from './api_utils';
const PROJECT_VSA_PATH_BASE = '/:project_path/-/analytics/value_stream_analytics/value_streams';
const PROJECT_VSA_STAGES_PATH = `${PROJECT_VSA_PATH_BASE}/:value_stream_id/stages`;
const buildProjectValueStreamPath = (projectPath, valueStreamId = null) => {
if (valueStreamId) {
return buildApiUrl(PROJECT_VSA_STAGES_PATH)
.replace(':project_path', projectPath)
.replace(':value_stream_id', valueStreamId);
}
return buildApiUrl(PROJECT_VSA_PATH_BASE).replace(':project_path', projectPath);
};
export const getProjectValueStreams = (projectPath) => {
const url = buildProjectValueStreamPath(projectPath);
return axios.get(url);
};
export const getProjectValueStreamStages = (projectPath, valueStreamId) => {
const url = buildProjectValueStreamPath(projectPath, valueStreamId);
return axios.get(url);
};
// NOTE: legacy VSA request use a different path
// the `requestPath` provides a full url for the request
export const getProjectValueStreamStageData = ({ requestPath, stageId, params }) =>
axios.get(`${requestPath}/events/${stageId}`, { params });
export const getProjectValueStreamMetrics = (requestPath, params) =>
axios.get(requestPath, { params });

View File

@ -58,6 +58,7 @@ export default {
'stages',
'summary',
'startDate',
'permissions',
]),
...mapGetters(['pathNavigationData']),
displayStageEvents() {
@ -68,7 +69,7 @@ export default {
return this.selectedStageReady && this.isEmptyStage;
},
displayNoAccess() {
return this.selectedStageReady && !this.selectedStage.isUserAllowed;
return this.selectedStageReady && !this.isUserAllowed(this.selectedStage.id);
},
selectedStageReady() {
return !this.isLoadingStage && this.selectedStage;
@ -91,25 +92,18 @@ export default {
]),
handleDateSelect(startDate) {
this.setDateRange({ startDate });
this.fetchCycleAnalyticsData();
},
isActiveStage(stage) {
return stage.slug === this.selectedStage.slug;
},
onSelectStage(stage) {
if (this.isLoadingStage || this.selectedStage?.slug === stage?.slug) return;
this.setSelectedStage(stage);
if (!stage.isUserAllowed) {
return;
}
this.fetchStageData();
},
dismissOverviewDialog() {
this.isOverviewDialogDismissed = true;
Cookies.set(OVERVIEW_DIALOG_COOKIE, '1', { expires: 365 });
},
isUserAllowed(id) {
const { permissions } = this;
return Boolean(permissions?.[id]);
},
},
dayRangeOptions: [7, 30, 90],
i18n: {

View File

@ -1,2 +1,8 @@
export const DEFAULT_DAYS_TO_DISPLAY = 30;
export const OVERVIEW_STAGE_ID = 'overview';
export const DEFAULT_VALUE_STREAM = {
id: 'default',
slug: 'default',
name: 'default',
};

View File

@ -8,10 +8,11 @@ Vue.use(Translate);
export default () => {
const store = createStore();
const el = document.querySelector('#js-cycle-analytics');
const { noAccessSvgPath, noDataSvgPath, requestPath } = el.dataset;
const { noAccessSvgPath, noDataSvgPath, requestPath, fullPath } = el.dataset;
store.dispatch('initializeVsa', {
requestPath,
fullPath,
});
// eslint-disable-next-line no-new
@ -24,6 +25,7 @@ export default () => {
props: {
noDataSvgPath,
noAccessSvgPath,
fullPath,
},
}),
});

View File

@ -1,27 +1,60 @@
import {
getProjectValueStreamStages,
getProjectValueStreams,
getProjectValueStreamStageData,
getProjectValueStreamMetrics,
} from '~/api/analytics_api';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
import { DEFAULT_DAYS_TO_DISPLAY, DEFAULT_VALUE_STREAM } from '../constants';
import * as types from './mutation_types';
export const fetchCycleAnalyticsData = ({
state: { requestPath, startDate },
dispatch,
commit,
}) => {
export const setSelectedValueStream = ({ commit, dispatch }, valueStream) => {
commit(types.SET_SELECTED_VALUE_STREAM, valueStream);
return dispatch('fetchValueStreamStages');
};
export const fetchValueStreamStages = ({ commit, state }) => {
const { fullPath, selectedValueStream } = state;
commit(types.REQUEST_VALUE_STREAM_STAGES);
return getProjectValueStreamStages(fullPath, selectedValueStream.id)
.then(({ data }) => commit(types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS, data))
.catch(({ response: { status } }) => {
commit(types.RECEIVE_VALUE_STREAM_STAGES_ERROR, status);
});
};
export const receiveValueStreamsSuccess = ({ commit, dispatch }, data = []) => {
commit(types.RECEIVE_VALUE_STREAMS_SUCCESS, data);
if (data.length) {
const [firstStream] = data;
return dispatch('setSelectedValueStream', firstStream);
}
return dispatch('setSelectedValueStream', DEFAULT_VALUE_STREAM);
};
export const fetchValueStreams = ({ commit, dispatch, state }) => {
const { fullPath } = state;
commit(types.REQUEST_VALUE_STREAMS);
return getProjectValueStreams(fullPath)
.then(({ data }) => dispatch('receiveValueStreamsSuccess', data))
.then(() => dispatch('setSelectedStage'))
.catch(({ response: { status } }) => {
commit(types.RECEIVE_VALUE_STREAMS_ERROR, status);
});
};
export const fetchCycleAnalyticsData = ({ state: { requestPath, startDate }, commit }) => {
commit(types.REQUEST_CYCLE_ANALYTICS_DATA);
return axios
.get(requestPath, {
params: { 'cycle_analytics[start_date]': startDate },
})
return getProjectValueStreamMetrics(requestPath, { 'cycle_analytics[start_date]': startDate })
.then(({ data }) => commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS, data))
.then(() => dispatch('setSelectedStage'))
.then(() => dispatch('fetchStageData'))
.catch(() => {
commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR);
createFlash({
message: __('There was an error while fetching value stream analytics data.'),
message: __('There was an error while fetching value stream summary data.'),
});
});
};
@ -29,10 +62,11 @@ export const fetchCycleAnalyticsData = ({
export const fetchStageData = ({ state: { requestPath, selectedStage, startDate }, commit }) => {
commit(types.REQUEST_STAGE_DATA);
return axios
.get(`${requestPath}/events/${selectedStage.name}.json`, {
params: { 'cycle_analytics[start_date]': startDate },
})
return getProjectValueStreamStageData({
requestPath,
stageId: selectedStage.id,
params: { 'cycle_analytics[start_date]': startDate },
})
.then(({ data }) => {
// when there's a query timeout, the request succeeds but the error is encoded in the response data
if (data?.error) {
@ -44,15 +78,26 @@ export const fetchStageData = ({ state: { requestPath, selectedStage, startDate
.catch(() => commit(types.RECEIVE_STAGE_DATA_ERROR));
};
export const setSelectedStage = ({ commit, state: { stages } }, selectedStage = null) => {
export const setSelectedStage = ({ dispatch, commit, state: { stages } }, selectedStage = null) => {
const stage = selectedStage || stages[0];
commit(types.SET_SELECTED_STAGE, stage);
return dispatch('fetchStageData');
};
export const setDateRange = ({ commit }, { startDate = DEFAULT_DAYS_TO_DISPLAY }) =>
const refetchData = (dispatch, commit) => {
commit(types.SET_LOADING, true);
return Promise.resolve()
.then(() => dispatch('fetchValueStreams'))
.then(() => dispatch('fetchCycleAnalyticsData'))
.finally(() => commit(types.SET_LOADING, false));
};
export const setDateRange = ({ dispatch, commit }, { startDate = DEFAULT_DAYS_TO_DISPLAY }) => {
commit(types.SET_DATE_RANGE, { startDate });
return refetchData(dispatch, commit);
};
export const initializeVsa = ({ commit, dispatch }, initialData = {}) => {
commit(types.INITIALIZE_VSA, initialData);
return dispatch('fetchCycleAnalyticsData');
return refetchData(dispatch, commit);
};

View File

@ -1,8 +1,18 @@
export const INITIALIZE_VSA = 'INITIALIZE_VSA';
export const SET_LOADING = 'SET_LOADING';
export const SET_SELECTED_VALUE_STREAM = 'SET_SELECTED_VALUE_STREAM';
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const SET_DATE_RANGE = 'SET_DATE_RANGE';
export const REQUEST_VALUE_STREAMS = 'REQUEST_VALUE_STREAMS';
export const RECEIVE_VALUE_STREAMS_SUCCESS = 'RECEIVE_VALUE_STREAMS_SUCCESS';
export const RECEIVE_VALUE_STREAMS_ERROR = 'RECEIVE_VALUE_STREAMS_ERROR';
export const REQUEST_VALUE_STREAM_STAGES = 'REQUEST_VALUE_STREAM_STAGES';
export const RECEIVE_VALUE_STREAM_STAGES_SUCCESS = 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS';
export const RECEIVE_VALUE_STREAM_STAGES_ERROR = 'RECEIVE_VALUE_STREAM_STAGES_ERROR';
export const REQUEST_CYCLE_ANALYTICS_DATA = 'REQUEST_CYCLE_ANALYTICS_DATA';
export const RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS = 'RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS';
export const RECEIVE_CYCLE_ANALYTICS_DATA_ERROR = 'RECEIVE_CYCLE_ANALYTICS_DATA_ERROR';

View File

@ -1,34 +1,61 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { decorateData, decorateEvents, formatMedianValues } from '../utils';
import * as types from './mutation_types';
export default {
[types.INITIALIZE_VSA](state, { requestPath }) {
[types.INITIALIZE_VSA](state, { requestPath, fullPath }) {
state.requestPath = requestPath;
state.fullPath = fullPath;
},
[types.SET_LOADING](state, loadingState) {
state.isLoading = loadingState;
},
[types.SET_SELECTED_VALUE_STREAM](state, selectedValueStream = {}) {
state.selectedValueStream = convertObjectPropsToCamelCase(selectedValueStream, { deep: true });
},
[types.SET_SELECTED_STAGE](state, stage) {
state.isLoadingStage = true;
state.selectedStage = stage;
state.isLoadingStage = false;
},
[types.SET_DATE_RANGE](state, { startDate }) {
state.startDate = startDate;
},
[types.REQUEST_VALUE_STREAMS](state) {
state.valueStreams = [];
},
[types.RECEIVE_VALUE_STREAMS_SUCCESS](state, valueStreams = []) {
state.valueStreams = valueStreams;
},
[types.RECEIVE_VALUE_STREAMS_ERROR](state) {
state.valueStreams = [];
},
[types.REQUEST_VALUE_STREAM_STAGES](state) {
state.stages = [];
},
[types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS](state, { stages = [] }) {
state.stages = stages.map((s) => ({
...convertObjectPropsToCamelCase(s, { deep: true }),
// NOTE: we set the component type here to match the current behaviour
// this can be removed when we migrate to the update stage table
// https://gitlab.com/gitlab-org/gitlab/-/issues/326704
component: `stage-${s.id}-component`,
}));
},
[types.RECEIVE_VALUE_STREAM_STAGES_ERROR](state) {
state.stages = [];
},
[types.REQUEST_CYCLE_ANALYTICS_DATA](state) {
state.isLoading = true;
state.stages = [];
state.hasError = false;
},
[types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state, data) {
state.isLoading = false;
const { stages, summary, medians } = decorateData(data);
state.stages = stages;
const { summary, medians } = decorateData(data);
state.permissions = data.permissions;
state.summary = summary;
state.medians = formatMedianValues(medians);
state.hasError = false;
},
[types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR](state) {
state.isLoading = false;
state.stages = [];
state.hasError = true;
},
[types.REQUEST_STAGE_DATA](state) {

View File

@ -2,11 +2,14 @@ import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
export default () => ({
requestPath: '',
fullPath: '',
startDate: DEFAULT_DAYS_TO_DISPLAY,
stages: [],
summary: [],
analytics: [],
stats: [],
valueStreams: [],
selectedValueStream: {},
selectedStage: {},
selectedStageEvents: [],
selectedStageError: '',
@ -15,4 +18,5 @@ export default () => ({
isLoading: false,
isLoadingStage: false,
isEmptyStage: false,
permissions: {},
});

View File

@ -2,31 +2,9 @@ import { unescape } from 'lodash';
import { sanitize } from '~/lib/dompurify';
import { roundToNearestHalf, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { parseSeconds } from '~/lib/utils/datetime_utility';
import { dasherize } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '../locale';
import { s__, sprintf } from '../locale';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
const EMPTY_STAGE_TEXTS = {
issue: __(
'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
),
plan: __(
'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
),
code: __(
'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
),
test: __(
'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
),
review: __(
'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
),
staging: __(
'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
),
};
/**
* These `decorate` methods will be removed when me migrate to the
* new table layout https://gitlab.com/gitlab-org/gitlab/-/issues/326704
@ -43,33 +21,12 @@ const mapToEvent = (event, stage) => {
export const decorateEvents = (events, stage) => events.map((event) => mapToEvent(event, stage));
/*
* NOTE: We currently use the `name` field since the project level stages are in memory
* once we migrate to a default value stream https://gitlab.com/gitlab-org/gitlab/-/issues/326705
* we can use the `id` to identify which median we are using
*/
const mapToStage = (permissions, { name, ...rest }) => {
const slug = dasherize(name.toLowerCase());
return {
...rest,
name,
id: name,
slug,
active: false,
isUserAllowed: permissions[slug],
emptyStageText: EMPTY_STAGE_TEXTS[slug],
component: `stage-${slug}-component`,
};
};
const mapToSummary = ({ value, ...rest }) => ({ ...rest, value: value || '-' });
const mapToMedians = ({ id, value }) => ({ id, value });
const mapToMedians = ({ name: id, value }) => ({ id, value });
export const decorateData = (data = {}) => {
const { permissions, stats, summary } = data;
const stages = stats?.map((item) => mapToStage(permissions, item)) || [];
const { stats: stages, summary } = data;
return {
stages,
summary: summary?.map((item) => mapToSummary(item)) || [],
medians: stages?.map((item) => mapToMedians(item)) || [],
};

View File

@ -31,7 +31,7 @@ function organizeQuery(obj, isFallbackKey = false) {
}
function format(searchTerm, isFallbackKey = false) {
const queryObject = queryToObject(searchTerm);
const queryObject = queryToObject(searchTerm, { legacySpacesDecode: true });
const organizeQueryObject = organizeQuery(queryObject, isFallbackKey);
const formattedQuery = objectToQuery(organizeQueryObject);

View File

@ -414,29 +414,35 @@ export function getWebSocketUrl(path) {
*
* @param {String} query from "document.location.search"
* @param {Object} options
* @param {Boolean} options.gatherArrays - gather array values into an Array
* @param {Boolean?} options.gatherArrays - gather array values into an Array
* @param {Boolean?} options.legacySpacesDecode - (deprecated) plus symbols (+) are not replaced with spaces, false by default
* @returns {Object}
*
* ex: "?one=1&two=2" into {one: 1, two: 2}
*/
export function queryToObject(query, options = {}) {
const { gatherArrays = false } = options;
export function queryToObject(query, { gatherArrays = false, legacySpacesDecode = false } = {}) {
const removeQuestionMarkFromQuery = String(query).startsWith('?') ? query.slice(1) : query;
return removeQuestionMarkFromQuery.split('&').reduce((accumulator, curr) => {
const [key, value] = curr.split('=');
if (value === undefined) {
return accumulator;
}
const decodedValue = decodeURIComponent(value);
const decodedValue = legacySpacesDecode ? decodeURIComponent(value) : decodeUrlParameter(value);
if (gatherArrays && key.endsWith('[]')) {
const decodedKey = decodeURIComponent(key.slice(0, -2));
const decodedKey = legacySpacesDecode
? decodeURIComponent(key.slice(0, -2))
: decodeUrlParameter(key.slice(0, -2));
if (!Array.isArray(accumulator[decodedKey])) {
accumulator[decodedKey] = [];
}
accumulator[decodedKey].push(decodedValue);
} else {
accumulator[decodeURIComponent(key)] = decodedValue;
const decodedKey = legacySpacesDecode ? decodeURIComponent(key) : decodeUrlParameter(key);
accumulator[decodedKey] = decodedValue;
}
return accumulator;

View File

@ -175,7 +175,7 @@ export const graphDataValidatorForAnomalyValues = (graphData) => {
* Returns `null` if no parameters form a time range.
*/
export const timeRangeFromUrl = (search = window.location.search) => {
const params = queryToObject(search);
const params = queryToObject(search, { legacySpacesDecode: true });
return timeRangeFromParams(params);
};
@ -228,7 +228,7 @@ export const convertVariablesForURL = (variables) =>
* @returns {Object} The custom variables defined by the user in the URL
*/
export const templatingVariablesFromUrl = (search = window.location.search) => {
const params = queryToObject(search);
const params = queryToObject(search, { legacySpacesDecode: true });
// pick the params with variable prefix
const paramsWithVars = pickBy(params, (val, key) => key.startsWith(VARIABLE_PREFIX));
// remove the prefix before storing in the Vuex store
@ -289,7 +289,7 @@ export const timeRangeToUrl = (timeRange, url = window.location.href) => {
* @throws Will throw an error if Panel cannot be located.
*/
export const expandedPanelPayloadFromUrl = (dashboard, search = window.location.search) => {
const params = queryToObject(search);
const params = queryToObject(search, { legacySpacesDecode: true });
// Search for the panel if any of the search params is identified
if (params.group || params.title || params.y_label) {

View File

@ -1,7 +1,8 @@
import { queryToObject } from '~/lib/utils/url_utility';
import { FILTERED_SEARCH_TERM } from './constants';
export const getQueryParams = (query) => queryToObject(query, { gatherArrays: true });
export const getQueryParams = (query) =>
queryToObject(query, { gatherArrays: true, legacySpacesDecode: true });
export const keyValueToFilterToken = (type, data) => ({ type, value: { data } });

View File

@ -8,10 +8,7 @@ import createStore from './store';
import { initTopbar } from './topbar';
export const initSearchApp = () => {
// Similar to url_utility.decodeUrlParameter
// Our query treats + as %20. This replaces the query + symbols with %20.
const sanitizedSearch = window.location.search.replace(/\+/g, '%20');
const query = queryToObject(sanitizedSearch);
const query = queryToObject(window.location.search);
const store = createStore({ query });

View File

@ -142,11 +142,11 @@ function extractNameAndOperator(filterName) {
* '?myFilterName=foo'
* gets translated into:
* { myFilterName: { value: 'foo', operator: '=' } }
* @param {String} query URL quert string, e.g. from `window.location.search`
* @param {String} query URL query string, e.g. from `window.location.search`
* @return {Object} filter object with filter names and their values
*/
export function urlQueryToFilter(query = '') {
const filters = queryToObject(query, { gatherArrays: true });
const filters = queryToObject(query, { gatherArrays: true, legacySpacesDecode: true });
return Object.keys(filters).reduce((memo, key) => {
const value = filters[key];
if (!value) {

View File

@ -337,6 +337,9 @@ h1 {
.d-none {
display: none !important;
}
.d-inline-block {
display: inline-block !important;
}
.d-block {
display: block !important;
}
@ -344,6 +347,9 @@ h1 {
.d-sm-none {
display: none !important;
}
.d-sm-inline-block {
display: inline-block !important;
}
}
@media (min-width: 768px) {
.d-md-block {
@ -351,6 +357,9 @@ h1 {
}
}
@media (min-width: 992px) {
.d-lg-none {
display: none !important;
}
.d-lg-block {
display: block !important;
}
@ -2287,24 +2296,11 @@ body.gl-dark {
.gl-display-none {
display: none;
}
@media (min-width: 62rem) {
.gl-lg-display-none {
display: none;
}
}
@media (min-width: 36rem) {
.gl-sm-display-block {
display: block;
}
}
.gl-display-inline-block {
display: inline-block;
}
@media (min-width: 36rem) {
.gl-sm-display-inline-block {
display: inline-block;
}
}
.gl-absolute {
position: absolute;
}

View File

@ -322,6 +322,9 @@ h1 {
.d-none {
display: none !important;
}
.d-inline-block {
display: inline-block !important;
}
.d-block {
display: block !important;
}
@ -329,6 +332,9 @@ h1 {
.d-sm-none {
display: none !important;
}
.d-sm-inline-block {
display: inline-block !important;
}
}
@media (min-width: 768px) {
.d-md-block {
@ -336,6 +342,9 @@ h1 {
}
}
@media (min-width: 992px) {
.d-lg-none {
display: none !important;
}
.d-lg-block {
display: block !important;
}
@ -2070,24 +2079,11 @@ body.sidebar-refactoring
.gl-display-none {
display: none;
}
@media (min-width: 62rem) {
.gl-lg-display-none {
display: none;
}
}
@media (min-width: 36rem) {
.gl-sm-display-block {
display: block;
}
}
.gl-display-inline-block {
display: inline-block;
}
@media (min-width: 36rem) {
.gl-sm-display-inline-block {
display: inline-block;
}
}
.gl-absolute {
position: absolute;
}

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
class Admin::AppearancesController < Admin::ApplicationController
class Admin::ApplicationSettings::AppearancesController < Admin::ApplicationController
before_action :set_appearance, except: :create
feature_category :navigation
@ -16,7 +16,7 @@ class Admin::AppearancesController < Admin::ApplicationController
@appearance = Appearance.new(appearance_params)
if @appearance.save
redirect_to admin_appearances_path, notice: _('Appearance was successfully created.')
redirect_to admin_application_settings_appearances_path, notice: _('Appearance was successfully created.')
else
render action: 'show'
end
@ -24,7 +24,7 @@ class Admin::AppearancesController < Admin::ApplicationController
def update
if @appearance.update(appearance_params)
redirect_to admin_appearances_path, notice: _('Appearance was successfully updated.')
redirect_to admin_application_settings_appearances_path, notice: _('Appearance was successfully updated.')
else
render action: 'show'
end
@ -35,21 +35,21 @@ class Admin::AppearancesController < Admin::ApplicationController
@appearance.save
redirect_to admin_appearances_path, notice: _('Logo was successfully removed.')
redirect_to admin_application_settings_appearances_path, notice: _('Logo was successfully removed.')
end
def header_logos
@appearance.remove_header_logo!
@appearance.save
redirect_to admin_appearances_path, notice: _('Header logo was successfully removed.')
redirect_to admin_application_settings_appearances_path, notice: _('Header logo was successfully removed.')
end
def favicon
@appearance.remove_favicon!
@appearance.save
redirect_to admin_appearances_path, notice: _('Favicon was successfully removed.')
redirect_to admin_application_settings_appearances_path, notice: _('Favicon was successfully removed.')
end
private

View File

@ -10,7 +10,7 @@ class Import::BulkImportsController < ApplicationController
POLLING_INTERVAL = 3_000
rescue_from BulkImports::Clients::Http::ConnectionError, with: :bulk_import_connection_error
rescue_from BulkImports::Clients::HTTP::ConnectionError, with: :bulk_import_connection_error
def configure
session[access_token_key] = configure_params[access_token_key]&.strip
@ -86,7 +86,7 @@ class Import::BulkImportsController < ApplicationController
end
def client
@client ||= BulkImports::Clients::Http.new(
@client ||= BulkImports::Clients::HTTP.new(
uri: session[url_key],
token: session[access_token_key],
per_page: params[:per_page],

View File

@ -9,7 +9,7 @@ module BulkImports
@relation = relation
@entity = @pipeline_tracker.entity
@configuration = @entity.bulk_import.configuration
@client = Clients::Http.new(uri: @configuration.url, token: @configuration.access_token)
@client = Clients::HTTP.new(uri: @configuration.url, token: @configuration.access_token)
end
def started?

View File

@ -39,6 +39,7 @@ module Ci
has_one :deployment, as: :deployable, class_name: 'Deployment'
has_one :pending_state, class_name: 'Ci::BuildPendingState', inverse_of: :build
has_one :queuing_entry, class_name: 'Ci::PendingBuild', foreign_key: :build_id
has_one :runtime_metadata, class_name: 'Ci::RunningBuild', foreign_key: :build_id
has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id, inverse_of: :build
has_many :report_results, class_name: 'Ci::BuildReportResult', inverse_of: :build
@ -310,7 +311,22 @@ module Ci
after_transition pending: any do |build, transition|
Ci::UpdateBuildQueueService.new.pop(build, transition)
end
after_transition any => [:running] do |build, transition|
Ci::UpdateBuildQueueService.new.track(build, transition)
end
after_transition running: any do |build, transition|
Ci::UpdateBuildQueueService.new.untrack(build, transition)
Ci::BuildRunnerSession.where(build: build).delete_all
end
# rubocop:enable CodeReuse/ServiceClass
#
after_transition pending: :running do |build|
build.ensure_metadata.update_timeout_state
end
after_transition pending: :running do |build|
build.deployment&.run
@ -364,14 +380,6 @@ module Ci
end
end
after_transition pending: :running do |build|
build.ensure_metadata.update_timeout_state
end
after_transition running: any do |build|
Ci::BuildRunnerSession.where(build: build).delete_all
end
after_transition any => [:skipped, :canceled] do |build, transition|
if transition.to_name == :skipped
build.deployment&.skip
@ -1068,16 +1076,26 @@ module Ci
options.dig(:allow_failure_criteria, :exit_codes).present?
end
def all_queuing_entries
# We can have only one queuing entry, because there is a unique index on
# `build_id`, but we need a relation to remove this single queuing entry
# more efficiently in a single statement without actually load data.
def create_queuing_entry!
::Ci::PendingBuild.upsert_from_build!(self)
end
##
# We can have only one queuing entry or running build tracking entry,
# because there is a unique index on `build_id` in each table, but we need
# a relation to remove these entries more efficiently in a single statement
# without actually loading data.
#
def all_queuing_entries
::Ci::PendingBuild.where(build_id: self.id)
end
def create_queuing_entry!
::Ci::PendingBuild.upsert_from_build!(self)
def all_runtime_metadata
::Ci::RunningBuild.where(build_id: self.id)
end
def shared_runner_build?
runner&.instance_type?
end
protected

View File

@ -8,7 +8,7 @@ module Ci
belongs_to :build, class_name: 'Ci::Build'
def self.upsert_from_build!(build)
entry = self.new(build: build, project: build.project)
entry = self.new(build: build, project: build.project, protected: build.protected?)
entry.validate!

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Ci
class RunningBuild < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :project
belongs_to :build, class_name: 'Ci::Build'
belongs_to :runner, class_name: 'Ci::Runner'
enum runner_type: ::Ci::Runner.runner_types
def self.upsert_shared_runner_build!(build)
unless build.shared_runner_build?
raise ArgumentError, 'build has not been picked by a shared runner'
end
entry = self.new(build: build,
project: build.project,
runner: build.runner,
runner_type: build.runner.runner_type)
entry.validate!
self.upsert(entry.attributes.compact, returning: %w[build_id], unique_by: :build_id)
end
end
end

View File

@ -38,7 +38,7 @@ class Commit
cache_markdown_field :description, pipeline: :commit_description, limit: 1.megabyte
# Share the cache used by the markdown fields
attr_mentionable :full_title, pipeline: :single_line, limit: 1.kilobyte
attr_mentionable :title, pipeline: :single_line
attr_mentionable :description, pipeline: :commit_description, limit: 1.megabyte
class << self

View File

@ -24,7 +24,7 @@ module Enums
project_deleted: 15,
ci_quota_exceeded: 16,
pipeline_loop_detected: 17,
no_matching_runner: 18,
no_matching_runner: 18, # not used anymore, but cannot be deleted because of old data
insufficient_bridge_permissions: 1_001,
downstream_bridge_project_not_found: 1_002,
invalid_bridge_trigger: 1_003,

View File

@ -9,8 +9,6 @@ class GroupDeployToken < ApplicationRecord
validates :deploy_token_id, uniqueness: { scope: [:group_id] }
def has_access_to?(requested_project)
return false unless Feature.enabled?(:allow_group_deploy_token, default_enabled: true)
requested_project_group = requested_project&.group
return false unless requested_project_group
return true if requested_project_group.id == group_id

View File

@ -6,9 +6,9 @@ module Boards
include ActiveRecord::ConnectionAdapters::Quoting
def execute
return items.order_closed_date_desc if list&.closed?
items = init_collection
ordered_items
order(items)
end
# rubocop: disable CodeReuse/ActiveRecord
@ -17,7 +17,7 @@ module Boards
keys = metadata_fields.keys
# TODO: eliminate need for SQL literal fragment
columns = Arel.sql(metadata_fields.values_at(*keys).join(', '))
results = item_model.where(id: items.select(issuables[:id])).pluck(columns)
results = item_model.where(id: init_collection.select(issuables[:id])).pluck(columns)
Hash[keys.zip(results.flatten)]
end
@ -29,7 +29,7 @@ module Boards
{ size: 'COUNT(*)' }
end
def ordered_items
def order(items)
raise NotImplementedError
end
@ -47,8 +47,8 @@ module Boards
# We memoize the query here since the finder methods we use are quite complex. This does not memoize the result of the query.
# rubocop: disable CodeReuse/ActiveRecord
def items
strong_memoize(:items) do
def init_collection
strong_memoize(:init_collection) do
filter(finder.execute).reorder(nil)
end
end

View File

@ -11,7 +11,9 @@ module Boards
private
def ordered_items
def order(items)
return items.order_closed_date_desc if list&.closed?
items.order_by_position_and_priority(with_cte: params[:search].present?)
end

View File

@ -52,7 +52,7 @@ module BulkImports
end
def http_client
@http_client ||= BulkImports::Clients::Http.new(
@http_client ||= BulkImports::Clients::HTTP.new(
uri: configuration.url,
token: configuration.access_token
)

View File

@ -1,91 +0,0 @@
# frozen_string_literal: true
module Ci
module PipelineCreation
class DropNotRunnableBuildsService
include Gitlab::Utils::StrongMemoize
def initialize(pipeline)
@pipeline = pipeline
end
##
# We want to run this service exactly once,
# before the first pipeline processing call
#
def execute
return unless ::Feature.enabled?(:ci_drop_new_builds_when_ci_quota_exceeded, project, default_enabled: :yaml)
return unless pipeline.created?
load_runners
validate_build_matchers
end
private
attr_reader :pipeline
attr_reader :instance_runners, :private_runners
delegate :project, to: :pipeline
def load_runners
@instance_runners, @private_runners = project
.all_runners
.active
.online
.runner_matchers
.partition(&:instance_type?)
end
def validate_build_matchers
pipeline.build_matchers.each do |build_matcher|
failure_reason = validate_build_matcher(build_matcher)
next unless failure_reason
drop_all_builds(build_matcher.build_ids, failure_reason)
end
end
def validate_build_matcher(build_matcher)
return if matching_private_runners?(build_matcher)
return if matching_instance_runners?(build_matcher)
matching_failure_reason(build_matcher)
end
##
# We skip pipeline processing until we drop all required builds. Otherwise
# as we drop the first build, the remaining builds to be dropped could
# transition to other states by `PipelineProcessWorker` running async.
#
def drop_all_builds(build_ids, failure_reason)
pipeline.builds.id_in(build_ids).each do |build|
build.drop(failure_reason, skip_pipeline_processing: true)
end
end
def matching_private_runners?(build_matcher)
private_runners
.find { |matcher| matcher.matches?(build_matcher) }
.present?
end
def matching_instance_runners?(build_matcher)
instance_runners
.find { |matcher| matching_criteria(matcher, build_matcher) }
.present?
end
# Overridden in EE
def matching_criteria(runner_matcher, build_matcher)
runner_matcher.matches?(build_matcher)
end
# Overridden in EE
def matching_failure_reason(build_matcher)
:no_matching_runner
end
end
end
end
Ci::PipelineCreation::DropNotRunnableBuildsService.prepend_mod_with('Ci::PipelineCreation::DropNotRunnableBuildsService')

View File

@ -10,9 +10,10 @@ module Ci
end
def execute
DropNotRunnableBuildsService.new(pipeline).execute
Ci::ProcessPipelineService.new(pipeline).execute
end
end
end
end
::Ci::PipelineCreation::StartPipelineService.prepend_mod_with('Ci::PipelineCreation::StartPipelineService')

View File

@ -48,6 +48,47 @@ module Ci
end
end
##
# Add shared runner build tracking entry (used for queuing).
#
def track(build, transition)
return unless Feature.enabled?(:ci_track_shared_runner_builds, build.project, default_enabled: :yaml)
return unless build.shared_runner_build?
raise InvalidQueueTransition unless transition.to == 'running'
transition.within_transaction do
result = ::Ci::RunningBuild.upsert_shared_runner_build!(build)
unless result.empty?
metrics.increment_queue_operation(:shared_runner_build_new)
result.rows.dig(0, 0)
end
end
end
##
# Remove a runtime build tracking entry for a shared runner build (used for
# queuing).
#
def untrack(build, transition)
return unless Feature.enabled?(:ci_untrack_shared_runner_builds, build.project, default_enabled: :yaml)
return unless build.shared_runner_build?
raise InvalidQueueTransition unless transition.from == 'running'
transition.within_transaction do
removed = build.all_runtime_metadata.delete_all
if removed > 0
metrics.increment_queue_operation(:shared_runner_build_done)
build.id
end
end
end
##
# Unblock runner associated with given project / build
#

View File

@ -11,6 +11,7 @@ module Projects
@initialize_with_readme = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_readme))
@import_data = @params.delete(:import_data)
@relations_block = @params.delete(:relations_block)
@default_branch = @params.delete(:default_branch)
build_topics
end
@ -130,20 +131,16 @@ module Projects
access_level: group_access_level)
end
if Feature.enabled?(:specialized_project_authorization_workers, default_enabled: :yaml)
AuthorizedProjectUpdate::ProjectCreateWorker.perform_async(@project.id)
# AuthorizedProjectsWorker uses an exclusive lease per user but
# specialized workers might have synchronization issues. Until we
# compare the inconsistency rates of both approaches, we still run
# AuthorizedProjectsWorker but with some delay and lower urgency as a
# safety net.
@project.group.refresh_members_authorized_projects(
blocking: false,
priority: UserProjectAccessChangedService::LOW_PRIORITY
)
else
@project.group.refresh_members_authorized_projects(blocking: false)
end
AuthorizedProjectUpdate::ProjectCreateWorker.perform_async(@project.id)
# AuthorizedProjectsWorker uses an exclusive lease per user but
# specialized workers might have synchronization issues. Until we
# compare the inconsistency rates of both approaches, we still run
# AuthorizedProjectsWorker but with some delay and lower urgency as a
# safety net.
@project.group.refresh_members_authorized_projects(
blocking: false,
priority: UserProjectAccessChangedService::LOW_PRIORITY
)
else
@project.add_maintainer(@project.namespace.owner, current_user: current_user)
end
@ -151,7 +148,7 @@ module Projects
def create_readme
commit_attrs = {
branch_name: @project.default_branch_or_main,
branch_name: @default_branch.presence || @project.default_branch_or_main,
commit_message: 'Initial commit',
file_path: 'README.md',
file_content: "# #{@project.name}\n\n#{@project.description}"

View File

@ -23,22 +23,18 @@ module Projects
private
def setup_authorizations(group, group_access = nil)
if Feature.enabled?(:specialized_project_authorization_project_share_worker, default_enabled: :yaml)
AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker.perform_async(
project.id, group.id, group_access)
AuthorizedProjectUpdate::ProjectGroupLinkCreateWorker.perform_async(
project.id, group.id, group_access)
# AuthorizedProjectsWorker uses an exclusive lease per user but
# specialized workers might have synchronization issues. Until we
# compare the inconsistency rates of both approaches, we still run
# AuthorizedProjectsWorker but with some delay and lower urgency as a
# safety net.
group.refresh_members_authorized_projects(
blocking: false,
priority: UserProjectAccessChangedService::LOW_PRIORITY
)
else
group.refresh_members_authorized_projects(blocking: false)
end
# AuthorizedProjectsWorker uses an exclusive lease per user but
# specialized workers might have synchronization issues. Until we
# compare the inconsistency rates of both approaches, we still run
# AuthorizedProjectsWorker but with some delay and lower urgency as a
# safety net.
group.refresh_members_authorized_projects(
blocking: false,
priority: UserProjectAccessChangedService::LOW_PRIORITY
)
end
end
end

View File

@ -1,6 +1,6 @@
- parsed_with_gfm = (_("Content parsed with %{link}.") % { link: link_to('GitLab Flavored Markdown', help_page_path('user/markdown'), target: '_blank') }).html_safe
= form_for @appearance, url: admin_appearances_path, html: { class: 'gl-mt-3' } do |f|
= form_for @appearance, url: admin_application_settings_appearances_path, html: { class: 'gl-mt-3' } do |f|
= form_errors(@appearance)
@ -16,7 +16,7 @@
= image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to _('Remove header logo'), header_logos_admin_appearances_path, data: { confirm: _("Header logo will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
= link_to _('Remove header logo'), header_logos_admin_application_settings_appearances_path, data: { confirm: _("Header logo will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
%hr
= f.hidden_field :header_logo_cache
= f.file_field :header_logo, class: "", accept: 'image/*'
@ -35,7 +35,7 @@
= image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview'
- if @appearance.persisted?
%br
= link_to _('Remove favicon'), favicon_admin_appearances_path, data: { confirm: _("Favicon will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
= link_to _('Remove favicon'), favicon_admin_application_settings_appearances_path, data: { confirm: _("Favicon will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm"
%hr
= f.hidden_field :favicon_cache
= f.file_field :favicon, class: '', accept: 'image/*'
@ -44,7 +44,7 @@
%br
= _("Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.")
= render partial: 'admin/appearances/system_header_footer_form', locals: { form: f }
= render partial: 'admin/application_settings/appearances/system_header_footer_form', locals: { form: f }
%hr
.row
@ -67,7 +67,7 @@
= image_tag @appearance.logo_path, class: 'appearance-logo-preview'
- if @appearance.persisted?
%br
= link_to _('Remove logo'), logo_admin_appearances_path, data: { confirm: _("Logo will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm remove-logo"
= link_to _('Remove logo'), logo_admin_application_settings_appearances_path, data: { confirm: _("Logo will be removed. Are you sure?") }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm remove-logo"
%hr
= f.hidden_field :logo_cache
= f.file_field :logo, class: "", accept: 'image/*'
@ -106,7 +106,7 @@
.mt-4
- if @appearance.persisted?
Preview last save:
= link_to _('Sign-in page'), preview_sign_in_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
= link_to _('Sign-in page'), preview_sign_in_admin_application_settings_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
= link_to _('New project page'), new_project_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer'
- if @appearance.updated_at

View File

@ -37,7 +37,7 @@
- search_menu_item = top_nav_search_menu_item_attrs
%li.nav-item.d-none.d-lg-block.m-auto
= render 'layouts/search' unless current_controller?(:search)
%li.nav-item{ class: use_top_nav_redesign ? "gl-display-none gl-sm-display-inline-block gl-lg-display-none" : "gl-display-inline-block gl-lg-display-none" }
%li.nav-item{ class: use_top_nav_redesign ? 'd-none d-sm-inline-block d-lg-none' : 'd-inline-block d-lg-none' }
= link_to search_menu_item.fetch(:href), title: search_menu_item.fetch(:title), aria: { label: search_menu_item.fetch(:title) }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= sprite_icon(search_menu_item.fetch(:icon))
- if header_link?(:issues)

View File

@ -234,19 +234,7 @@
%strong.fly-out-top-item-name
= _('Labels')
= nav_link(controller: :appearances) do
= link_to admin_appearances_path do
.nav-icon-container
= sprite_icon('appearance')
%span.nav-item-name
= _('Appearance')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :appearances, html_options: { class: "fly-out-top-item" } ) do
= link_to admin_appearances_path do
%strong.fly-out-top-item-name
= _('Appearance')
= nav_link(controller: [:application_settings, :integrations]) do
= nav_link(controller: [:application_settings, :integrations, :appearances]) do
= link_to general_admin_application_settings_path, class: 'has-sub-items' do
.nav-icon-container
= sprite_icon('settings')
@ -255,7 +243,7 @@
%ul.sidebar-sub-level-items{ data: { qa_selector: 'admin_sidebar_settings_submenu_content' } }
-# This active_nav_link check is also used in `app/views/layouts/admin.html.haml`
= nav_link(controller: [:application_settings, :integrations], html_options: { class: "fly-out-top-item" } ) do
= nav_link(controller: [:application_settings, :integrations, :appearances], html_options: { class: "fly-out-top-item" } ) do
= link_to general_admin_application_settings_path do
%strong.fly-out-top-item-name
= _('Settings')
@ -302,6 +290,10 @@
= link_to network_admin_application_settings_path, title: _('Network'), data: { qa_selector: 'admin_settings_network_item' } do
%span
= _('Network')
= nav_link(controller: :appearances ) do
= link_to admin_application_settings_appearances_path do
%span
= _('Appearance')
= nav_link(path: 'application_settings#preferences') do
= link_to preferences_admin_application_settings_path, title: _('Preferences'), data: { qa_selector: 'admin_settings_preferences_link' } do
%span

View File

@ -2,7 +2,7 @@
- avatar_classes = ['avatar-container', 'rect-avatar', 'group-avatar']
- avatar_classes << avatar_size_class
= link_to group_path(@group), title: @group.name do
= link_to group_path(@group), title: @group.name, data: { qa_selector: 'group_scope_link' } do
%span{ class: avatar_classes }
= group_icon(@group, class: ['avatar', 'avatar-tile', avatar_size_class])
%span.sidebar-context-title

View File

@ -19,14 +19,14 @@
- paths = group_overview_nav_link_paths
= nav_link(path: paths, unless: -> { current_path?('groups/contribution_analytics#show') }, html_options: { class: 'home' }) do
- information_link = sidebar_refactor_enabled? ? activity_group_path(@group) : group_path(@group)
= link_to information_link, class: 'has-sub-items' do
= link_to information_link, class: 'has-sub-items', data: { qa_selector: 'group_information_link' } do
.nav-icon-container
- sprite = sidebar_refactor_enabled? ? 'group' : 'home'
= sprite_icon(sprite)
%span.nav-item-name
= group_information_title(@group)
%ul.sidebar-sub-level-items
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_information_submenu'} }
= nav_link(path: paths, html_options: { class: "fly-out-top-item" } ) do
= link_to information_link do
%strong.fly-out-top-item-name

View File

@ -5,11 +5,13 @@
- if @conflict
.gl-alert.gl-alert-danger.gl-mb-5.gl-mt-5
= sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
Someone edited the file the same time you did. Please check out
= link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer', class: 'gl-link'
and make sure your changes will not unintentionally remove theirs.
.gl-alert-container
= sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-content
.gl-alert-body
Someone edited the file the same time you did. Please check out
= link_to _('the file'), project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer', class: 'gl-link'
and make sure your changes will not unintentionally remove theirs.
%h3.page-title.blob-edit-page-title
Edit file

View File

@ -1,6 +1,6 @@
- page_title _("Value Stream Analytics")
- add_page_specific_style 'page_bundles/cycle_analytics'
- svgs = { empty_state_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), no_data_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), no_access_svg_path: image_path("illustrations/analytics/no-access.svg") }
- initial_data = { request_path: project_cycle_analytics_path(@project) }.merge!(svgs)
- initial_data = { request_path: project_cycle_analytics_path(@project), full_path: @project.full_path }.merge!(svgs)
#js-cycle-analytics{ data: initial_data }

View File

@ -1,7 +1,7 @@
- avatar_size = sidebar_refactor_disabled? ? 40 : 32
- avatar_size_class = sidebar_refactor_disabled? ? 's40' : 's32'
= link_to scope_menu.link, **scope_menu.container_html_options do
= link_to scope_menu.link, **scope_menu.container_html_options, data: { qa_selector: 'project_scope_link' } do
%span{ class: ['avatar-container', 'rect-avatar', 'project-avatar', avatar_size_class] }
= source_icon(scope_menu.container, alt: scope_menu.title, class: ['avatar', 'avatar-tile', avatar_size_class], width: avatar_size, height: avatar_size)
%span.sidebar-context-title

View File

@ -24,7 +24,7 @@ module BulkImports
end
def http_client(configuration)
@client ||= Clients::Http.new(
@client ||= Clients::HTTP.new(
uri: configuration.url,
token: configuration.access_token
)

View File

@ -1,8 +0,0 @@
---
name: allow_group_deploy_token
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23460
rollout_issue_url:
milestone: '12.8'
type: development
group: group::release
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: ci_track_shared_runner_builds
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62912
rollout_issue_url:
milestone: '14.0'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: ci_untrack_shared_runner_builds
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62912
rollout_issue_url:
milestone: '14.0'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326111
milestone: '13.11'
type: development
group: group::editor
default_enabled: false
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: specialized_project_authorization_project_share_worker
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32864
rollout_issue_url:
milestone: '13.2'
type: development
group: group::access
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: specialized_project_authorization_workers
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31377
rollout_issue_url:
milestone: '13.0'
type: development
group: group::access
default_enabled: true

View File

@ -124,15 +124,6 @@ namespace :admin do
end
end
resource :appearances, only: [:show, :create, :update], path: 'appearance' do
member do
get :preview_sign_in
delete :logo
delete :header_logos
delete :favicon
end
end
resource :application_settings, only: :update do
resources :services, only: [:index, :edit, :update]
resources :integrations, only: [:edit, :update] do
@ -153,6 +144,15 @@ namespace :admin do
get :status_create_self_monitoring_project
delete :delete_self_monitoring_project
get :status_delete_self_monitoring_project
resource :appearances, only: [:show, :create, :update], path: 'appearance', module: 'application_settings' do
member do
get :preview_sign_in
delete :logo
delete :header_logos
delete :favicon
end
end
end
resources :plan_limits, only: :create

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddRunningBuildsTable < ActiveRecord::Migration[6.0]
def up
create_table :ci_running_builds do |t|
t.references :build, index: { unique: true }, null: false, foreign_key: { to_table: :ci_builds, on_delete: :cascade }
t.references :project, index: true, null: false, foreign_key: { on_delete: :cascade }
t.references :runner, index: true, null: false, foreign_key: { to_table: :ci_runners, on_delete: :cascade }
t.datetime_with_timezone :created_at, null: false, default: -> { 'NOW()' }
t.integer :runner_type, limit: 2, null: false
end
end
def down
drop_table :ci_running_builds
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class RemoveTemporaryIndexOnSecurityFindingsScanId < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'tmp_index_on_security_findings_scan_id'
disable_ddl_transaction!
def up
remove_concurrent_index_by_name :security_findings, INDEX_NAME
end
def down
add_concurrent_index :security_findings, :scan_id, where: 'uuid is null', name: INDEX_NAME
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddProtectedAttributeToPendingBuilds < ActiveRecord::Migration[6.1]
def change
add_column :ci_pending_builds, :protected, :boolean, null: false, default: false
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
class MigrateProtectedAttributeToPendingBuilds < ActiveRecord::Migration[6.1]
include ::Gitlab::Database::DynamicModelHelpers
disable_ddl_transaction!
def up
return unless Gitlab.dev_or_test_env? || Gitlab.com?
each_batch_range('ci_pending_builds', of: 1000) do |min, max|
execute <<~SQL
UPDATE ci_pending_builds
SET protected = true
FROM ci_builds
WHERE ci_pending_builds.build_id = ci_builds.id
AND ci_builds.protected = true
AND ci_pending_builds.id BETWEEN #{min} AND #{max}
SQL
end
end
def down
# no op
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddIndexToProtectedPendingBuilds < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
INDEX_NAME = 'index_ci_pending_builds_id_on_protected_partial'
disable_ddl_transaction!
def up
add_concurrent_index :ci_pending_builds, :id, where: 'protected = true', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :ci_pending_builds, INDEX_NAME
end
end

View File

@ -0,0 +1 @@
d4a0098c30cd1acea008fa5f1cfb4c23d5b5b894eab2b72f5004acc5233f2576

View File

@ -0,0 +1 @@
88f16dc06371d320a1245de68aba5ed4ad7cd8f15c4e5898619a751840981072

View File

@ -0,0 +1 @@
dab13c78f6f758c63be923277c0f31e4cce4e30f77a8dc2983a9bb1500a454f9

View File

@ -0,0 +1 @@
ce21070d44a34081c6babd14e6a1b607bad5ed9047b18f4ef0beb64b5a2ce120

View File

@ -0,0 +1 @@
3ad279a7c57e433a8ee349dabd2536c1de9055936b05c26b5469606067eb90d4

View File

@ -10799,7 +10799,8 @@ CREATE TABLE ci_pending_builds (
id bigint NOT NULL,
build_id bigint NOT NULL,
project_id bigint NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL
created_at timestamp with time zone DEFAULT now() NOT NULL,
protected boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE ci_pending_builds_id_seq
@ -11150,6 +11151,24 @@ CREATE SEQUENCE ci_runners_id_seq
ALTER SEQUENCE ci_runners_id_seq OWNED BY ci_runners.id;
CREATE TABLE ci_running_builds (
id bigint NOT NULL,
build_id bigint NOT NULL,
project_id bigint NOT NULL,
runner_id bigint NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
runner_type smallint NOT NULL
);
CREATE SEQUENCE ci_running_builds_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ci_running_builds_id_seq OWNED BY ci_running_builds.id;
CREATE TABLE ci_sources_pipelines (
id integer NOT NULL,
project_id integer,
@ -19686,6 +19705,8 @@ ALTER TABLE ONLY ci_runner_projects ALTER COLUMN id SET DEFAULT nextval('ci_runn
ALTER TABLE ONLY ci_runners ALTER COLUMN id SET DEFAULT nextval('ci_runners_id_seq'::regclass);
ALTER TABLE ONLY ci_running_builds ALTER COLUMN id SET DEFAULT nextval('ci_running_builds_id_seq'::regclass);
ALTER TABLE ONLY ci_sources_pipelines ALTER COLUMN id SET DEFAULT nextval('ci_sources_pipelines_id_seq'::regclass);
ALTER TABLE ONLY ci_sources_projects ALTER COLUMN id SET DEFAULT nextval('ci_sources_projects_id_seq'::regclass);
@ -20891,6 +20912,9 @@ ALTER TABLE ONLY ci_runner_projects
ALTER TABLE ONLY ci_runners
ADD CONSTRAINT ci_runners_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_running_builds
ADD CONSTRAINT ci_running_builds_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_sources_pipelines
ADD CONSTRAINT ci_sources_pipelines_pkey PRIMARY KEY (id);
@ -22776,6 +22800,8 @@ CREATE INDEX index_ci_minutes_additional_packs_on_namespace_id_purchase_xid ON c
CREATE UNIQUE INDEX index_ci_namespace_monthly_usages_on_namespace_id_and_date ON ci_namespace_monthly_usages USING btree (namespace_id, date);
CREATE INDEX index_ci_pending_builds_id_on_protected_partial ON ci_pending_builds USING btree (id) WHERE (protected = true);
CREATE UNIQUE INDEX index_ci_pending_builds_on_build_id ON ci_pending_builds USING btree (build_id);
CREATE INDEX index_ci_pending_builds_on_project_id ON ci_pending_builds USING btree (project_id);
@ -22886,6 +22912,12 @@ CREATE INDEX index_ci_runners_on_token ON ci_runners USING btree (token);
CREATE INDEX index_ci_runners_on_token_encrypted ON ci_runners USING btree (token_encrypted);
CREATE UNIQUE INDEX index_ci_running_builds_on_build_id ON ci_running_builds USING btree (build_id);
CREATE INDEX index_ci_running_builds_on_project_id ON ci_running_builds USING btree (project_id);
CREATE INDEX index_ci_running_builds_on_runner_id ON ci_running_builds USING btree (runner_id);
CREATE INDEX index_ci_sources_pipelines_on_pipeline_id ON ci_sources_pipelines USING btree (pipeline_id);
CREATE INDEX index_ci_sources_pipelines_on_project_id ON ci_sources_pipelines USING btree (project_id);
@ -25028,8 +25060,6 @@ CREATE INDEX tmp_idx_deduplicate_vulnerability_occurrences ON vulnerability_occu
CREATE INDEX tmp_idx_on_namespaces_delayed_project_removal ON namespaces USING btree (id) WHERE (delayed_project_removal = true);
CREATE INDEX tmp_index_on_security_findings_scan_id ON security_findings USING btree (scan_id) WHERE (uuid IS NULL);
CREATE INDEX tmp_index_on_vulnerabilities_non_dismissed ON vulnerabilities USING btree (id) WHERE (state <> 2);
CREATE UNIQUE INDEX uniq_pkgs_deb_grp_architectures_on_distribution_id_and_name ON packages_debian_group_architectures USING btree (distribution_id, name);
@ -26708,6 +26738,9 @@ ALTER TABLE ONLY vulnerability_scanners
ALTER TABLE ONLY reviews
ADD CONSTRAINT fk_rails_5ca11d8c31 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_running_builds
ADD CONSTRAINT fk_rails_5ca491d360 FOREIGN KEY (runner_id) REFERENCES ci_runners(id) ON DELETE CASCADE;
ALTER TABLE ONLY epic_issues
ADD CONSTRAINT fk_rails_5d942936b4 FOREIGN KEY (epic_id) REFERENCES epics(id) ON DELETE CASCADE;
@ -27413,6 +27446,9 @@ ALTER TABLE ONLY geo_hashed_storage_attachments_events
ALTER TABLE ONLY merge_request_reviewers
ADD CONSTRAINT fk_rails_d9fec24b9d FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_running_builds
ADD CONSTRAINT fk_rails_da45cfa165 FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
ALTER TABLE ONLY jira_imports
ADD CONSTRAINT fk_rails_da617096ce FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL;
@ -27425,6 +27461,9 @@ ALTER TABLE ONLY issues_prometheus_alert_events
ALTER TABLE ONLY board_user_preferences
ADD CONSTRAINT fk_rails_dbebdaa8fe FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_running_builds
ADD CONSTRAINT fk_rails_dc1d0801e8 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_occurrence_pipelines
ADD CONSTRAINT fk_rails_dc3ae04693 FOREIGN KEY (occurrence_id) REFERENCES vulnerability_occurrences(id) ON DELETE CASCADE;

View File

@ -1171,7 +1171,7 @@ POST /projects
| `ci_config_path` | string | **{dotted-circle}** No | The path to CI configuration file. |
| `container_expiration_policy_attributes` | hash | **{dotted-circle}** No | Update the image cleanup policy for this project. Accepts: `cadence` (string), `keep_n` (integer), `older_than` (string), `name_regex` (string), `name_regex_delete` (string), `name_regex_keep` (string), `enabled` (boolean). Valid values for `cadence` are: `1d` (every day), `7d` (every week), `14d` (every two weeks), `1month` (every month), or `3month` (every quarter). |
| `container_registry_enabled` | boolean | **{dotted-circle}** No | Enable container registry for this project. |
| `default_branch` | string | **{dotted-circle}** No | The [default branch](../user/project/repository/branches/default.md) name. |
| `default_branch` | string | **{dotted-circle}** No | The [default branch](../user/project/repository/branches/default.md) name. Requires `initialize_with_readme` to be `true`. |
| `description` | string | **{dotted-circle}** No | Short project description. |
| `emails_disabled` | boolean | **{dotted-circle}** No | Disable email notifications. |
| `external_authorization_classification_label` **(PREMIUM)** | string | **{dotted-circle}** No | The classification label for the project. |
@ -1246,6 +1246,7 @@ POST /projects/user/:user_id
| `ci_config_path` | string | **{dotted-circle}** No | The path to CI configuration file. |
| `container_registry_enabled` | boolean | **{dotted-circle}** No | Enable container registry for this project. |
| `description` | string | **{dotted-circle}** No | Short project description. |
| `default_branch` | string | **{dotted-circle}** No | The [default branch](../user/project/repository/branches/default.md) name. Requires `initialize_with_readme` to be `true`. |
| `emails_disabled` | boolean | **{dotted-circle}** No | Disable email notifications. |
| `external_authorization_classification_label` **(PREMIUM)** | string | **{dotted-circle}** No | The classification label for the project. |
| `forking_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. |

View File

@ -557,6 +557,7 @@ request, be sure to start the `dont-interrupt-me` job before pushing.
1. These cache definitions are composed of [multiple atomic caches](../ci/yaml/README.md#multiple-caches).
1. Only 6 specific jobs, running in 2-hourly scheduled pipelines, are pushing (i.e. updating) to the caches:
- `update-setup-test-env-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).
- `update-gitaly-binaries-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).
- `update-static-analysis-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).
- `update-qa-cache`, defined in [`.gitlab/ci/qa.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/qa.gitlab-ci.yml).
- `update-assets-compile-production-cache`, defined in [`.gitlab/ci/frontend.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/frontend.gitlab-ci.yml).

View File

@ -16,15 +16,25 @@ staging and canary deployments,
## Custom buildpacks
If the automatic buildpack detection fails for your project, or if you want to
use a custom buildpack, you can override the buildpack using a project CI/CD variable
or a `.buildpacks` file in your project:
If the automatic buildpack detection fails for your project, or if you
need more control over your build, you can customize the buildpacks
used for the build.
- **Project variable** - Create a project variable `BUILDPACK_URL` with the URL
of the buildpack to use.
- **`.buildpacks` file** - Add a file in your project's repository called `.buildpacks`,
and add the URL of the buildpack to use on a line in the file. If you want to
use multiple buildpacks, enter one buildpack per line.
### Custom buildpacks with Cloud Native Buildpacks
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28165) in GitLab 12.10.
Specify either:
- The CI/CD variable `BUILDPACK_URL` according to [`pack`'s specifications](https://buildpacks.io/docs/app-developer-guide/specific-buildpacks/).
- A [`project.toml` project descriptor](https://buildpacks.io/docs/app-developer-guide/using-project-descriptor/) with the buildpacks you would like to include.
### Custom buildpacks with Herokuish
Specify either:
- The CI/CD variable `BUILDPACK_URL`.
- A `.buildpacks` file at the root of your project, containing one buildpack URL per line.
The buildpack URL can point to either a Git repository URL or a tarball URL.
For Git repositories, you can point to a specific Git reference (such as
@ -347,8 +357,8 @@ applications.
| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the Ingress hosts. |
| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the Ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
| `AUTO_DEVOPS_ATOMIC_RELEASE` | As of GitLab 13.0, Auto DevOps uses [`--atomic`](https://v2.helm.sh/docs/helm/#options-43) for Helm deployments by default. Set this variable to `false` to disable the use of `--atomic` |
| `AUTO_DEVOPS_BUILD_IMAGE_CNB_ENABLED` | When set to a non-empty value and no `Dockerfile` is present, Auto Build builds your application using Cloud Native Buildpacks instead of Herokuish. [More details](stages.md#auto-build-using-cloud-native-buildpacks-beta). |
| `AUTO_DEVOPS_BUILD_IMAGE_CNB_BUILDER` | The builder used when building with Cloud Native Buildpacks. The default builder is `heroku/buildpacks:18`. [More details](stages.md#auto-build-using-cloud-native-buildpacks-beta). |
| `AUTO_DEVOPS_BUILD_IMAGE_CNB_ENABLED` | Set to `false` to use Herokuish instead of Cloud Native Buildpacks with Auto Build. [More details](stages.md#auto-build-using-cloud-native-buildpacks). |
| `AUTO_DEVOPS_BUILD_IMAGE_CNB_BUILDER` | The builder used when building with Cloud Native Buildpacks. The default builder is `heroku/buildpacks:18`. [More details](stages.md#auto-build-using-cloud-native-buildpacks). |
| `AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS` | Extra arguments to be passed to the `docker build` command. Note that using quotes doesn't prevent word splitting. [More details](#passing-arguments-to-docker-build). |
| `AUTO_DEVOPS_BUILD_IMAGE_FORWARDED_CI_VARIABLES` | A [comma-separated list of CI/CD variable names](#forward-cicd-variables-to-the-build-environment) to be forwarded to the build environment (the buildpack builder or `docker build`). |
| `AUTO_DEVOPS_CHART` | Helm Chart used to deploy your apps. Defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image/-/tree/master/assets/auto-deploy-app). |
@ -358,7 +368,7 @@ applications.
| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From GitLab 11.11, used to set a password to connect to the Helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME`. |
| `AUTO_DEVOPS_DEPLOY_DEBUG` | From GitLab 13.1, if this variable is present, Helm outputs debug logs. |
| `AUTO_DEVOPS_ALLOW_TO_FORCE_DEPLOY_V<N>` | From [auto-deploy-image](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image) v1.0.0, if this variable is present, a new major version of chart is forcibly deployed. For more information, see [Ignore warnings and continue deploying](upgrading_auto_deploy_dependencies.md#ignore-warnings-and-continue-deploying). |
| `BUILDPACK_URL` | Buildpack's full URL. Can point to either [a Git repository URL or a tarball URL](#custom-buildpacks). |
| `BUILDPACK_URL` | Buildpack's full URL. [Must point to a URL supported by Pack or Herokuish](#custom-buildpacks). |
| `CANARY_ENABLED` | From GitLab 11.0, used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). |
| `CANARY_PRODUCTION_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. Takes precedence over `CANARY_REPLICAS`. Defaults to 1. |
| `CANARY_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md). Defaults to 1. |

View File

@ -33,15 +33,24 @@ your own `Dockerfile`, you must either:
- Override the default values by
[customizing the Auto Deploy Helm chart](customize.md#custom-helm-chart).
### Auto Build using Heroku buildpacks
### Auto Build using Cloud Native Buildpacks
> - Introduced in [GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28165).
> - Auto Build using Cloud Native Buildpacks by default was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63351) in GitLab 14.0.
Auto Build builds an application using a project's `Dockerfile` if present. If no
`Dockerfile` is present, it uses [Herokuish](https://github.com/gliderlabs/herokuish)
and [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks)
to detect and build the application into a Docker image.
`Dockerfile` is present, Auto Build builds your application using
[Cloud Native Buildpacks](https://buildpacks.io) to detect and build the
application into a Docker image. The feature uses the
[`pack` command](https://github.com/buildpacks/pack).
The default [builder](https://buildpacks.io/docs/concepts/components/builder/)
is `heroku/buildpacks:18` but a different builder can be selected using
the CI/CD variable `AUTO_DEVOPS_BUILD_IMAGE_CNB_BUILDER`.
Each buildpack requires your project's repository to contain certain files for
Auto Build to build your application successfully. For example, your application's
Auto Build to build your application successfully. The structure is
specific to the builder and buildpacks you have selected.
For example, when using the Heroku's builder (the default), your application's
root directory must contain the appropriate file for your application's
language:
@ -51,40 +60,39 @@ language:
For the requirements of other languages and frameworks, read the
[Heroku buildpacks documentation](https://devcenter.heroku.com/articles/buildpacks#officially-supported-buildpacks).
NOTE:
Auto Test still uses Herokuish, as test suite detection is not
yet part of the Cloud Native Buildpack specification. For more information, see
[this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/212689).
### Auto Build using Herokuish
> [Replaced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63351) with Cloud Native Buildpacks in GitLab 14.0.
Prior to GitLab 14.0, [Herokuish](https://github.com/gliderlabs/herokuish) was
the default build method for projects without a `Dockerfile`. Herokuish can
still be used by setting the CI/CD variable `AUTO_DEVOPS_BUILD_IMAGE_CNB_ENABLED`
to `false`.
NOTE:
If Auto Build fails despite the project meeting the buildpack requirements, set
a project CI/CD variable `TRACE=true` to enable verbose logging, which may help you
troubleshoot.
### Auto Build using Cloud Native Buildpacks (beta)
> Introduced in [GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28165).
Auto Build supports building your application using [Cloud Native Buildpacks](https://buildpacks.io)
through the [`pack` command](https://github.com/buildpacks/pack). To use Cloud Native Buildpacks,
set the CI/CD variable `AUTO_DEVOPS_BUILD_IMAGE_CNB_ENABLED` to a non-empty
value. The default builder is `heroku/buildpacks:18` but a different builder
can be selected using the CI/CD variable `AUTO_DEVOPS_BUILD_IMAGE_CNB_BUILDER`.
Cloud Native Buildpacks (CNBs) are an evolution of Heroku buildpacks, and
GitLab expects them to eventually supersede Herokuish-based builds within Auto DevOps. For more
information, see [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/212692).
### Moving from Herokuish to Cloud Native Buildpacks
Builds using Cloud Native Buildpacks support the same options as builds using
Heroku buildpacks, with the following caveats:
Herokuish, with the following caveats:
- The buildpack must be a Cloud Native Buildpack. A Heroku buildpack can be
converted to a Cloud Native Buildpack using Heroku's
[`cnb-shim`](https://github.com/heroku/cnb-shim).
- `BUILDPACK_URL` must be in a form
- `BUILDPACK_URL` must be in a format
[supported by `pack`](https://buildpacks.io/docs/app-developer-guide/specific-buildpacks/).
- The `/bin/herokuish` command is not present in the resulting image, and prefixing
- The `/bin/herokuish` command is not present in the built image, and prefixing
commands with `/bin/herokuish procfile exec` is no longer required (nor possible).
NOTE:
Auto Test still uses Herokuish, as test suite detection is not
yet part of the Cloud Native Buildpack specification. For more information, see
[this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/212689).
Instead, custom commands should be prefixed with `/cnb/lifecycle/launcher`
to receive the correct execution environment.
## Auto Test
@ -461,15 +469,16 @@ If present, `DB_MIGRATE` is run as a shell command within an application pod as
a Helm pre-upgrade hook.
For example, in a Rails application in an image built with
[Herokuish](https://github.com/gliderlabs/herokuish):
[Cloud Native Buildpacks](#auto-build-using-cloud-native-buildpacks):
- `DB_INITIALIZE` can be set to `RAILS_ENV=production /bin/herokuish procfile exec bin/rails db:setup`
- `DB_MIGRATE` can be set to `RAILS_ENV=production /bin/herokuish procfile exec bin/rails db:migrate`
- `DB_INITIALIZE` can be set to `RAILS_ENV=production /cnb/lifecycle/launcher bin/rails db:setup`
- `DB_MIGRATE` can be set to `RAILS_ENV=production /cnb/lifecycle/launcher bin/rails db:migrate`
Unless your repository contains a `Dockerfile`, your image is built with
Herokuish, and you must prefix commands run in these images with
`/bin/herokuish procfile exec` (for Herokuish) or `/cnb/lifecycle/launcher`
(for Cloud Native Buildpacks) to replicate the environment where your
Cloud Native Buildpacks, and you must prefix commands run in these images with
`/cnb/lifecycle/launcher`, (or `/bin/herokuish procfile exec` when
using [Herokuish](#auto-build-using-herokuish))
to replicate the environment where your
application runs.
### Upgrade auto-deploy-app Chart
@ -508,14 +517,10 @@ workers:
sidekiq:
replicaCount: 1
command:
- /bin/herokuish
- procfile
- exec
- /cnb/lifecycle/launcher
- sidekiq
preStopCommand:
- /bin/herokuish
- procfile
- exec
- /cnb/lifecycle/launcher
- sidekiqctl
- quiet
terminationGracePeriodSeconds: 60

View File

@ -9,7 +9,7 @@ disqus_identifier: 'https://docs.gitlab.com/ee/customization/branded_login_page.
# GitLab Appearance **(FREE SELF)**
There are several options for customizing the appearance of a self-managed instance
of GitLab. These settings are accessed from the **Admin Area** in the **Appearance**
of GitLab. These settings are accessed from the **Admin Area** in the **Settings > Appearance**
section.
## Navigation bar

View File

@ -5,16 +5,22 @@ module API
class Snippet < BasicSnippet
expose :author, using: Entities::UserBasic
expose :file_name do |snippet|
snippet.file_name_on_repo || snippet.file_name
snippet_files.first || snippet.file_name
end
expose :files do |snippet, options|
snippet.list_files.map do |file|
snippet_files.map do |file|
{
path: file,
raw_url: Gitlab::UrlBuilder.build(snippet, file: file, ref: snippet.repository.root_ref)
}
end
end
private
def snippet_files
@snippet_files ||= object.list_files
end
end
end
end

View File

@ -234,6 +234,7 @@ module API
params do
optional :name, type: String, desc: 'The name of the project'
optional :path, type: String, desc: 'The path of the repository'
optional :default_branch, type: String, desc: 'The default branch of the project'
at_least_one_of :name, :path
use :optional_create_project_params
use :create_params

View File

@ -2,7 +2,7 @@
module BulkImports
module Clients
class Http
class HTTP
API_VERSION = 'v4'
DEFAULT_PAGE = 1
DEFAULT_PER_PAGE = 30

View File

@ -24,7 +24,7 @@ module BulkImports
attr_reader :query
def http_client(configuration)
@http_client ||= BulkImports::Clients::Http.new(
@http_client ||= BulkImports::Clients::HTTP.new(
uri: configuration.url,
token: configuration.access_token,
per_page: 100

View File

@ -17,7 +17,7 @@ module BulkImports
private
def http_client(configuration)
@http_client ||= BulkImports::Clients::Http.new(
@http_client ||= BulkImports::Clients::HTTP.new(
uri: configuration.url,
token: configuration.access_token,
per_page: 100

View File

@ -33,7 +33,9 @@ module Gitlab
:queue_replication_lag,
:runner_pre_assign_checks_failed,
:runner_pre_assign_checks_success,
:runner_queue_tick
:runner_queue_tick,
:shared_runner_build_new,
:shared_runner_build_done
].to_set.freeze
QUEUE_DEPTH_HISTOGRAMS = [

View File

@ -1,10 +1,10 @@
build:
stage: build
image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:v0.6.0"
image: 'registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:v1.0.0'
variables:
DOCKER_TLS_CERTDIR: ""
DOCKER_TLS_CERTDIR: ''
services:
- name: "docker:20.10.6-dind"
- name: 'docker:20.10.6-dind'
command: ['--tls=false', '--host=tcp://0.0.0.0:2375']
script:
- |

View File

@ -32967,6 +32967,9 @@ msgstr ""
msgid "There was an error while fetching value stream analytics duration data."
msgstr ""
msgid "There was an error while fetching value stream summary data."
msgstr ""
msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
msgstr ""
@ -39385,6 +39388,9 @@ msgstr ""
msgid "the correct format."
msgstr ""
msgid "the file"
msgstr ""
msgid "the following issue(s)"
msgstr ""

View File

@ -338,7 +338,7 @@ module QA
autoload :Jenkins, 'qa/page/project/settings/services/jenkins'
autoload :Prometheus, 'qa/page/project/settings/services/prometheus'
end
autoload :Operations, 'qa/page/project/settings/operations'
autoload :Monitor, 'qa/page/project/settings/monitor'
autoload :Alerts, 'qa/page/project/settings/alerts'
autoload :Integrations, 'qa/page/project/settings/integrations'
end
@ -347,7 +347,9 @@ module QA
autoload :CiCd, 'qa/page/project/sub_menus/ci_cd'
autoload :Common, 'qa/page/project/sub_menus/common'
autoload :Issues, 'qa/page/project/sub_menus/issues'
autoload :Operations, 'qa/page/project/sub_menus/operations'
autoload :Monitor, 'qa/page/project/sub_menus/monitor'
autoload :Deployments, 'qa/page/project/sub_menus/deployments'
autoload :Infrastructure, 'qa/page/project/sub_menus/infrastructure'
autoload :Repository, 'qa/page/project/sub_menus/repository'
autoload :Settings, 'qa/page/project/sub_menus/settings'
autoload :Project, 'qa/page/project/sub_menus/project'
@ -370,25 +372,29 @@ module QA
autoload :Index, 'qa/page/project/milestone/index'
end
module Operations
module Deployments
module Environments
autoload :Index, 'qa/page/project/operations/environments/index'
autoload :Show, 'qa/page/project/operations/environments/show'
autoload :Index, 'qa/page/project/deployments/environments/index'
autoload :Show, 'qa/page/project/deployments/environments/show'
end
end
module Infrastructure
module Kubernetes
autoload :Index, 'qa/page/project/operations/kubernetes/index'
autoload :Add, 'qa/page/project/operations/kubernetes/add'
autoload :AddExisting, 'qa/page/project/operations/kubernetes/add_existing'
autoload :Show, 'qa/page/project/operations/kubernetes/show'
autoload :Index, 'qa/page/project/infrastructure/kubernetes/index'
autoload :Add, 'qa/page/project/infrastructure/kubernetes/add'
autoload :AddExisting, 'qa/page/project/infrastructure/kubernetes/add_existing'
autoload :Show, 'qa/page/project/infrastructure/kubernetes/show'
end
end
module Monitor
module Metrics
autoload :Show, 'qa/page/project/operations/metrics/show'
autoload :Show, 'qa/page/project/monitor/metrics/show'
end
module Incidents
autoload :Index, 'qa/page/project/operations/incidents/index'
autoload :Index, 'qa/page/project/monitor/incidents/index'
end
end

View File

@ -12,6 +12,8 @@ module QA
element :group_members_item
element :group_milestones_link
element :group_settings
element :group_information_link
element :group_information_submenu
end
view 'app/views/groups/sidebar/_packages_settings.html.haml' do
@ -24,8 +26,10 @@ module QA
end
def click_group_members_item
within_sidebar do
click_element(:group_members_item)
hover_element(:group_information_link) do
within_submenu(:group_information_submenu) do
click_element(:group_members_item)
end
end
end

View File

@ -3,7 +3,7 @@
module QA
module Page
module Project
module Operations
module Deployments
module Environments
class Index < Page::Base
view 'app/assets/javascripts/environments/components/environment_item.vue' do

View File

@ -3,7 +3,7 @@
module QA
module Page
module Project
module Operations
module Deployments
module Environments
class Show < Page::Base
view 'app/views/projects/environments/_external_url.html.haml' do

View File

@ -3,7 +3,7 @@
module QA
module Page
module Project
module Operations
module Infrastructure
module Kubernetes
class Add < Page::Base
view 'app/views/clusters/clusters/new.html.haml' do

View File

@ -3,7 +3,7 @@
module QA
module Page
module Project
module Operations
module Infrastructure
module Kubernetes
class AddExisting < Page::Base
view 'app/views/clusters/clusters/user/_form.html.haml' do
@ -32,7 +32,7 @@ module QA
end
def add_cluster!
click_element :add_kubernetes_cluster_button, Page::Project::Operations::Kubernetes::Show
click_element :add_kubernetes_cluster_button, Page::Project::Infrastructure::Kubernetes::Show
end
def uncheck_rbac!

View File

@ -3,7 +3,7 @@
module QA
module Page
module Project
module Operations
module Infrastructure
module Kubernetes
class Index < Page::Base
view 'app/views/clusters/clusters/_empty_state.html.haml' do

View File

@ -3,7 +3,7 @@
module QA
module Page
module Project
module Operations
module Infrastructure
module Kubernetes
class Show < Page::Base
view 'app/assets/javascripts/clusters/components/applications.vue' do
@ -77,7 +77,7 @@ module QA
end
def save_domain
click_element :save_changes_button, Page::Project::Operations::Kubernetes::Show
click_element :save_changes_button, Page::Project::Infrastructure::Kubernetes::Show
end
def wait_for_cluster_health

View File

@ -8,7 +8,9 @@ module QA
include SubMenus::Project
include SubMenus::CiCd
include SubMenus::Issues
include SubMenus::Operations
include SubMenus::Deployments
include SubMenus::Monitor
include SubMenus::Infrastructure
include SubMenus::Repository
include SubMenus::Settings
include SubMenus::Packages
@ -26,8 +28,10 @@ module QA
end
def click_activity
within_sidebar do
click_element(:sidebar_menu_item_link, menu_item: 'Activity')
hover_project_information do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Activity')
end
end
end
@ -38,8 +42,21 @@ module QA
end
def click_members
hover_project_information do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Members')
end
end
end
private
def hover_project_information
within_sidebar do
click_element(:sidebar_menu_link, menu_item: 'Members')
scroll_to_element(:sidebar_menu_link, menu_item: 'Project information')
find_element(:sidebar_menu_link, menu_item: 'Project information').hover
yield
end
end
end

View File

@ -3,7 +3,7 @@
module QA
module Page
module Project
module Operations
module Monitor
module Incidents
class Index < Page::Base
view 'app/assets/javascripts/incidents/components/incidents_list.vue' do

View File

@ -5,7 +5,7 @@ require 'securerandom'
module QA
module Page
module Project
module Operations
module Monitor
module Metrics
class Show < Page::Base
EXPECTED_TITLE = 'Memory Usage (Total)'
@ -134,4 +134,4 @@ module QA
end
end
QA::Page::Project::Operations::Metrics::Show.prepend_mod_with('Page::Project::Operations::Metrics::Show', namespace: QA)
QA::Page::Project::Monitor::Metrics::Show.prepend_mod_with('Page::Project::Monitor::Metrics::Show', namespace: QA)

View File

@ -4,7 +4,7 @@ module QA
module Page
module Project
module Settings
class Operations < Page::Base
class Monitor < Page::Base
include QA::Page::Settings::Common
view 'app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue' do

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
module QA
module Page
module Project
module SubMenus
module Deployments
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::Common
end
end
def go_to_deployments_environments
hover_deployments do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Environments')
end
end
end
private
def hover_deployments
within_sidebar do
scroll_to_element(:sidebar_menu_link, menu_item: 'Deployments')
find_element(:sidebar_menu_link, menu_item: 'Deployments').hover
yield
end
end
end
end
end
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
module QA
module Page
module Project
module SubMenus
module Infrastructure
extend QA::Page::PageConcern
def self.included(base)
super
base.class_eval do
include QA::Page::Project::SubMenus::Common
end
end
def go_to_infrastructure_kubernetes
hover_infrastructure do
within_submenu do
click_link('Kubernetes clusters')
end
end
end
private
def hover_infrastructure
within_sidebar do
scroll_to_element(:sidebar_menu_link, menu_item: 'Infrastructure')
find_element(:sidebar_menu_link, menu_item: 'Infrastructure').hover
yield
end
end
end
end
end
end
end

View File

@ -4,7 +4,7 @@ module QA
module Page
module Project
module SubMenus
module Operations
module Monitor
extend QA::Page::PageConcern
def self.included(base)
@ -15,32 +15,16 @@ module QA
end
end
def go_to_operations_environments
hover_operations do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Environments')
end
end
end
def go_to_operations_metrics
hover_operations do
def go_to_monitor_metrics
hover_monitor do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Metrics')
end
end
end
def go_to_operations_kubernetes
hover_operations do
within_submenu do
click_link('Kubernetes')
end
end
end
def go_to_operations_incidents
hover_operations do
def go_to_monitor_incidents
hover_monitor do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Incidents')
end
@ -49,10 +33,10 @@ module QA
private
def hover_operations
def hover_monitor
within_sidebar do
scroll_to_element(:sidebar_menu_link, menu_item: 'Operations')
find_element(:sidebar_menu_link, menu_item: 'Operations').hover
scroll_to_element(:sidebar_menu_link, menu_item: 'Monitor')
find_element(:sidebar_menu_link, menu_item: 'Monitor').hover
yield
end

View File

@ -12,13 +12,17 @@ module QA
base.class_eval do
include QA::Page::Project::SubMenus::Common
view 'app/views/shared/nav/_scope_menu_body.html.haml' do
element :project_scope_link
end
end
end
def click_project
retry_on_exception do
within_sidebar do
click_element(:sidebar_menu_link, menu_item: 'Project overview')
click_element(:project_scope_link)
end
end
end

View File

@ -53,10 +53,10 @@ module QA
end
end
def go_to_operations_settings
def go_to_monitor_settings
hover_settings do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Operations')
click_element(:sidebar_menu_item_link, menu_item: 'Monitor')
end
end
end

View File

@ -12,22 +12,22 @@ module QA
end
attribute :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
Page::Project::Infrastructure::Kubernetes::Show.perform(&:ingress_ip)
end
def fabricate!
project.visit!
Page::Project::Menu.perform(
&:go_to_operations_kubernetes)
&:go_to_infrastructure_kubernetes)
Page::Project::Operations::Kubernetes::Index.perform(
Page::Project::Infrastructure::Kubernetes::Index.perform(
&:add_kubernetes_cluster)
Page::Project::Operations::Kubernetes::Add.perform(
Page::Project::Infrastructure::Kubernetes::Add.perform(
&:add_existing_cluster)
Page::Project::Operations::Kubernetes::AddExisting.perform do |cluster_page|
Page::Project::Infrastructure::Kubernetes::AddExisting.perform do |cluster_page|
cluster_page.set_cluster_name(@cluster.cluster_name)
cluster_page.set_api_url(@cluster.api_url)
cluster_page.set_ca_certificate(@cluster.ca_certificate)
@ -36,7 +36,7 @@ module QA
cluster_page.add_cluster!
end
Page::Project::Operations::Kubernetes::Show.perform do |show|
Page::Project::Infrastructure::Kubernetes::Show.perform do |show|
# We must wait a few seconds for permissions to be set up correctly for new cluster
sleep 25

View File

@ -1,12 +1,10 @@
# frozen_string_literal: true
module QA
# TODO: Remove `:requires_admin` meta when the feature flag is removed
RSpec.describe 'Verify', :runner, :requires_admin do
RSpec.describe 'Verify', :runner do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let(:max_wait) { 30 }
let(:feature_flag) { :ci_drop_new_builds_when_ci_quota_exceeded }
let(:project) do
Resource::Project.fabricate_via_api! do |project|
@ -28,8 +26,6 @@ module QA
it 'users creates a pipeline which gets processed', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1849' do
# TODO: Convert back to :smoke once proved to be stable. Related issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300909
tags_mismatch_status = Runtime::Feature.enabled?(feature_flag, project: project) ? :failed : :pending
Flow::Login.sign_in
Resource::Repository::Commit.fabricate_via_api! do |commit|
@ -75,7 +71,7 @@ module QA
{
'test-success': :passed,
'test-failure': :failed,
'test-tags-mismatch': tags_mismatch_status,
'test-tags-mismatch': :pending,
'test-artifacts': :passed
}.each do |job, status|
Page::Project::Pipeline::Show.perform do |pipeline|

View File

@ -81,11 +81,11 @@ module QA
job.click_element(:pipeline_path)
end
Page::Project::Menu.perform(&:go_to_operations_environments)
Page::Project::Operations::Environments::Index.perform do |index|
Page::Project::Menu.perform(&:go_to_deployments_environments)
Page::Project::Deployments::Environments::Index.perform do |index|
index.click_environment_link('production')
end
Page::Project::Operations::Environments::Show.perform do |show|
Page::Project::Deployments::Environments::Show.perform do |show|
show.view_deployment do
expect(page).to have_content('Hello World!')
expect(page).to have_content('you_can_see_this_variable')

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