Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
536d72ba7e
commit
a5605d87fb
|
@ -46,8 +46,6 @@ Rails/SaveBang:
|
|||
Exclude:
|
||||
- 'ee/spec/controllers/projects/merge_requests_controller_spec.rb'
|
||||
- 'ee/spec/controllers/subscriptions_controller_spec.rb'
|
||||
- 'ee/spec/frontend/fixtures/analytics.rb'
|
||||
- 'ee/spec/graphql/resolvers/vulnerabilities_resolver_spec.rb'
|
||||
- 'ee/spec/initializers/fog_google_https_private_urls_spec.rb'
|
||||
- 'ee/spec/lib/analytics/merge_request_metrics_calculator_spec.rb'
|
||||
- 'ee/spec/lib/ee/gitlab/auth/ldap/sync/group_spec.rb'
|
||||
|
@ -182,8 +180,6 @@ Rails/SaveBang:
|
|||
- 'spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb'
|
||||
- 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb'
|
||||
- 'spec/lib/gitlab/git_access_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/object_pool_service_spec.rb'
|
||||
- 'spec/lib/gitlab/gitaly_client/repository_service_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/avatar_saver_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/base/relation_factory_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/design_repo_restorer_spec.rb'
|
||||
|
@ -424,7 +420,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
|
|||
Exclude:
|
||||
- ee/spec/controllers/admin/geo/projects_controller_spec.rb
|
||||
- ee/spec/controllers/admin/projects_controller_spec.rb
|
||||
- ee/spec/controllers/ee/projects/jobs_controller_spec.rb
|
||||
- ee/spec/controllers/groups/analytics/cycle_analytics/stages_controller_spec.rb
|
||||
- ee/spec/controllers/groups/analytics/cycle_analytics/summary_controller_spec.rb
|
||||
- ee/spec/controllers/groups/analytics/cycle_analytics/value_streams_controller_spec.rb
|
||||
|
|
6
Gemfile
6
Gemfile
|
@ -108,7 +108,7 @@ gem 'hashie-forbidden_attributes'
|
|||
gem 'kaminari', '~> 1.0'
|
||||
|
||||
# HAML
|
||||
gem 'hamlit', '~> 2.14.4'
|
||||
gem 'hamlit', '~> 2.15.0'
|
||||
|
||||
# Files attachments
|
||||
gem 'carrierwave', '~> 1.3'
|
||||
|
@ -294,7 +294,7 @@ gem 'terser', '1.0.2'
|
|||
|
||||
gem 'addressable', '~> 2.7'
|
||||
gem 'gemojione', '~> 3.3'
|
||||
gem 'gon', '~> 6.2'
|
||||
gem 'gon', '~> 6.4.0'
|
||||
gem 'request_store', '~> 1.5'
|
||||
gem 'base32', '~> 0.3.0'
|
||||
|
||||
|
@ -366,7 +366,7 @@ group :development, :test do
|
|||
|
||||
gem 'database_cleaner', '~> 1.7.0'
|
||||
gem 'factory_bot_rails', '~> 6.1.0'
|
||||
gem 'rspec-rails', '~> 4.1.2'
|
||||
gem 'rspec-rails', '~> 5.0.1'
|
||||
|
||||
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
|
||||
gem 'minitest', '~> 5.11.0'
|
||||
|
|
21
Gemfile.lock
21
Gemfile.lock
|
@ -499,8 +499,9 @@ GEM
|
|||
rubyntlm (~> 0.5)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
gon (6.2.0)
|
||||
actionpack (>= 3.0)
|
||||
gon (6.4.0)
|
||||
actionpack (>= 3.0.20)
|
||||
i18n (>= 0.7)
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
google-api-client (0.50.0)
|
||||
|
@ -591,7 +592,7 @@ GEM
|
|||
rainbow
|
||||
rubocop (>= 0.50.0)
|
||||
sysexits (~> 1.1)
|
||||
hamlit (2.14.4)
|
||||
hamlit (2.15.0)
|
||||
temple (>= 0.8.2)
|
||||
thor
|
||||
tilt
|
||||
|
@ -1075,10 +1076,10 @@ GEM
|
|||
proc_to_ast
|
||||
rspec (>= 2.13, < 4)
|
||||
unparser
|
||||
rspec-rails (4.1.2)
|
||||
actionpack (>= 4.2)
|
||||
activesupport (>= 4.2)
|
||||
railties (>= 4.2)
|
||||
rspec-rails (5.0.1)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rspec-core (~> 3.10)
|
||||
rspec-expectations (~> 3.10)
|
||||
rspec-mocks (~> 3.10)
|
||||
|
@ -1444,7 +1445,7 @@ DEPENDENCIES
|
|||
gitlab-styles (~> 6.2.0)
|
||||
gitlab_chronic_duration (~> 0.10.6.2)
|
||||
gitlab_omniauth-ldap (~> 2.1.1)
|
||||
gon (~> 6.2)
|
||||
gon (~> 6.4.0)
|
||||
google-api-client (~> 0.33)
|
||||
google-protobuf (~> 3.14.0)
|
||||
gpgme (~> 2.0.19)
|
||||
|
@ -1460,7 +1461,7 @@ DEPENDENCIES
|
|||
gssapi
|
||||
guard-rspec
|
||||
haml_lint (~> 0.36.0)
|
||||
hamlit (~> 2.14.4)
|
||||
hamlit (~> 2.15.0)
|
||||
hangouts-chat (~> 0.0.5)
|
||||
hashie
|
||||
hashie-forbidden_attributes
|
||||
|
@ -1563,7 +1564,7 @@ DEPENDENCIES
|
|||
rouge (~> 3.26.0)
|
||||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-parameterized
|
||||
rspec-rails (~> 4.1.2)
|
||||
rspec-rails (~> 5.0.1)
|
||||
rspec-retry (~> 0.6.1)
|
||||
rspec_junit_formatter
|
||||
rspec_profiling (~> 0.0.6)
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 29 KiB |
|
@ -184,7 +184,12 @@ export default {
|
|||
'viewDiffsFileByFile',
|
||||
'mrReviews',
|
||||
]),
|
||||
...mapGetters('diffs', ['whichCollapsedTypes', 'isParallelView', 'currentDiffIndex']),
|
||||
...mapGetters('diffs', [
|
||||
'whichCollapsedTypes',
|
||||
'isParallelView',
|
||||
'currentDiffIndex',
|
||||
'fileCodequalityDiff',
|
||||
]),
|
||||
...mapGetters(['isNotesFetched', 'getNoteableData']),
|
||||
diffs() {
|
||||
if (!this.viewDiffsFileByFile) {
|
||||
|
@ -282,6 +287,7 @@ export default {
|
|||
endpointMetadata: this.endpointMetadata,
|
||||
endpointBatch: this.endpointBatch,
|
||||
endpointCoverage: this.endpointCoverage,
|
||||
endpointCodequality: this.endpointCodequality,
|
||||
endpointUpdateUser: this.endpointUpdateUser,
|
||||
projectPath: this.projectPath,
|
||||
dismissEndpoint: this.dismissEndpoint,
|
||||
|
@ -291,10 +297,6 @@ export default {
|
|||
mrReviews: this.rehydratedMrReviews,
|
||||
});
|
||||
|
||||
if (this.endpointCodequality) {
|
||||
this.setCodequalityEndpoint(this.endpointCodequality);
|
||||
}
|
||||
|
||||
if (this.shouldShow) {
|
||||
this.fetchData();
|
||||
}
|
||||
|
@ -339,7 +341,6 @@ export default {
|
|||
...mapActions('diffs', [
|
||||
'moveToNeighboringCommit',
|
||||
'setBaseConfig',
|
||||
'setCodequalityEndpoint',
|
||||
'fetchDiffFilesMeta',
|
||||
'fetchDiffFilesBatch',
|
||||
'fetchCoverageFiles',
|
||||
|
@ -531,6 +532,7 @@ export default {
|
|||
:help-page-path="helpPagePath"
|
||||
:can-current-user-fork="canCurrentUserFork"
|
||||
:view-diffs-file-by-file="viewDiffsFileByFile"
|
||||
:codequality-diff="fileCodequalityDiff(file.file_path)"
|
||||
/>
|
||||
<div
|
||||
v-if="showFileByFileNavigation"
|
||||
|
|
|
@ -67,6 +67,11 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
codequalityDiff: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -80,7 +85,7 @@ export default {
|
|||
genericError: GENERIC_ERROR,
|
||||
},
|
||||
computed: {
|
||||
...mapState('diffs', ['currentDiffFileId', 'codequalityDiff']),
|
||||
...mapState('diffs', ['currentDiffFileId']),
|
||||
...mapGetters(['isNotesFetched']),
|
||||
...mapGetters('diffs', ['getDiffFileDiscussions']),
|
||||
viewBlobHref() {
|
||||
|
@ -149,9 +154,7 @@ export default {
|
|||
return loggedIn && featureOn;
|
||||
},
|
||||
hasCodequalityChanges() {
|
||||
return (
|
||||
this.codequalityDiff?.files && this.codequalityDiff?.files[this.file.file_path]?.length > 0
|
||||
);
|
||||
return this.codequalityDiff.length > 0;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -13,6 +13,11 @@ import {
|
|||
CONFLICT_THEIR,
|
||||
CONFLICT_MARKER,
|
||||
} from '../constants';
|
||||
import {
|
||||
getInteropInlineAttributes,
|
||||
getInteropOldSideAttributes,
|
||||
getInteropNewSideAttributes,
|
||||
} from '../utils/interoperability';
|
||||
import DiffGutterAvatars from './diff_gutter_avatars.vue';
|
||||
import * as utils from './diff_row_utils';
|
||||
|
||||
|
@ -116,6 +121,16 @@ export default {
|
|||
isLeftConflictMarker() {
|
||||
return [CONFLICT_MARKER_OUR, CONFLICT_MARKER_THEIR].includes(this.line.left?.type);
|
||||
},
|
||||
interopLeftAttributes() {
|
||||
if (this.inline) {
|
||||
return getInteropInlineAttributes(this.line.left);
|
||||
}
|
||||
|
||||
return getInteropOldSideAttributes(this.line.left);
|
||||
},
|
||||
interopRightAttributes() {
|
||||
return getInteropNewSideAttributes(this.line.right);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToLineIfNeededParallel(this.line);
|
||||
|
@ -181,6 +196,7 @@ export default {
|
|||
<div
|
||||
data-testid="left-side"
|
||||
class="diff-grid-left left-side"
|
||||
v-bind="interopLeftAttributes"
|
||||
@dragover.prevent
|
||||
@dragenter="onDragEnter(line.left, index)"
|
||||
@dragend="onDragEnd"
|
||||
|
@ -286,6 +302,7 @@ export default {
|
|||
v-if="!inline"
|
||||
data-testid="right-side"
|
||||
class="diff-grid-right right-side"
|
||||
v-bind="interopRightAttributes"
|
||||
@dragover.prevent
|
||||
@dragenter="onDragEnter(line.right, index)"
|
||||
@dragend="onDragEnd"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { CONTEXT_LINE_CLASS_NAME } from '../constants';
|
||||
import { getInteropInlineAttributes } from '../utils/interoperability';
|
||||
import DiffGutterAvatars from './diff_gutter_avatars.vue';
|
||||
import {
|
||||
isHighlighted,
|
||||
|
@ -96,6 +97,9 @@ export default {
|
|||
shouldShowAvatarsOnGutter() {
|
||||
return this.line.hasDiscussions;
|
||||
},
|
||||
interopAttrs() {
|
||||
return getInteropInlineAttributes(this.line);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToLineIfNeededInline(this.line);
|
||||
|
@ -124,6 +128,7 @@ export default {
|
|||
:id="inlineRowId"
|
||||
:class="classNameMap"
|
||||
class="line_holder"
|
||||
v-bind="interopAttrs"
|
||||
@mouseover="handleMouseMove"
|
||||
@mouseout="handleMouseMove"
|
||||
>
|
||||
|
|
|
@ -3,6 +3,10 @@ import { GlTooltipDirective, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gi
|
|||
import $ from 'jquery';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { CONTEXT_LINE_CLASS_NAME, PARALLEL_DIFF_VIEW_TYPE } from '../constants';
|
||||
import {
|
||||
getInteropOldSideAttributes,
|
||||
getInteropNewSideAttributes,
|
||||
} from '../utils/interoperability';
|
||||
import DiffGutterAvatars from './diff_gutter_avatars.vue';
|
||||
import * as utils from './diff_row_utils';
|
||||
|
||||
|
@ -108,6 +112,12 @@ export default {
|
|||
this.line.hasDiscussionsRight,
|
||||
);
|
||||
},
|
||||
interopLeftAttributes() {
|
||||
return getInteropOldSideAttributes(this.line.left);
|
||||
},
|
||||
interopRightAttributes() {
|
||||
return getInteropNewSideAttributes(this.line.right);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToLineIfNeededParallel(this.line);
|
||||
|
@ -217,6 +227,7 @@ export default {
|
|||
:key="line.left.line_code"
|
||||
v-safe-html="line.left.rich_text"
|
||||
:class="parallelViewLeftLineType"
|
||||
v-bind="interopLeftAttributes"
|
||||
class="line_content with-coverage parallel left-side"
|
||||
@mousedown="handleParallelLineMouseDown"
|
||||
></td>
|
||||
|
@ -283,6 +294,7 @@ export default {
|
|||
hll: isHighlighted,
|
||||
},
|
||||
]"
|
||||
v-bind="interopRightAttributes"
|
||||
class="line_content with-coverage parallel right-side"
|
||||
@mousedown="handleParallelLineMouseDown"
|
||||
></td>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Cookies from 'js-cookie';
|
||||
import Visibility from 'visibilityjs';
|
||||
import Vue from 'vue';
|
||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
|
@ -52,12 +53,15 @@ import {
|
|||
prepareLineForRenamedFile,
|
||||
} from './utils';
|
||||
|
||||
let eTagPoll;
|
||||
|
||||
export const setBaseConfig = ({ commit }, options) => {
|
||||
const {
|
||||
endpoint,
|
||||
endpointMetadata,
|
||||
endpointBatch,
|
||||
endpointCoverage,
|
||||
endpointCodequality,
|
||||
endpointUpdateUser,
|
||||
projectPath,
|
||||
dismissEndpoint,
|
||||
|
@ -71,6 +75,7 @@ export const setBaseConfig = ({ commit }, options) => {
|
|||
endpointMetadata,
|
||||
endpointBatch,
|
||||
endpointCoverage,
|
||||
endpointCodequality,
|
||||
endpointUpdateUser,
|
||||
projectPath,
|
||||
dismissEndpoint,
|
||||
|
@ -233,6 +238,48 @@ export const fetchCoverageFiles = ({ commit, state }) => {
|
|||
coveragePoll.makeRequest();
|
||||
};
|
||||
|
||||
export const clearEtagPoll = () => {
|
||||
eTagPoll = null;
|
||||
};
|
||||
|
||||
export const stopCodequalityPolling = () => {
|
||||
if (eTagPoll) eTagPoll.stop();
|
||||
};
|
||||
|
||||
export const restartCodequalityPolling = () => {
|
||||
if (eTagPoll) eTagPoll.restart();
|
||||
};
|
||||
|
||||
export const fetchCodequality = ({ commit, state, dispatch }) => {
|
||||
eTagPoll = new Poll({
|
||||
resource: {
|
||||
getCodequalityDiffReports: (endpoint) => axios.get(endpoint),
|
||||
},
|
||||
data: state.endpointCodequality,
|
||||
method: 'getCodequalityDiffReports',
|
||||
successCallback: ({ status, data }) => {
|
||||
if (status === httpStatusCodes.OK) {
|
||||
commit(types.SET_CODEQUALITY_DATA, data);
|
||||
|
||||
eTagPoll.stop();
|
||||
}
|
||||
},
|
||||
errorCallback: () => createFlash(__('Something went wrong on our end. Please try again!')),
|
||||
});
|
||||
|
||||
if (!Visibility.hidden()) {
|
||||
eTagPoll.makeRequest();
|
||||
}
|
||||
|
||||
Visibility.change(() => {
|
||||
if (!Visibility.hidden()) {
|
||||
dispatch('restartCodequalityPolling');
|
||||
} else {
|
||||
dispatch('stopCodequalityPolling');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const setHighlightedRow = ({ commit }, lineCode) => {
|
||||
const fileHash = lineCode.split('_')[0];
|
||||
commit(types.SET_HIGHLIGHTED_ROW, lineCode);
|
||||
|
|
|
@ -135,6 +135,16 @@ export const fileLineCoverage = (state) => (file, line) => {
|
|||
return {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the codequality diff data for a given file
|
||||
* @param {string} filePath
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const fileCodequalityDiff = (state) => (filePath) => {
|
||||
if (!state.codequalityDiff.files || !state.codequalityDiff.files[filePath]) return [];
|
||||
return state.codequalityDiff.files[filePath];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns index of a currently selected diff in diffFiles
|
||||
* @returns {number}
|
||||
|
|
|
@ -9,7 +9,8 @@ import {
|
|||
import { fileByFile } from '../../utils/preferences';
|
||||
import { getDefaultWhitespace } from '../utils';
|
||||
|
||||
const viewTypeFromQueryString = getParameterValues('view')[0];
|
||||
const getViewTypeFromQueryString = () => getParameterValues('view')[0];
|
||||
|
||||
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
|
||||
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
|
||||
const whiteSpaceFromQueryString = getParameterValues('w')[0];
|
||||
|
@ -29,9 +30,10 @@ export default () => ({
|
|||
startVersion: null, // Null unless a target diff is selected for comparison that is not the "base" diff
|
||||
diffFiles: [],
|
||||
coverageFiles: {},
|
||||
codequalityDiff: {},
|
||||
mergeRequestDiffs: [],
|
||||
mergeRequestDiff: null,
|
||||
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
|
||||
diffViewType: getViewTypeFromQueryString() || viewTypeFromCookie || defaultViewType,
|
||||
tree: [],
|
||||
treeEntries: {},
|
||||
showTreeList: true,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as actions from 'ee_else_ce/diffs/store/actions';
|
||||
import createState from 'ee_else_ce/diffs/store/modules/diff_state';
|
||||
import mutations from 'ee_else_ce/diffs/store/mutations';
|
||||
import * as actions from '../actions';
|
||||
import * as getters from '../getters';
|
||||
import mutations from '../mutations';
|
||||
import createState from './diff_state';
|
||||
|
||||
export default () => ({
|
||||
namespaced: true,
|
||||
|
|
|
@ -11,6 +11,7 @@ export const SET_MR_FILE_REVIEWS = 'SET_MR_FILE_REVIEWS';
|
|||
|
||||
export const SET_DIFF_VIEW_TYPE = 'SET_DIFF_VIEW_TYPE';
|
||||
export const SET_COVERAGE_DATA = 'SET_COVERAGE_DATA';
|
||||
export const SET_CODEQUALITY_DATA = 'SET_CODEQUALITY_DATA';
|
||||
export const SET_MERGE_REQUEST_DIFFS = 'SET_MERGE_REQUEST_DIFFS';
|
||||
export const TOGGLE_LINE_HAS_FORM = 'TOGGLE_LINE_HAS_FORM';
|
||||
export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
|
||||
|
|
|
@ -33,6 +33,7 @@ export default {
|
|||
endpointMetadata,
|
||||
endpointBatch,
|
||||
endpointCoverage,
|
||||
endpointCodequality,
|
||||
endpointUpdateUser,
|
||||
projectPath,
|
||||
dismissEndpoint,
|
||||
|
@ -46,6 +47,7 @@ export default {
|
|||
endpointMetadata,
|
||||
endpointBatch,
|
||||
endpointCoverage,
|
||||
endpointCodequality,
|
||||
endpointUpdateUser,
|
||||
projectPath,
|
||||
dismissEndpoint,
|
||||
|
@ -89,6 +91,10 @@ export default {
|
|||
Object.assign(state, { coverageFiles });
|
||||
},
|
||||
|
||||
[types.SET_CODEQUALITY_DATA](state, codequalityDiffData) {
|
||||
Object.assign(state, { codequalityDiff: codequalityDiffData });
|
||||
},
|
||||
|
||||
[types.RENDER_FILE](state, file) {
|
||||
renderFile(file);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
const OLD = 'old';
|
||||
const NEW = 'new';
|
||||
const ATTR_PREFIX = 'data-interop-';
|
||||
|
||||
export const ATTR_TYPE = `${ATTR_PREFIX}type`;
|
||||
export const ATTR_LINE = `${ATTR_PREFIX}line`;
|
||||
export const ATTR_NEW_LINE = `${ATTR_PREFIX}new-line`;
|
||||
export const ATTR_OLD_LINE = `${ATTR_PREFIX}old-line`;
|
||||
|
||||
export const getInteropInlineAttributes = (line) => {
|
||||
if (!line) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const interopType = line.type?.startsWith(OLD) ? OLD : NEW;
|
||||
|
||||
const interopLine = interopType === OLD ? line.old_line : line.new_line;
|
||||
|
||||
return {
|
||||
[ATTR_TYPE]: interopType,
|
||||
[ATTR_LINE]: interopLine,
|
||||
[ATTR_NEW_LINE]: line.new_line,
|
||||
[ATTR_OLD_LINE]: line.old_line,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInteropOldSideAttributes = (line) => {
|
||||
if (!line) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
[ATTR_TYPE]: OLD,
|
||||
[ATTR_LINE]: line.old_line,
|
||||
[ATTR_OLD_LINE]: line.old_line,
|
||||
};
|
||||
};
|
||||
|
||||
export const getInteropNewSideAttributes = (line) => {
|
||||
if (!line) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
[ATTR_TYPE]: NEW,
|
||||
[ATTR_LINE]: line.new_line,
|
||||
[ATTR_NEW_LINE]: line.new_line,
|
||||
};
|
||||
};
|
|
@ -20,7 +20,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-flex-center flex-nowrap text-nowrap js-ide-status-mr">
|
||||
<div class="d-flex-center gl-flex-nowrap text-nowrap js-ide-status-mr">
|
||||
<gl-icon name="merge-request" />
|
||||
<span class="ml-1 d-none d-sm-block">{{ s__('WebIDE|Merge request') }}</span>
|
||||
<gl-link class="ml-1" :href="url">{{ text }}</gl-link>
|
||||
|
|
|
@ -31,7 +31,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<dropdown-button>
|
||||
<span class="row flex-nowrap">
|
||||
<span class="row gl-flex-nowrap">
|
||||
<span class="col-auto flex-fill text-truncate">
|
||||
<gl-icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }}
|
||||
</span>
|
||||
|
|
|
@ -111,7 +111,7 @@ export default {
|
|||
</gl-link>
|
||||
</td>
|
||||
<td
|
||||
class="gl-display-flex gl-flex-sm-wrap gl-p-4 gl-pt-5 gl-vertical-align-top"
|
||||
class="gl-display-flex gl-sm-flex-wrap gl-p-4 gl-pt-5 gl-vertical-align-top"
|
||||
data-testid="fullPath"
|
||||
data-qa-selector="project_path_content"
|
||||
>
|
||||
|
|
|
@ -15,7 +15,7 @@ export default {
|
|||
<template>
|
||||
<div
|
||||
ref="linksSection"
|
||||
class="gl-sm-display-flex gl-flex-sm-wrap gl-mt-5 gl-p-3 gl-bg-gray-10 border gl-rounded-base links-section"
|
||||
class="gl-sm-display-flex gl-sm-flex-wrap gl-mt-5 gl-p-3 gl-bg-gray-10 border gl-rounded-base links-section"
|
||||
>
|
||||
<div
|
||||
v-for="(link, key) in links"
|
||||
|
|
|
@ -96,6 +96,9 @@ export default {
|
|||
<p class="gl-text-gray-700 gl-mb-6">{{ $options.i18n.plan.description }}</p>
|
||||
|
||||
<div class="row row-cols-2 row-cols-md-3 row-cols-lg-4">
|
||||
<div class="col gl-mb-6">
|
||||
<learn-gitlab-info-card v-bind="infoProps('issueCreated')" />
|
||||
</div>
|
||||
<div class="col gl-mb-6">
|
||||
<learn-gitlab-info-card v-bind="infoProps('mergeRequestCreated')" />
|
||||
</div>
|
||||
|
|
|
@ -61,7 +61,7 @@ export default {
|
|||
<div
|
||||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img :src="svg" />
|
||||
<img :src="svg" :alt="actionLabel" />
|
||||
<h6>{{ title }}</h6>
|
||||
<p class="gl-font-sm gl-text-gray-700">{{ description }}</p>
|
||||
<gl-link :href="url" target="_blank">{{ actionLabel }}</gl-link>
|
||||
|
|
|
@ -63,6 +63,15 @@ export const ACTION_LABELS = {
|
|||
section: 'deploy',
|
||||
position: 1,
|
||||
},
|
||||
issueCreated: {
|
||||
title: s__('LearnGitLab|Create an issue'),
|
||||
actionLabel: s__('LearnGitLab|Create an issue'),
|
||||
description: s__(
|
||||
'LearnGitLab|Create/import issues (tickets) to collaborate on ideas and plan work.',
|
||||
),
|
||||
section: 'plan',
|
||||
position: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const ACTION_SECTIONS = {
|
||||
|
|
|
@ -13,5 +13,6 @@ export const GRAPHQL = 'graphql';
|
|||
|
||||
export const STAGE_VIEW = 'stage';
|
||||
export const LAYER_VIEW = 'layer';
|
||||
export const VIEW_TYPE_KEY = 'pipeline_graph_view_type';
|
||||
|
||||
export const IID_FAILURE = 'missing_iid';
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
|
||||
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
|
||||
import { __ } from '~/locale';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { DEFAULT, DRAW_FAILURE, LOAD_FAILURE } from '../../constants';
|
||||
import { reportToSentry } from '../../utils';
|
||||
import { listByLayers } from '../parsing_utils';
|
||||
import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW } from './constants';
|
||||
import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY } from './constants';
|
||||
import PipelineGraph from './graph_component.vue';
|
||||
import GraphViewSelector from './graph_view_selector.vue';
|
||||
import {
|
||||
|
@ -22,6 +23,7 @@ export default {
|
|||
GlAlert,
|
||||
GlLoadingIcon,
|
||||
GraphViewSelector,
|
||||
LocalStorageSync,
|
||||
PipelineGraph,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
|
@ -143,7 +145,7 @@ export default {
|
|||
return this.$apollo.queries.pipeline.loading && !this.pipeline;
|
||||
},
|
||||
showGraphViewSelector() {
|
||||
return Boolean(this.glFeatures.pipelineGraphLayersView && this.pipeline);
|
||||
return Boolean(this.glFeatures.pipelineGraphLayersView && this.pipeline?.usesNeeds);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
@ -184,6 +186,7 @@ export default {
|
|||
this.currentViewType = type;
|
||||
},
|
||||
},
|
||||
viewTypeKey: VIEW_TYPE_KEY,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -191,11 +194,17 @@ export default {
|
|||
<gl-alert v-if="showAlert" :variant="alert.variant" @dismiss="hideAlert">
|
||||
{{ alert.text }}
|
||||
</gl-alert>
|
||||
<local-storage-sync
|
||||
:storage-key="$options.viewTypeKey"
|
||||
:value="currentViewType"
|
||||
@input="updateViewType"
|
||||
>
|
||||
<graph-view-selector
|
||||
v-if="showGraphViewSelector"
|
||||
:type="currentViewType"
|
||||
@updateViewType="updateViewType"
|
||||
/>
|
||||
</local-storage-sync>
|
||||
<gl-loading-icon v-if="showLoadingIcon" class="gl-mx-auto gl-my-4" size="lg" />
|
||||
<pipeline-graph
|
||||
v-if="pipeline"
|
||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
|||
<template>
|
||||
<div class="gl-display-flex gl-align-items-center gl-my-4">
|
||||
<span>{{ $options.i18n.labelText }}</span>
|
||||
<gl-dropdown class="gl-ml-4">
|
||||
<gl-dropdown data-testid="pipeline-view-selector" class="gl-ml-4">
|
||||
<template #button-content>
|
||||
<gl-sprintf :message="currentDropdownText">
|
||||
<template #code="{ content }">
|
||||
|
|
|
@ -3,18 +3,13 @@
|
|||
class Profiles::NotificationsController < Profiles::ApplicationController
|
||||
feature_category :users
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def show
|
||||
@user = current_user
|
||||
@user_groups = user_groups
|
||||
@group_notifications = UserGroupNotificationSettingsFinder.new(current_user, user_groups).execute
|
||||
|
||||
@project_notifications = current_user.notification_settings.for_projects.order(:id)
|
||||
.preload_source_route
|
||||
.select { |notification| current_user.can?(:read_project, notification.source) }
|
||||
@project_notifications = project_notifications_with_preloaded_associations
|
||||
@global_notification_setting = current_user.global_notification_setting
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def update
|
||||
result = Users::UpdateService.new(current_user, user_params.merge(user: current_user)).execute
|
||||
|
@ -37,4 +32,20 @@ class Profiles::NotificationsController < Profiles::ApplicationController
|
|||
def user_groups
|
||||
GroupsFinder.new(current_user, all_available: false).execute.order_name_asc.page(params[:page])
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def project_notifications_with_preloaded_associations
|
||||
project_notifications = current_user
|
||||
.notification_settings
|
||||
.for_projects
|
||||
.order_by_id_asc
|
||||
.preload_source_route
|
||||
|
||||
projects = project_notifications.map(&:source)
|
||||
ActiveRecord::Associations::Preloader.new.preload(projects, { namespace: [:route, :owner], group: [] })
|
||||
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute
|
||||
|
||||
project_notifications.select { |notification| current_user.can?(:read_project, notification.source) }
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
@ -27,6 +27,7 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) {
|
|||
__typename
|
||||
id
|
||||
iid
|
||||
usesNeeds
|
||||
downstream {
|
||||
__typename
|
||||
nodes {
|
||||
|
|
|
@ -18,6 +18,17 @@ module BoardIssueFilterable
|
|||
end
|
||||
|
||||
def set_filter_values(filters)
|
||||
filter_by_assignee(filters)
|
||||
end
|
||||
|
||||
def filter_by_assignee(filters)
|
||||
if filters[:assignee_username] && filters[:assignee_wildcard_id]
|
||||
raise ::Gitlab::Graphql::Errors::ArgumentError, 'Incompatible arguments: assigneeUsername, assigneeWildcardId.'
|
||||
end
|
||||
|
||||
if filters[:assignee_wildcard_id]
|
||||
filters[:assignee_id] = filters.delete(:assignee_wildcard_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Boards
|
||||
class AssigneeWildcardIdEnum < BaseEnum
|
||||
graphql_name 'AssigneeWildcardId'
|
||||
description 'Assignee ID wildcard values'
|
||||
|
||||
value 'NONE', 'No assignee is assigned.'
|
||||
value 'ANY', 'An assignee is assigned.'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -18,6 +18,10 @@ module Types
|
|||
argument :search, GraphQL::STRING_TYPE,
|
||||
required: false,
|
||||
description: 'Search query for issue title or description.'
|
||||
|
||||
argument :assignee_wildcard_id, ::Types::Boards::AssigneeWildcardIdEnum,
|
||||
required: false,
|
||||
description: 'Filter by assignee wildcard. Incompatible with assigneeUsername.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -84,3 +84,4 @@ module AppearancesHelper
|
|||
end
|
||||
|
||||
AppearancesHelper.prepend_if_ee('EE::AppearancesHelper')
|
||||
AppearancesHelper.prepend_if_jh('JH::AppearancesHelper')
|
||||
|
|
|
@ -194,10 +194,16 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def promo_host
|
||||
# This needs to be used outside of Rails
|
||||
def self.promo_host
|
||||
'about.gitlab.com'
|
||||
end
|
||||
|
||||
# Convenient method for Rails helper
|
||||
def promo_host
|
||||
ApplicationHelper.promo_host
|
||||
end
|
||||
|
||||
def promo_url
|
||||
'https://' + promo_host
|
||||
end
|
||||
|
@ -406,3 +412,4 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
ApplicationHelper.prepend_if_ee('EE::ApplicationHelper')
|
||||
ApplicationHelper.prepend_if_jh('JH::ApplicationHelper')
|
||||
|
|
|
@ -24,6 +24,7 @@ module LearnGitlabHelper
|
|||
private
|
||||
|
||||
ACTION_ISSUE_IDS = {
|
||||
issue_created: 4,
|
||||
git_write: 6,
|
||||
pipeline_created: 7,
|
||||
merge_request_created: 9,
|
||||
|
|
|
@ -13,13 +13,7 @@ module BulkMemberAccessLoad
|
|||
raise 'Block is mandatory' unless block_given?
|
||||
|
||||
resource_ids = resource_ids.uniq
|
||||
key = max_member_access_for_resource_key(resource_klass, memoization_index)
|
||||
access = {}
|
||||
|
||||
if Gitlab::SafeRequestStore.active?
|
||||
Gitlab::SafeRequestStore[key] ||= {}
|
||||
access = Gitlab::SafeRequestStore[key]
|
||||
end
|
||||
access = load_access_hash(resource_klass, memoization_index)
|
||||
|
||||
# Look up only the IDs we need
|
||||
resource_ids -= access.keys
|
||||
|
@ -39,10 +33,28 @@ module BulkMemberAccessLoad
|
|||
access
|
||||
end
|
||||
|
||||
def merge_value_to_request_store(resource_klass, resource_id, memoization_index, value)
|
||||
max_member_access_for_resource_ids(resource_klass, [resource_id], memoization_index) do
|
||||
{ resource_id => value }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def max_member_access_for_resource_key(klass, memoization_index)
|
||||
"max_member_access_for_#{klass.name.underscore.pluralize}:#{memoization_index}"
|
||||
end
|
||||
|
||||
def load_access_hash(resource_klass, memoization_index)
|
||||
key = max_member_access_for_resource_key(resource_klass, memoization_index)
|
||||
|
||||
access = {}
|
||||
if Gitlab::SafeRequestStore.active?
|
||||
Gitlab::SafeRequestStore[key] ||= {}
|
||||
access = Gitlab::SafeRequestStore[key]
|
||||
end
|
||||
|
||||
access
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ module CascadingNamespaceSettingAttribute
|
|||
|
||||
class_methods do
|
||||
def cascading_settings_feature_enabled?
|
||||
::Feature.enabled?(:cascading_namespace_settings, default_enabled: false)
|
||||
::Feature.enabled?(:cascading_namespace_settings, default_enabled: true)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -77,9 +77,14 @@ module HasRepository
|
|||
def default_branch_from_preferences
|
||||
return unless empty_repo?
|
||||
|
||||
group_branch_default_name = group&.default_branch_name if respond_to?(:group)
|
||||
(default_branch_from_group_preferences || Gitlab::CurrentSettings.default_branch_name).presence
|
||||
end
|
||||
|
||||
(group_branch_default_name || Gitlab::CurrentSettings.default_branch_name).presence
|
||||
def default_branch_from_group_preferences
|
||||
return unless respond_to?(:group)
|
||||
return unless group
|
||||
|
||||
group.default_branch_name || group.root_ancestor.default_branch_name
|
||||
end
|
||||
|
||||
def reload_default_branch
|
||||
|
|
|
@ -30,6 +30,8 @@ class NotificationSetting < ApplicationRecord
|
|||
|
||||
scope :preload_source_route, -> { preload(source: [:route]) }
|
||||
|
||||
scope :order_by_id_asc, -> { order(id: :asc) }
|
||||
|
||||
# NOTE: Applicable unfound_translations.rb also needs to be updated when below events are changed.
|
||||
EMAIL_EVENTS = [
|
||||
:new_release,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Preloaders
|
||||
# This class preloads the max access level for the user within the given projects and
|
||||
# stores the values in requests store via the ProjectTeam class.
|
||||
class UserMaxAccessLevelInProjectsPreloader
|
||||
def initialize(projects, user)
|
||||
@projects = projects
|
||||
@user = user
|
||||
end
|
||||
|
||||
def execute
|
||||
access_levels = @user
|
||||
.project_authorizations
|
||||
.where(project_id: @projects)
|
||||
.group(:project_id)
|
||||
.maximum(:access_level)
|
||||
|
||||
@projects.each do |project|
|
||||
access_level = access_levels[project.id] || Gitlab::Access::NO_ACCESS
|
||||
ProjectTeam.new(project).write_member_access_for_user_id(@user.id, access_level)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -174,6 +174,10 @@ class ProjectTeam
|
|||
end
|
||||
end
|
||||
|
||||
def write_member_access_for_user_id(user_id, project_access_level)
|
||||
merge_value_to_request_store(User, user_id, project.id, project_access_level)
|
||||
end
|
||||
|
||||
def max_member_access(user_id)
|
||||
max_member_access_for_user_ids([user_id])[user_id]
|
||||
end
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Boards
|
||||
module Lists
|
||||
class BaseUpdateService < Boards::BaseService
|
||||
def execute(list)
|
||||
if execute_by_params(list)
|
||||
success(list: list)
|
||||
else
|
||||
error(list.errors.messages, 422)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_by_params(list)
|
||||
update_preferences_result = update_preferences(list) if can_read?(list)
|
||||
update_position_result = update_position(list) if can_admin?(list)
|
||||
|
||||
update_preferences_result || update_position_result
|
||||
end
|
||||
|
||||
def update_preferences(list)
|
||||
return unless preferences?
|
||||
|
||||
list.update_preferences_for(current_user, preferences)
|
||||
end
|
||||
|
||||
def update_position(list)
|
||||
return unless position?
|
||||
|
||||
move_service = Boards::Lists::MoveService.new(parent, current_user, params)
|
||||
|
||||
move_service.execute(list)
|
||||
end
|
||||
|
||||
def preferences
|
||||
{ collapsed: Gitlab::Utils.to_boolean(params[:collapsed]) }
|
||||
end
|
||||
|
||||
def preferences?
|
||||
params.has_key?(:collapsed)
|
||||
end
|
||||
|
||||
def position?
|
||||
params.has_key?(:position)
|
||||
end
|
||||
|
||||
def can_read?(list)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def can_admin?(list)
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,50 +2,7 @@
|
|||
|
||||
module Boards
|
||||
module Lists
|
||||
class UpdateService < Boards::BaseService
|
||||
def execute(list)
|
||||
if execute_by_params(list)
|
||||
success(list: list)
|
||||
else
|
||||
error(list.errors.messages, 422)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_by_params(list)
|
||||
update_preferences_result = update_preferences(list) if can_read?(list)
|
||||
update_position_result = update_position(list) if can_admin?(list)
|
||||
|
||||
update_preferences_result || update_position_result
|
||||
end
|
||||
|
||||
def update_preferences(list)
|
||||
return unless preferences?
|
||||
|
||||
list.update_preferences_for(current_user, preferences)
|
||||
end
|
||||
|
||||
def update_position(list)
|
||||
return unless position?
|
||||
|
||||
move_service = Boards::Lists::MoveService.new(parent, current_user, params)
|
||||
|
||||
move_service.execute(list)
|
||||
end
|
||||
|
||||
def preferences
|
||||
{ collapsed: Gitlab::Utils.to_boolean(params[:collapsed]) }
|
||||
end
|
||||
|
||||
def preferences?
|
||||
params.has_key?(:collapsed)
|
||||
end
|
||||
|
||||
def position?
|
||||
params.has_key?(:position)
|
||||
end
|
||||
|
||||
class UpdateService < Boards::Lists::BaseUpdateService
|
||||
def can_read?(list)
|
||||
Ability.allowed?(current_user, :read_issue_board_list, parent)
|
||||
end
|
||||
|
|
|
@ -31,9 +31,9 @@ module Projects
|
|||
|
||||
# Create status notifying the deployment of pages
|
||||
@status = create_status
|
||||
@status.update_older_statuses_retried! if Feature.enabled?(:ci_fix_commit_status_retried, project, default_enabled: :yaml)
|
||||
@status.enqueue!
|
||||
@status.run!
|
||||
@status.update_older_statuses_retried! if Feature.enabled?(:ci_fix_commit_status_retried, project, default_enabled: :yaml)
|
||||
|
||||
raise InvalidStateError, 'missing pages artifacts' unless build.artifacts?
|
||||
raise InvalidStateError, 'build SHA is outdated for this ref' unless latest?
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
= render "devise/shared/error_messages", resource: resource
|
||||
.form-group
|
||||
= f.label :email
|
||||
= f.email_field :email, class: "form-control gl-form-input", required: true, value: params[:user_email], autofocus: true, title: 'Please provide a valid email address.'
|
||||
= f.email_field :email, class: "form-control gl-form-input", required: true, value: params[:user_email], autofocus: true, title: _('Please provide a valid email address.')
|
||||
.clearfix
|
||||
= f.submit "Reset password", class: "gl-button btn-confirm btn"
|
||||
= f.submit _("Reset password"), class: "gl-button btn-confirm btn"
|
||||
|
||||
.clearfix.prepend-top-20
|
||||
= render 'devise/shared/sign_in_link'
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
.form-group.col-12.col-sm-6
|
||||
= label_tag :namespace_id, _('Project URL'), class: 'label-bold'
|
||||
.form-group
|
||||
.input-group.flex-nowrap
|
||||
.input-group.gl-flex-nowrap
|
||||
- if current_user.can_select_namespace?
|
||||
.input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
|
||||
.input-group-text
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
.form-group.project-path.col-sm-6
|
||||
= f.label :namespace_id, class: 'label-bold' do
|
||||
%span= s_("Project URL")
|
||||
.input-group.flex-nowrap
|
||||
.input-group.gl-flex-nowrap
|
||||
- if current_user.can_select_namespace?
|
||||
.input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
|
||||
.input-group-text
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
- flex_grow_and_shrink_xs = 'd-flex flex-xs-grow-1 flex-xs-shrink-1 flex-grow-0 flex-shrink-0'
|
||||
|
||||
.filtered-search-block.row-content-block.bt-0
|
||||
.filtered-search-wrapper.d-flex.flex-nowrap.flex-column.flex-sm-wrap.flex-sm-row.flex-xl-nowrap
|
||||
.filtered-search-wrapper.d-flex.gl-flex-nowrap.flex-column.flex-sm-wrap.flex-sm-row.flex-xl-nowrap
|
||||
- unless project_tab_filter == :starred
|
||||
.filtered-search-nav.mb-2.mb-lg-0{ class: flex_grow_and_shrink_xs }
|
||||
= render 'dashboard/projects/nav', project_tab_filter: project_tab_filter
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Eliminate N+1 database queries on the user notifications page within the project
|
||||
notifications section
|
||||
merge_request: 59029
|
||||
author:
|
||||
type: performance
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support filtering by assignee wildcard in GraphQL board list issues query
|
||||
merge_request: 58996
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Inherit default branch name for subgroups
|
||||
merge_request: 57101
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Externalize strings in passwords/new.html.haml
|
||||
merge_request: 58236
|
||||
author: nuwe1
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Default enable cascading settings feature flag
|
||||
merge_request: 59026
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update gon gem to 6.4.0
|
||||
merge_request: 51210
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Bump rspec-rails to 5.0.1
|
||||
merge_request: 59194
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix Rails/SaveBang Rubocop offenses for gitaly client models
|
||||
merge_request: 58089
|
||||
author: Huzaifa Iftikhar @huzaifaiftikhar
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Schedule artifact expiry backfill again.
|
||||
merge_request: 59270
|
||||
author:
|
||||
type: changed
|
|
@ -57,21 +57,29 @@ module Gitlab
|
|||
|
||||
config.generators.templates.push("#{config.root}/generator_templates")
|
||||
|
||||
if Gitlab.ee?
|
||||
ee_paths = config.eager_load_paths.each_with_object([]) do |path, memo|
|
||||
ee_path = config.root.join('ee', Pathname.new(path).relative_path_from(config.root))
|
||||
memo << ee_path.to_s
|
||||
load_paths = lambda do |dir:|
|
||||
ext_paths = config.eager_load_paths.each_with_object([]) do |path, memo|
|
||||
ext_path = config.root.join(dir, Pathname.new(path).relative_path_from(config.root))
|
||||
memo << ext_path.to_s
|
||||
end
|
||||
|
||||
ee_paths << "#{config.root}/ee/app/replicators"
|
||||
ext_paths << "#{config.root}/#{dir}/app/replicators"
|
||||
|
||||
# Eager load should load CE first
|
||||
config.eager_load_paths.push(*ee_paths)
|
||||
config.helpers_paths.push "#{config.root}/ee/app/helpers"
|
||||
config.eager_load_paths.push(*ext_paths)
|
||||
config.helpers_paths.push "#{config.root}/#{dir}/app/helpers"
|
||||
|
||||
# Other than Ruby modules we load EE first
|
||||
config.paths['lib/tasks'].unshift "#{config.root}/ee/lib/tasks"
|
||||
config.paths['app/views'].unshift "#{config.root}/ee/app/views"
|
||||
# Other than Ruby modules we load extensions first
|
||||
config.paths['lib/tasks'].unshift "#{config.root}/#{dir}/lib/tasks"
|
||||
config.paths['app/views'].unshift "#{config.root}/#{dir}/app/views"
|
||||
end
|
||||
|
||||
Gitlab.ee do
|
||||
load_paths.call(dir: 'ee')
|
||||
end
|
||||
|
||||
Gitlab.jh do
|
||||
load_paths.call(dir: 'jh')
|
||||
end
|
||||
|
||||
# Rake tasks ignore the eager loading settings, so we need to set the
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: cascading_namespace_settings
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55678
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321724
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327230
|
||||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::access
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -31,6 +31,12 @@ module InjectEnterpriseEditionModule
|
|||
include(ee_module) if Gitlab.ee?
|
||||
end
|
||||
|
||||
def prepend_if_jh(constant, with_descendants: false)
|
||||
return unless Gitlab.jh?
|
||||
|
||||
prepend_module(constant.constantize, with_descendants)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepend_module(mod, with_descendants)
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Gitlab.ee do
|
||||
load_license = lambda do |dir:, license_name:|
|
||||
prefix = ENV['GITLAB_LICENSE_MODE'] == 'test' ? 'test_' : ''
|
||||
public_key_file = File.read(Rails.root.join(".#{prefix}license_encryption_key.pub"))
|
||||
public_key_file = File.read(Rails.root.join(dir, ".#{prefix}license_encryption_key.pub"))
|
||||
public_key = OpenSSL::PKey::RSA.new(public_key_file)
|
||||
Gitlab::License.encryption_key = public_key
|
||||
rescue
|
||||
warn "WARNING: No valid license encryption key provided."
|
||||
warn "WARNING: No valid #{license_name} encryption key provided."
|
||||
end
|
||||
|
||||
Gitlab.ee do
|
||||
load_license.call(dir: '.', license_name: 'license')
|
||||
end
|
||||
|
||||
Gitlab.jh do
|
||||
load_license.call(dir: 'jh', license_name: 'JH license')
|
||||
end
|
||||
|
|
|
@ -35,5 +35,6 @@ ActiveSupport::Inflector.inflections do |inflect|
|
|||
vulnerability_feedback
|
||||
)
|
||||
inflect.acronym 'EE'
|
||||
inflect.acronym 'JH'
|
||||
inflect.acronym 'CSP'
|
||||
end
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RescheduleArtifactExpiryBackfillAgain < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
MIGRATION = 'BackfillArtifactExpiryDate'
|
||||
SWITCH_DATE = Date.new(2020, 06, 22).freeze
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
class JobArtifact < ActiveRecord::Base
|
||||
include EachBatch
|
||||
|
||||
self.inheritance_column = :_type_disabled
|
||||
self.table_name = 'ci_job_artifacts'
|
||||
|
||||
scope :without_expiry_date, -> { where(expire_at: nil) }
|
||||
scope :before_switch, -> { where("date(created_at AT TIME ZONE 'UTC') < ?::date", SWITCH_DATE) }
|
||||
end
|
||||
|
||||
def up
|
||||
Gitlab::BackgroundMigration.steal(MIGRATION) do |job|
|
||||
job.delete
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
queue_background_migration_jobs_by_range_at_intervals(
|
||||
JobArtifact.without_expiry_date.before_switch,
|
||||
MIGRATION,
|
||||
2.minutes,
|
||||
batch_size: 200_000
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
Gitlab::BackgroundMigration.steal(MIGRATION) do |job|
|
||||
job.delete
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
407806cc168ef9859c9a4f1bd4db7a56aee01367e784ea0767889863b9ace35d
|
|
@ -446,6 +446,134 @@ Now we need to make each **secondary** node listen to changes on the new **prima
|
|||
to [initiate the replication process](../setup/database.md#step-3-initiate-the-replication-process) again but this time
|
||||
for another **primary** node. All the old replication settings will be overwritten.
|
||||
|
||||
## Promoting a secondary Geo cluster in GitLab Cloud Native Helm Charts
|
||||
|
||||
When updating a Cloud Native Geo deployment, the process for updating any node that is external to the secondary Kubernetes cluster does not differ from the non Cloud Native approach. As such, you can always defer to [Promoting a secondary Geo node in single-secondary configurations](#promoting-a-secondary-geo-node-in-single-secondary-configurations) for more information.
|
||||
|
||||
The following sections assume you are using the `gitlab` namespace. If you used a different namespace when setting up your cluster, you should also replace `--namespace gitlab` with your namespace.
|
||||
|
||||
WARNING:
|
||||
In GitLab 13.2 and 13.3, promoting a secondary site to a primary while the
|
||||
secondary is paused fails. Do not pause replication before promoting a
|
||||
secondary. If the site is paused, be sure to resume before promoting. This
|
||||
issue has been fixed in GitLab 13.4 and later.
|
||||
|
||||
### Step 1. Permanently disable the **primary** cluster
|
||||
|
||||
WARNING:
|
||||
If the **primary** site goes offline, there may be data saved on the **primary** site
|
||||
that has not been replicated to the **secondary** site. This data should be treated
|
||||
as lost if you proceed.
|
||||
|
||||
If an outage on the **primary** site happens, you should do everything possible to
|
||||
avoid a split-brain situation where writes can occur in two different GitLab
|
||||
instances, complicating recovery efforts. So to prepare for the failover, you
|
||||
must disable the **primary** site:
|
||||
|
||||
- If you have access to the **primary** Kubernetes cluster, connect to it and disable the GitLab webservice and Sidekiq pods:
|
||||
|
||||
```shell
|
||||
kubectl --namespace gitlab scale deploy gitlab-geo-webservice-default --replicas=0
|
||||
kubectl --namespace gitlab scale deploy gitlab-geo-sidekiq-all-in-1-v1 --replicas=0
|
||||
```
|
||||
|
||||
- If you do not have access to the **primary** Kubernetes cluster, take the cluster offline and
|
||||
prevent it from coming back online by any means at your disposal.
|
||||
Since there are many ways you may prefer to accomplish this, we will avoid a
|
||||
single recommendation. You may need to:
|
||||
|
||||
- Reconfigure the load balancers.
|
||||
- Change DNS records (for example, point the primary DNS record to the
|
||||
**secondary** site to stop usage of the **primary** site).
|
||||
- Stop the virtual servers.
|
||||
- Block traffic through a firewall.
|
||||
- Revoke object storage permissions from the **primary** site.
|
||||
- Physically disconnect a machine.
|
||||
|
||||
### Step 2. Promote all **secondary** nodes external to the cluster
|
||||
|
||||
WARNING:
|
||||
If the secondary site [has been paused](../../geo/index.md#pausing-and-resuming-replication), this performs
|
||||
a point-in-time recovery to the last known state.
|
||||
Data that was created on the primary while the secondary was paused will be lost.
|
||||
|
||||
1. SSH in to the database node in the **secondary** and trigger PostgreSQL to
|
||||
promote to read-write:
|
||||
|
||||
```shell
|
||||
sudo gitlab-ctl promote-db
|
||||
```
|
||||
|
||||
In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found).
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb` on the database node in the **secondary** site to
|
||||
reflect its new status as **primary** by removing any lines that enabled the
|
||||
`geo_secondary_role`:
|
||||
|
||||
NOTE:
|
||||
Depending on your architecture these steps will need to be run on any GitLab node that is external to the **secondary** Kubernetes cluster.
|
||||
|
||||
```ruby
|
||||
## In pre-11.5 documentation, the role was enabled as follows. Remove this line.
|
||||
geo_secondary_role['enable'] = true
|
||||
|
||||
## In 11.5+ documentation, the role was enabled as follows. Remove this line.
|
||||
roles ['geo_secondary_role']
|
||||
```
|
||||
|
||||
After making these changes, [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure) on the database node.
|
||||
|
||||
### Step 3. Promote the **secondary** cluster
|
||||
|
||||
1. Find the task runner pod:
|
||||
|
||||
```shell
|
||||
kubectl --namespace gitlab get pods -lapp=task-runner
|
||||
```
|
||||
|
||||
1. Promote the secondary:
|
||||
|
||||
```shell
|
||||
kubectl --namespace gitlab exec -ti gitlab-geo-task-runner-XXX -- gitlab-rake geo:set_secondary_as_primary
|
||||
```
|
||||
|
||||
1. Update the existing cluster configuration.
|
||||
|
||||
You can retrieve the existing config with Helm:
|
||||
|
||||
```shell
|
||||
helm --namespace gitlab get values gitlab-geo > gitlab.yaml
|
||||
```
|
||||
|
||||
The existing config will contain a section for Geo that should resemble:
|
||||
|
||||
```yaml
|
||||
geo:
|
||||
enabled: true
|
||||
role: secondary
|
||||
nodeName: secondary.example.com
|
||||
psql:
|
||||
host: geo-2.db.example.com
|
||||
port: 5431
|
||||
password:
|
||||
secret: geo
|
||||
key: geo-postgresql-password
|
||||
```
|
||||
|
||||
To promote the **secondary** cluster to a **primary** cluster, update `role: secondary` to `role: primary`.
|
||||
|
||||
You can remove the entire `psql` section if the cluster will remain as a primary site, this refers to the tracking database and will be ignored whilst the cluster is acting as a primary site.
|
||||
|
||||
Update the cluster with the new config:
|
||||
|
||||
```shell
|
||||
helm upgrade --install --version <current Chart version> gitlab-geo gitlab/gitlab --namespace gitlab -f gitlab.yaml
|
||||
```
|
||||
|
||||
1. Verify you can connect to the newly promoted primary using the URL used previously for the secondary.
|
||||
|
||||
1. Success! The secondary has now been promoted to primary.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
This section was moved to [another location](../replication/troubleshooting.md#fixing-errors-during-a-failover-or-when-promoting-a-secondary-to-a-primary-node).
|
||||
|
|
|
@ -139,6 +139,18 @@ The following metrics can be controlled by feature flags:
|
|||
| `gitlab_method_call_duration_seconds` | `prometheus_metrics_method_instrumentation` |
|
||||
| `gitlab_view_rendering_duration_seconds` | `prometheus_metrics_view_instrumentation` |
|
||||
|
||||
## Praefect metrics
|
||||
|
||||
You can [configure Praefect to report metrics](../../gitaly/praefect.md#praefect).
|
||||
These are some of the Praefect metrics served from the `/metrics` path on the [configured port](index.md#changing-the-port-and-address-prometheus-listens-on)
|
||||
(9652 by default).
|
||||
|
||||
| Metric | Type | Since | Description | Labels |
|
||||
| :----- | :--- | ----: | :---------- | :----- |
|
||||
| `gitaly_praefect_replication_latency_bucket` | Histogram | 12.10 | The amount of time it takes for replication to complete once the replication job starts. | |
|
||||
| `gitaly_praefect_replication_delay_bucket` | Histogram | 12.10 | A measure of how much time passes between when the replication job is created and when it starts. | |
|
||||
| `gitaly_praefect_node_latency_bucket` | Histogram | 12.10 | The latency in Gitaly returning health check information to Praefect. This indicates Praefect connection saturation. | |
|
||||
|
||||
## Sidekiq metrics
|
||||
|
||||
Sidekiq jobs may also gather metrics, and these metrics can be accessed if the
|
||||
|
|
|
@ -7525,6 +7525,15 @@ The kind of an approval rule.
|
|||
| `REGULAR` | A `regular` approval rule. |
|
||||
| `REPORT_APPROVER` | A `report_approver` approval rule. |
|
||||
|
||||
### `AssigneeWildcardId`
|
||||
|
||||
Assignee ID wildcard values.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| `ANY` | An assignee is assigned. |
|
||||
| `NONE` | No assignee is assigned. |
|
||||
|
||||
### `AvailabilityEnum`
|
||||
|
||||
User availability status.
|
||||
|
|
|
@ -1,181 +1,8 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
redirect_to: 'https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#monthly-documentation-releases'
|
||||
---
|
||||
|
||||
# Monthly release process
|
||||
This file was moved to [another location](https://about.gitlab.com/handbook/engineering/ux/technical-writing/workflow/#monthly-documentation-releases).
|
||||
|
||||
When a new GitLab version is released on the 22nd, we release version-specific published
|
||||
documentation for the new version.
|
||||
|
||||
We complete the process as soon as possible after the GitLab version is announced. The result is:
|
||||
|
||||
- The [online published documentation](https://docs.gitlab.com) includes:
|
||||
- The three most recent minor releases of the current major version. For example 13.9, 13.8, and
|
||||
13.7.
|
||||
- The most recent minor releases of the last two major versions. For example 12.10, and 11.11.
|
||||
- Documentation updates after the 22nd are for the next release. The versions drop down
|
||||
should have the current milestone with `-pre` appended to it, for example `13.10-pre`.
|
||||
|
||||
Each documentation release:
|
||||
|
||||
- Has a dedicated branch, named in the format `XX.yy`.
|
||||
- Has a Docker image that contains a build of that branch.
|
||||
|
||||
For example:
|
||||
|
||||
- For [GitLab 13.9](https://docs.gitlab.com/13.9/index.html), the
|
||||
[stable branch](https://gitlab.com/gitlab-org/gitlab-docs/-/tree/13.9) and Docker image:
|
||||
[`registry.gitlab.com/gitlab-org/gitlab-docs:13.9`](https://gitlab.com/gitlab-org/gitlab-docs/container_registry/631635).
|
||||
- For [GitLab 13.8](https://docs.gitlab.com/13.8/index.html), the
|
||||
[stable branch](https://gitlab.com/gitlab-org/gitlab-docs/-/tree/13.8) and Docker image:
|
||||
[`registry.gitlab.com/gitlab-org/gitlab-docs:13.8`](https://gitlab.com/gitlab-org/gitlab-docs/container_registry/631635).
|
||||
|
||||
## Recommended timeline
|
||||
|
||||
To minimize problems during the documentation release process, use the following timeline:
|
||||
|
||||
- Any time before the 17th of the month:
|
||||
|
||||
[Add the charts version](#add-chart-version), so that the documentation is built using the
|
||||
[version of the charts project that maps to](https://docs.gitlab.com/charts/installation/version_mappings.html)
|
||||
the GitLab release. This step may have been completed already.
|
||||
|
||||
- Between the 17th and the 20th of the month:
|
||||
|
||||
1. [Create a stable branch and Docker image](#create-stable-branch-and-docker-image-for-release) for
|
||||
the new version.
|
||||
1. [Create a release merge request](#create-release-merge-request) for the new version, which
|
||||
updates the version dropdown menu for the current documentation and adds the release to the
|
||||
Docker configuration. For example, the
|
||||
[release merge request for 13.9](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/1555).
|
||||
1. [Update the three online versions](#update-dropdown-for-online-versions), so that they display the new release on their
|
||||
version dropdown menus.
|
||||
|
||||
- On the 22nd of the month:
|
||||
|
||||
[Merge the release merge requests and run the necessary Docker image builds](#merge-merge-requests-and-run-docker-image-builds).
|
||||
|
||||
## Add chart version
|
||||
|
||||
To add a new charts version for the release:
|
||||
|
||||
1. Make sure you're in the root path of the `gitlab-docs` repository.
|
||||
1. Open `content/_data/chart_versions.yaml` and add the new stable branch version using the
|
||||
[version mapping](https://docs.gitlab.com/charts/installation/version_mappings.html). Only the
|
||||
`major.minor` version is needed.
|
||||
1. Create a new merge request and merge it.
|
||||
|
||||
NOTE:
|
||||
If you have time, add anticipated future mappings to `content/_data/chart_versions.yaml`. This saves
|
||||
a step for the next GitLab release.
|
||||
|
||||
## Create stable branch and Docker image for release
|
||||
|
||||
To create a stable branch and Docker image for the release:
|
||||
|
||||
1. Make sure you're in the root path of the `gitlab-docs` repository.
|
||||
1. Run the Rake task to create the single version. For example, to create the 13.9 release branch
|
||||
and perform others tasks:
|
||||
|
||||
```shell
|
||||
./bin/rake "release:single[13.9]"
|
||||
```
|
||||
|
||||
A branch for the release is created, a new `Dockerfile.13.9` is created, and `.gitlab-ci.yml`
|
||||
has branches variables updated into a new branch. These files are automatically committed.
|
||||
|
||||
1. Push the newly created branch, but **don't create a merge request**. After you push, the
|
||||
`image:docs-single` job creates a new Docker image tagged with the name of the branch you created
|
||||
earlier. You can see the Docker image in the `registry` environment at
|
||||
<https://gitlab.com/gitlab-org/gitlab-docs/-/environments/folders/registry>.
|
||||
|
||||
For example, see [the 13.9 release pipeline](https://gitlab.com/gitlab-org/gitlab-docs/-/pipelines/260288747).
|
||||
|
||||
Optionally, you can test locally by:
|
||||
|
||||
1. Building the image and running it. For example, for GitLab 13.9 documentation:
|
||||
|
||||
```shell
|
||||
docker build -t docs:13.9 -f Dockerfile.13.9 .
|
||||
docker run -it --rm -p 4000:4000 docs:13.9
|
||||
```
|
||||
|
||||
1. Visiting <http://localhost:4000/13.9/> to see if everything works correctly.
|
||||
|
||||
## Create release merge request
|
||||
|
||||
NOTE:
|
||||
An [epic is open](https://gitlab.com/groups/gitlab-org/-/epics/4361) to automate this step.
|
||||
|
||||
To create the release merge request for the release:
|
||||
|
||||
1. Make sure you're in the root path of the `gitlab-docs` repository.
|
||||
1. Create a branch `release-X-Y`. For example:
|
||||
|
||||
```shell
|
||||
git checkout master
|
||||
git checkout -b release-13-9
|
||||
```
|
||||
|
||||
1. Edit `content/_data/versions.yaml` and update the lists of versions to reflect the new release:
|
||||
|
||||
- Add the latest version to the `online:` section.
|
||||
- Move the oldest version in `online:` to the `offline:` section. There should now be three
|
||||
versions in `online:`.
|
||||
|
||||
1. Update these Dockerfiles:
|
||||
|
||||
- `dockerfiles/Dockerfile.archives`: Add the latest version to the top of the list.
|
||||
- `Dockerfile.master`: Remove the oldest version, and add the newest version to the
|
||||
top of the list.
|
||||
|
||||
1. Commit and push to create the merge request. For example:
|
||||
|
||||
```shell
|
||||
git add content/ Dockerfile.master dockerfiles/Dockerfile.archives
|
||||
git commit -m "Release 13.9"
|
||||
git push origin release-13-9
|
||||
```
|
||||
|
||||
Do not merge the release merge request yet.
|
||||
|
||||
## Update dropdown for online versions
|
||||
|
||||
To update `content/_data/versions.yaml` for all online versions (stable branches `X.Y` of the
|
||||
`gitlab-docs` project). For example:
|
||||
|
||||
- The merge request to [update the 13.9 version dropdown menu for the 13.9 release](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/1556).
|
||||
- The merge request to [update the 13.8 version dropdown menu for the 13.9 release](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/1557).
|
||||
- The merge request to [update the 13.7 version dropdown menu for the 13.9 release](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/1558).
|
||||
|
||||
1. Run the Rake task that creates all of the necessary merge requests to update the dropdowns. For
|
||||
example, for the 13.9 release:
|
||||
|
||||
```shell
|
||||
git checkout release-13-9
|
||||
./bin/rake release:dropdowns
|
||||
```
|
||||
|
||||
1. [Visit the merge requests page](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests?label_name%5B%5D=release)
|
||||
to check that their pipelines pass.
|
||||
|
||||
Do not merge these merge requests yet.
|
||||
|
||||
## Merge merge requests and run Docker image builds
|
||||
|
||||
The merge requests for the dropdowns should now all be merged into their respective stable branches.
|
||||
Each merge triggers a new pipeline for each stable branch. Wait for the stable branch pipelines to
|
||||
complete, then:
|
||||
|
||||
1. Check the [pipelines page](https://gitlab.com/gitlab-org/gitlab-docs/pipelines)
|
||||
and make sure all stable branches have green pipelines.
|
||||
1. After all the pipelines succeed:
|
||||
1. Merge all of the [dropdown merge requests](#update-dropdown-for-online-versions).
|
||||
1. Merge the [release merge request](#create-release-merge-request).
|
||||
1. Finally, run the
|
||||
[`Build docker images weekly` pipeline](https://gitlab.com/gitlab-org/gitlab-docs/pipeline_schedules)
|
||||
that builds the `:latest` and `:archives` Docker images.
|
||||
|
||||
As the last step in the scheduled pipeline, the documentation site deploys with all new versions.
|
||||
<!-- This redirect file can be deleted after <2021-07-12>. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
||||
|
|
|
@ -23,6 +23,8 @@ when no license is active. So EE features always should be guarded by
|
|||
`project.feature_available?` or `group.feature_available?` (or
|
||||
`License.feature_available?` if it is a system-wide feature).
|
||||
|
||||
Frontend features should be guarded by pushing a flag from the backend by [using `push_licensed_feature`](licensed_feature_availability.md#restricting-frontend-features), and checked using `this.glFeatures.someFeature` in the frontend.
|
||||
|
||||
CE specs should remain untouched as much as possible and extra specs
|
||||
should be added for EE. Licensed features can be stubbed using the
|
||||
spec helper `stub_licensed_features` in `EE::LicenseHelpers`.
|
||||
|
|
|
@ -292,8 +292,7 @@ end
|
|||
|
||||
### Frontend
|
||||
|
||||
Use the `push_frontend_feature_flag` method for frontend code, which is
|
||||
available to all controllers that inherit from `ApplicationController`. You can use
|
||||
Use the `push_frontend_feature_flag` method which is available to all controllers that inherit from `ApplicationController`. You can use
|
||||
this method to expose the state of a feature flag, for example:
|
||||
|
||||
```ruby
|
||||
|
|
|
@ -41,3 +41,16 @@ the instance license.
|
|||
```ruby
|
||||
License.feature_available?(:feature_symbol)
|
||||
```
|
||||
|
||||
## Restricting frontend features
|
||||
|
||||
To restrict frontend features based on the license, use `push_licensed_feature`.
|
||||
The frontend can then access this via `this.glFeatures`:
|
||||
|
||||
```ruby
|
||||
before_action do
|
||||
push_licensed_feature(:feature_symbol)
|
||||
# or by project/namespace
|
||||
push_licensed_feature(:feature_symbol, project)
|
||||
end
|
||||
```
|
||||
|
|
|
@ -58,6 +58,8 @@ To add a new application for your user:
|
|||
|
||||
## Group owned applications
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/16227) in GitLab 13.11.
|
||||
|
||||
To add a new application for a group:
|
||||
|
||||
1. Navigate to the desired group.
|
||||
|
|
|
@ -70,6 +70,12 @@ breaking communication between **primary** and **secondary** nodes when using
|
|||
HTTPS, customize your Internal URL to point to a load balancer with TLS
|
||||
terminated at the load balancer.
|
||||
|
||||
WARNING:
|
||||
Starting with GitLab 13.3 and [until 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/325522),
|
||||
using an internal URL that is not accessible to the users will result in the
|
||||
OAuth authorization flow not working properly, as the users will get redirected
|
||||
to the internal URL instead of the external one.
|
||||
|
||||
## Multiple secondary nodes behind a load balancer
|
||||
|
||||
In GitLab 11.11, **secondary** nodes can use identical external URLs as long as
|
||||
|
|
|
@ -51,8 +51,10 @@ directory in your repository. Commit and push to your default branch.
|
|||
|
||||
To create a Markdown file:
|
||||
|
||||
1. Click the `+` button next to `master` and click **New file**.
|
||||
1. Add the name of your issue template to the **File name** text field next to `master`.
|
||||
1. In a project, go to **Repository**.
|
||||
1. Next to the default branch, select the **{plus}** button.
|
||||
1. Select **New file**.
|
||||
1. Next to the default branch, in the **File name** field, add the name of your issue template.
|
||||
Make sure that your file has the `.md` extension, for
|
||||
example `feature_request.md` or `Feature Request.md`.
|
||||
1. Commit and push to your default branch.
|
||||
|
@ -61,9 +63,12 @@ If you don't have a `.gitlab/issue_templates` directory in your repository, you
|
|||
|
||||
To create the `.gitlab/issue_templates` directory:
|
||||
|
||||
1. Click the `+` button next to `master` and select **New directory**.
|
||||
1. In a project, go to **Repository**.
|
||||
1. Next to the default branch, select the **{plus}** button.
|
||||
1. Select **New directory**.
|
||||
1. Name this new directory `.gitlab` and commit to your default branch.
|
||||
1. Click the `+` button next to `master` again and select **New directory**.
|
||||
1. Next to the default branch, select the **{plus}** button.
|
||||
1. Select **New directory**.
|
||||
1. Name your directory `issue_templates` and commit to your default branch.
|
||||
|
||||
To check if this has worked correctly, [create a new issue](issues/managing_issues.md#create-a-new-issue)
|
||||
|
|
|
@ -65,6 +65,71 @@ can now create their own.
|
|||
|
||||
New compliance framework labels can be created and updated using GraphQL.
|
||||
|
||||
#### Compliance pipeline configuration **(ULTIMATE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3156) in GitLab 13.9.
|
||||
> - [Deployed behind a feature flag](../../feature_flags.md).
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/300324) in GitLab 13.11.
|
||||
> - Enabled on GitLab.com.
|
||||
> - Recommended for production use.
|
||||
|
||||
WARNING:
|
||||
This feature might not be available to you. Check the **version history** note above for details.
|
||||
|
||||
Group owners can use the compliance pipeline configuration to define compliance requirements
|
||||
such as scans or tests, and enforce them in individual projects.
|
||||
|
||||
The [custom compliance framework](#custom-compliance-frameworks) feature allows group owners to specify the location
|
||||
of a compliance pipeline configuration stored and managed in a dedicated project, distinct from a developer's project.
|
||||
|
||||
When you set up the compliance pipeline configuration field, use the
|
||||
`file@group/project` format. For example, you can configure
|
||||
`.compliance-gitlab-ci.yml@compliance-group/compliance-project`.
|
||||
This field is inherited by projects where the compliance framework label is applied. The result
|
||||
forces the project to run the compliance configurations.
|
||||
|
||||
When a project with a custom label executes a pipeline, it begins by evaluating the compliance pipeline configuration.
|
||||
The custom pipeline configuration can then execute any included individual project configuration.
|
||||
|
||||
The user running the pipeline in the project should at least have Reporter access to the compliance project.
|
||||
|
||||
Example `.compliance-gitlab-ci.yml`
|
||||
|
||||
```yaml
|
||||
stages: # Allows compliance team to control the ordering and interweaving of stages/jobs
|
||||
- pre-compliance
|
||||
- build
|
||||
- test
|
||||
- pre-deploy-compliance
|
||||
- deploy
|
||||
- post-compliance
|
||||
|
||||
variables: # can be overriden by a developer's local .gitlab-ci.yml
|
||||
FOO: sast
|
||||
|
||||
sast: # none of these attributes can be overriden by a developer's local .gitlab-ci.yml
|
||||
variables:
|
||||
FOO: sast
|
||||
stage: pre-compliance
|
||||
script:
|
||||
- echo "running $FOO"
|
||||
|
||||
sanity check:
|
||||
stage: pre-deploy-compliance
|
||||
script:
|
||||
- echo "running $FOO"
|
||||
|
||||
|
||||
audit trail:
|
||||
stage: post-compliance
|
||||
script:
|
||||
- echo "running $FOO"
|
||||
|
||||
include: # Execute individual project's configuration
|
||||
project: '$CI_PROJECT_PATH'
|
||||
file: '$CI_PROJECT_CONFIG_PATH'
|
||||
```
|
||||
|
||||
### Sharing and permissions
|
||||
|
||||
For your repository, you can set up features such as public access, repository features,
|
||||
|
|
|
@ -108,10 +108,21 @@ module Gitlab
|
|||
!%w[true 1].include?(ENV['FOSS_ONLY'].to_s)
|
||||
end
|
||||
|
||||
def self.jh?
|
||||
@is_jh ||=
|
||||
ee? &&
|
||||
root.join('jh').exist? &&
|
||||
!%w[true 1].include?(ENV['EE_ONLY'].to_s)
|
||||
end
|
||||
|
||||
def self.ee
|
||||
yield if ee?
|
||||
end
|
||||
|
||||
def self.jh
|
||||
yield if jh?
|
||||
end
|
||||
|
||||
def self.http_proxy_env?
|
||||
HTTP_PROXY_ENV_VARS.any? { |name| ENV[name] }
|
||||
end
|
||||
|
|
|
@ -6,6 +6,11 @@ module Gitlab
|
|||
::Gitlab.dev_or_test_env? ? 'https://customers.stg.gitlab.com' : 'https://customers.gitlab.com'
|
||||
end
|
||||
|
||||
SUBSCRIPTIONS_URL = ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url).freeze
|
||||
def self.subscriptions_url
|
||||
ENV.fetch('CUSTOMER_PORTAL_URL', default_subscriptions_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Gitlab::SubscriptionPortal.prepend_if_jh('JH::Gitlab::SubscriptionPortal')
|
||||
Gitlab::SubscriptionPortal::SUBSCRIPTIONS_URL = Gitlab::SubscriptionPortal.subscriptions_url.freeze
|
||||
|
|
|
@ -8804,9 +8804,24 @@ msgstr ""
|
|||
msgid "Corpus Management|Are you sure you want to delete the corpus?"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Actions"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Corpus are used in fuzz testing as mutation source to Improve future testing."
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Corpus name"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Fuzz testing corpus management"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Last updated"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Last used"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Latest Job:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8816,6 +8831,9 @@ msgstr ""
|
|||
msgid "CorpusManagement|Not Set"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Target"
|
||||
msgstr ""
|
||||
|
||||
msgid "CorpusManagement|Total Size: %{totalSize}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -18397,6 +18415,9 @@ msgstr ""
|
|||
msgid "Learn GitLab|Trial only"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn More"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -18475,12 +18496,18 @@ msgstr ""
|
|||
msgid "LearnGitLab|Create a workflow for your new workspace, and learn how GitLab features work together:"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Create an issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Create or import a repository"
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Create or import your first repository into your new project."
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Create/import issues (tickets) to collaborate on ideas and plan work."
|
||||
msgstr ""
|
||||
|
||||
msgid "LearnGitLab|Deploy"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26722,6 +26749,9 @@ msgstr ""
|
|||
msgid "Reset key"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset password"
|
||||
msgstr ""
|
||||
|
||||
msgid "Reset registration token"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29023,9 +29053,6 @@ msgstr ""
|
|||
msgid "Something went wrong on our end"
|
||||
msgstr ""
|
||||
|
||||
msgid "Something went wrong on our end while loading the code quality diff."
|
||||
msgstr ""
|
||||
|
||||
msgid "Something went wrong on our end."
|
||||
msgstr ""
|
||||
|
||||
|
@ -30997,7 +31024,7 @@ msgstr ""
|
|||
msgid "The merge request can now be merged."
|
||||
msgstr ""
|
||||
|
||||
msgid "The merge request has made changes to this file that affect the number of code quality violations in it."
|
||||
msgid "The merge request has been updated, and the number of code quality violations in this file has changed."
|
||||
msgstr ""
|
||||
|
||||
msgid "The metric must be one of %{metrics}."
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "1.188.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "29.3.0",
|
||||
"@gitlab/ui": "29.4.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-4",
|
||||
"@rails/ujs": "^6.0.3-4",
|
||||
|
@ -155,7 +155,7 @@
|
|||
"vuex": "^3.6.0",
|
||||
"web-vitals": "^0.2.4",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-bundle-analyzer": "^4.4.0",
|
||||
"webpack-bundle-analyzer": "^4.4.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-stats-plugin": "^0.3.1",
|
||||
"worker-loader": "^2.0.0",
|
||||
|
|
|
@ -97,6 +97,10 @@ function rspec_paralellized_job() {
|
|||
spec_folder_prefix="ee/"
|
||||
fi
|
||||
|
||||
if [[ "${test_tool}" =~ "-jh" ]]; then
|
||||
spec_folder_prefix="jh/"
|
||||
fi
|
||||
|
||||
export KNAPSACK_LOG_LEVEL="debug"
|
||||
export KNAPSACK_REPORT_PATH="knapsack/${report_name}_report.json"
|
||||
|
||||
|
|
|
@ -21,6 +21,30 @@ RSpec.describe Profiles::NotificationsController do
|
|||
expect(response).to render_template :show
|
||||
end
|
||||
|
||||
context 'when personal projects are present', :request_store do
|
||||
let!(:personal_project_1) { create(:project, namespace: user.namespace) }
|
||||
|
||||
context 'N+1 query check' do
|
||||
render_views
|
||||
|
||||
it 'does not have an N+1' do
|
||||
sign_in(user)
|
||||
|
||||
get :show
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new do
|
||||
get :show
|
||||
end
|
||||
|
||||
create_list(:project, 2, namespace: user.namespace)
|
||||
|
||||
expect do
|
||||
get :show
|
||||
end.not_to exceed_query_limit(control)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with groups that do not have notification preferences' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:subgroup) { create(:group, parent: group) }
|
||||
|
|
|
@ -56,7 +56,6 @@ module DeprecationToolkitEnv
|
|||
def self.allowed_kwarg_warning_paths
|
||||
%w[
|
||||
activerecord-6.0.3.4/lib/active_record/migration.rb
|
||||
devise-4.7.3/lib/devise/test/controller_helpers.rb
|
||||
activesupport-6.0.3.4/lib/active_support/cache.rb
|
||||
batch-loader-1.4.0/lib/batch_loader/graphql.rb
|
||||
carrierwave-1.3.1/lib/carrierwave/sanitized_file.rb
|
||||
|
|
|
@ -596,7 +596,7 @@ RSpec.describe 'Admin updates settings' do
|
|||
|
||||
context 'Nav bar' do
|
||||
it 'shows default help links in nav' do
|
||||
default_support_url = 'https://about.gitlab.com/getting-help/'
|
||||
default_support_url = "https://#{ApplicationHelper.promo_host}/getting-help/"
|
||||
|
||||
visit root_dashboard_path
|
||||
|
||||
|
|
|
@ -211,21 +211,22 @@ describe('CompareVersions', () => {
|
|||
});
|
||||
|
||||
describe('prev commit', () => {
|
||||
const { location } = window;
|
||||
|
||||
beforeAll(() => {
|
||||
delete window.location;
|
||||
window.location = { href: `${TEST_HOST}?commit_id=${mrCommit.id}` };
|
||||
global.jsdom.reconfigure({
|
||||
url: `${TEST_HOST}?commit_id=${mrCommit.id}`,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
global.jsdom.reconfigure({
|
||||
url: TEST_HOST,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
window.location = location;
|
||||
});
|
||||
|
||||
it('uses the correct href', () => {
|
||||
const link = getPrevCommitNavElement();
|
||||
|
||||
|
@ -253,21 +254,22 @@ describe('CompareVersions', () => {
|
|||
});
|
||||
|
||||
describe('next commit', () => {
|
||||
const { location } = window;
|
||||
|
||||
beforeAll(() => {
|
||||
delete window.location;
|
||||
window.location = { href: `${TEST_HOST}?commit_id=${mrCommit.id}` };
|
||||
global.jsdom.reconfigure({
|
||||
url: `${TEST_HOST}?commit_id=${mrCommit.id}`,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
global.jsdom.reconfigure({
|
||||
url: TEST_HOST,
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(wrapper.vm, 'moveToNeighboringCommit').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
window.location = location;
|
||||
});
|
||||
|
||||
it('uses the correct href', () => {
|
||||
const link = getNextCommitNavElement();
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import Vuex from 'vuex';
|
|||
import DiffRow from '~/diffs/components/diff_row.vue';
|
||||
import { mapParallel } from '~/diffs/components/diff_row_utils';
|
||||
import diffsModule from '~/diffs/store/modules';
|
||||
import { findInteropAttributes } from '../find_interop_attributes';
|
||||
import diffFileMockData from '../mock_data/diff_file';
|
||||
|
||||
describe('DiffRow', () => {
|
||||
|
@ -211,4 +212,20 @@ describe('DiffRow', () => {
|
|||
expect(coverage.classes('no-coverage')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('interoperability', () => {
|
||||
it.each`
|
||||
desc | line | inline | leftSide | rightSide
|
||||
${'with inline and new_line'} | ${{ left: { old_line: 3, new_line: 5, type: 'new' } }} | ${true} | ${{ type: 'new', line: '5', oldLine: '3', newLine: '5' }} | ${null}
|
||||
${'with inline and no new_line'} | ${{ left: { old_line: 3, type: 'old' } }} | ${true} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${null}
|
||||
${'with parallel and no right side'} | ${{ left: { old_line: 3, new_line: 5 } }} | ${false} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${null}
|
||||
${'with parallel and no left side'} | ${{ right: { old_line: 3, new_line: 5 } }} | ${false} | ${null} | ${{ type: 'new', line: '5', newLine: '5' }}
|
||||
${'with parallel and right side'} | ${{ left: { old_line: 3 }, right: { new_line: 5 } }} | ${false} | ${{ type: 'old', line: '3', oldLine: '3' }} | ${{ type: 'new', line: '5', newLine: '5' }}
|
||||
`('$desc, sets interop data attributes', ({ line, inline, leftSide, rightSide }) => {
|
||||
const wrapper = createWrapper({ props: { line, inline } });
|
||||
|
||||
expect(findInteropAttributes(wrapper, '[data-testid="left-side"]')).toEqual(leftSide);
|
||||
expect(findInteropAttributes(wrapper, '[data-testid="right-side"]')).toEqual(rightSide);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
|
|||
import { mapInline } from '~/diffs/components/diff_row_utils';
|
||||
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
|
||||
import { createStore } from '~/mr_notes/stores';
|
||||
import { findInteropAttributes } from '../find_interop_attributes';
|
||||
import discussionsMockData from '../mock_data/diff_discussions';
|
||||
import diffFileMockData from '../mock_data/diff_file';
|
||||
|
||||
|
@ -310,4 +311,16 @@ describe('InlineDiffTableRow', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('interoperability', () => {
|
||||
it.each`
|
||||
desc | line | expectation
|
||||
${'with type old'} | ${{ ...thisLine, type: 'old', old_line: 3, new_line: 5 }} | ${{ type: 'old', line: '3', oldLine: '3', newLine: '5' }}
|
||||
${'with type new'} | ${{ ...thisLine, type: 'new', old_line: 3, new_line: 5 }} | ${{ type: 'new', line: '5', oldLine: '3', newLine: '5' }}
|
||||
`('$desc, sets interop data attributes', ({ line, expectation }) => {
|
||||
createComponent({ line });
|
||||
|
||||
expect(findInteropAttributes(wrapper)).toEqual(expectation);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
|
|||
import { mapParallel } from '~/diffs/components/diff_row_utils';
|
||||
import ParallelDiffTableRow from '~/diffs/components/parallel_diff_table_row.vue';
|
||||
import { createStore } from '~/mr_notes/stores';
|
||||
import { findInteropAttributes } from '../find_interop_attributes';
|
||||
import discussionsMockData from '../mock_data/diff_discussions';
|
||||
import diffFileMockData from '../mock_data/diff_file';
|
||||
|
||||
|
@ -418,5 +419,27 @@ describe('ParallelDiffTableRow', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('interoperability', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('adds old side interoperability data attributes', () => {
|
||||
expect(findInteropAttributes(wrapper, '.line_content.left-side')).toEqual({
|
||||
type: 'old',
|
||||
line: thisLine.left.old_line.toString(),
|
||||
oldLine: thisLine.left.old_line.toString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('adds new side interoperability data attributes', () => {
|
||||
expect(findInteropAttributes(wrapper, '.line_content.right-side')).toEqual({
|
||||
type: 'new',
|
||||
line: thisLine.right.new_line.toString(),
|
||||
newLine: thisLine.right.new_line.toString(),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
export const findInteropAttributes = (parent, sel) => {
|
||||
const target = sel ? parent.find(sel) : parent;
|
||||
|
||||
if (!target.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const type = target.attributes('data-interop-type');
|
||||
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
line: target.attributes('data-interop-line'),
|
||||
oldLine: target.attributes('data-interop-old-line'),
|
||||
newLine: target.attributes('data-interop-new-line'),
|
||||
};
|
||||
};
|
|
@ -17,6 +17,9 @@ import {
|
|||
fetchDiffFilesBatch,
|
||||
fetchDiffFilesMeta,
|
||||
fetchCoverageFiles,
|
||||
clearEtagPoll,
|
||||
stopCodequalityPolling,
|
||||
fetchCodequality,
|
||||
assignDiscussionsToDiff,
|
||||
removeDiscussionsFromDiff,
|
||||
startRenderDiffsQueue,
|
||||
|
@ -98,6 +101,7 @@ describe('DiffsStoreActions', () => {
|
|||
const endpointMetadata = '/diffs/set/endpoint/metadata';
|
||||
const endpointBatch = '/diffs/set/endpoint/batch';
|
||||
const endpointCoverage = '/diffs/set/coverage_reports';
|
||||
const endpointCodequality = '/diffs/set/codequality_diff';
|
||||
const projectPath = '/root/project';
|
||||
const dismissEndpoint = '/-/user_callouts';
|
||||
const showSuggestPopover = false;
|
||||
|
@ -109,6 +113,7 @@ describe('DiffsStoreActions', () => {
|
|||
endpointBatch,
|
||||
endpointMetadata,
|
||||
endpointCoverage,
|
||||
endpointCodequality,
|
||||
projectPath,
|
||||
dismissEndpoint,
|
||||
showSuggestPopover,
|
||||
|
@ -118,6 +123,7 @@ describe('DiffsStoreActions', () => {
|
|||
endpointBatch: '',
|
||||
endpointMetadata: '',
|
||||
endpointCoverage: '',
|
||||
endpointCodequality: '',
|
||||
projectPath: '',
|
||||
dismissEndpoint: '',
|
||||
showSuggestPopover: true,
|
||||
|
@ -130,6 +136,7 @@ describe('DiffsStoreActions', () => {
|
|||
endpointMetadata,
|
||||
endpointBatch,
|
||||
endpointCoverage,
|
||||
endpointCodequality,
|
||||
projectPath,
|
||||
dismissEndpoint,
|
||||
showSuggestPopover,
|
||||
|
@ -299,6 +306,47 @@ describe('DiffsStoreActions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('fetchCodequality', () => {
|
||||
let mock;
|
||||
const endpointCodequality = '/fetch';
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
stopCodequalityPolling();
|
||||
clearEtagPoll();
|
||||
});
|
||||
|
||||
it('should commit SET_CODEQUALITY_DATA with received response', (done) => {
|
||||
const data = {
|
||||
files: { 'app.js': [{ line: 1, description: 'Unexpected alert.', severity: 'minor' }] },
|
||||
};
|
||||
|
||||
mock.onGet(endpointCodequality).reply(200, { data });
|
||||
|
||||
testAction(
|
||||
fetchCodequality,
|
||||
{},
|
||||
{ endpointCodequality },
|
||||
[{ type: types.SET_CODEQUALITY_DATA, payload: { data } }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
|
||||
it('should show flash on API error', (done) => {
|
||||
mock.onGet(endpointCodequality).reply(400);
|
||||
|
||||
testAction(fetchCodequality, {}, { endpointCodequality }, [], [], () => {
|
||||
expect(createFlash).toHaveBeenCalledTimes(1);
|
||||
expect(createFlash).toHaveBeenCalledWith(expect.stringMatching('Something went wrong'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setHighlightedRow', () => {
|
||||
it('should mark currently selected diff and set lineHash and fileHash of highlightedRow', () => {
|
||||
testAction(setHighlightedRow, 'ABC_123', {}, [
|
||||
|
|
|
@ -376,6 +376,26 @@ describe('Diffs Module Getters', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('fileCodequalityDiff', () => {
|
||||
beforeEach(() => {
|
||||
Object.assign(localState.codequalityDiff, {
|
||||
files: { 'app.js': [{ line: 1, description: 'Unexpected alert.', severity: 'minor' }] },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty array when no codequality data is available', () => {
|
||||
Object.assign(localState.codequalityDiff, {});
|
||||
|
||||
expect(getters.fileCodequalityDiff(localState)('test.js')).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns array when codequality data is available for given file', () => {
|
||||
expect(getters.fileCodequalityDiff(localState)('app.js')).toEqual([
|
||||
{ line: 1, description: 'Unexpected alert.', severity: 'minor' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('suggestionCommitMessage', () => {
|
||||
let rootState;
|
||||
|
||||
|
|
|
@ -115,6 +115,19 @@ describe('DiffsStoreMutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('SET_CODEQUALITY_DATA', () => {
|
||||
it('should set codequality data', () => {
|
||||
const state = { codequalityDiff: {} };
|
||||
const codequality = {
|
||||
files: { 'app.js': [{ line: 1, description: 'Unexpected alert.', severity: 'minor' }] },
|
||||
};
|
||||
|
||||
mutations[types.SET_CODEQUALITY_DATA](state, codequality);
|
||||
|
||||
expect(state.codequalityDiff).toEqual(codequality);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_DIFF_VIEW_TYPE', () => {
|
||||
it('should set diff view type properly', () => {
|
||||
const state = {};
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
import {
|
||||
getInteropInlineAttributes,
|
||||
getInteropNewSideAttributes,
|
||||
getInteropOldSideAttributes,
|
||||
ATTR_TYPE,
|
||||
ATTR_LINE,
|
||||
ATTR_NEW_LINE,
|
||||
ATTR_OLD_LINE,
|
||||
} from '~/diffs/utils/interoperability';
|
||||
|
||||
describe('~/diffs/utils/interoperability', () => {
|
||||
describe('getInteropInlineAttributes', () => {
|
||||
it.each([
|
||||
['with null input', { input: null, output: null }],
|
||||
[
|
||||
'with type=old input',
|
||||
{
|
||||
input: { type: 'old', old_line: 3, new_line: 5 },
|
||||
output: { [ATTR_TYPE]: 'old', [ATTR_LINE]: 3, [ATTR_OLD_LINE]: 3, [ATTR_NEW_LINE]: 5 },
|
||||
},
|
||||
],
|
||||
[
|
||||
'with type=old-nonewline input',
|
||||
{
|
||||
input: { type: 'old-nonewline', old_line: 3, new_line: 5 },
|
||||
output: { [ATTR_TYPE]: 'old', [ATTR_LINE]: 3, [ATTR_OLD_LINE]: 3, [ATTR_NEW_LINE]: 5 },
|
||||
},
|
||||
],
|
||||
[
|
||||
'with type=new input',
|
||||
{
|
||||
input: { type: 'new', old_line: 3, new_line: 5 },
|
||||
output: { [ATTR_TYPE]: 'new', [ATTR_LINE]: 5, [ATTR_OLD_LINE]: 3, [ATTR_NEW_LINE]: 5 },
|
||||
},
|
||||
],
|
||||
[
|
||||
'with type=bogus input',
|
||||
{
|
||||
input: { type: 'bogus', old_line: 3, new_line: 5 },
|
||||
output: { [ATTR_TYPE]: 'new', [ATTR_LINE]: 5, [ATTR_OLD_LINE]: 3, [ATTR_NEW_LINE]: 5 },
|
||||
},
|
||||
],
|
||||
])('%s', (desc, { input, output }) => {
|
||||
expect(getInteropInlineAttributes(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getInteropOldSideAttributes', () => {
|
||||
it.each`
|
||||
input | output
|
||||
${null} | ${null}
|
||||
${{ old_line: 2 }} | ${{ [ATTR_TYPE]: 'old', [ATTR_LINE]: 2, [ATTR_OLD_LINE]: 2 }}
|
||||
`('with input=$input', ({ input, output }) => {
|
||||
expect(getInteropOldSideAttributes(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getInteropNewSideAttributes', () => {
|
||||
it.each`
|
||||
input | output
|
||||
${null} | ${null}
|
||||
${{ new_line: 2 }} | ${{ [ATTR_TYPE]: 'new', [ATTR_LINE]: 2, [ATTR_NEW_LINE]: 2 }}
|
||||
`('with input=$input', ({ input, output }) => {
|
||||
expect(getInteropNewSideAttributes(input)).toEqual(output);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -25,6 +25,10 @@ RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)'
|
|||
end
|
||||
|
||||
before do
|
||||
# Create a user that matches the project.commit author
|
||||
# This is so that the "author" information will be populated
|
||||
create(:user, email: project.commit.author_email, name: project.commit.author_name)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
@ -33,17 +37,21 @@ RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)'
|
|||
end
|
||||
|
||||
it 'merge_request_diffs/with_commit.json' do
|
||||
# Create a user that matches the project.commit author
|
||||
# This is so that the "author" information will be populated
|
||||
create(:user, email: project.commit.author_email, name: project.commit.author_name)
|
||||
|
||||
render_merge_request(merge_request, commit_id: project.commit.sha)
|
||||
end
|
||||
|
||||
it 'merge_request_diffs/diffs_metadata.json' do
|
||||
render_merge_request(merge_request, action: :diffs_metadata)
|
||||
end
|
||||
|
||||
it 'merge_request_diffs/diffs_batch.json' do
|
||||
render_merge_request(merge_request, action: :diffs_batch, page: 1, per_page: 30)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_merge_request(merge_request, view: 'inline', **extra_params)
|
||||
get :show, params: {
|
||||
def render_merge_request(merge_request, action: :show, view: 'inline', **extra_params)
|
||||
get action, params: {
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project,
|
||||
id: merge_request.to_param,
|
||||
|
|
|
@ -29,21 +29,21 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
|
|||
class="gl-text-gray-500 gl-mb-2"
|
||||
data-testid="completion-percentage"
|
||||
>
|
||||
25% completed
|
||||
22% completed
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="progress"
|
||||
max="8"
|
||||
max="9"
|
||||
value="2"
|
||||
>
|
||||
<div
|
||||
aria-valuemax="8"
|
||||
aria-valuemax="9"
|
||||
aria-valuemin="0"
|
||||
aria-valuenow="2"
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
style="width: 25%;"
|
||||
style="width: 22.22222222222222%;"
|
||||
>
|
||||
<!---->
|
||||
</div>
|
||||
|
@ -234,6 +234,20 @@ exports[`Learn GitLab Design A renders correctly 1`] = `
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
<span>
|
||||
<a
|
||||
class="gl-link"
|
||||
href="http://example.com/"
|
||||
>
|
||||
Create an issue
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
<div
|
||||
class="gl-mb-4"
|
||||
>
|
||||
|
|
|
@ -29,21 +29,21 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-gray-500 gl-mb-2"
|
||||
data-testid="completion-percentage"
|
||||
>
|
||||
25% completed
|
||||
22% completed
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="progress"
|
||||
max="8"
|
||||
max="9"
|
||||
value="2"
|
||||
>
|
||||
<div
|
||||
aria-valuemax="8"
|
||||
aria-valuemax="9"
|
||||
aria-valuemin="0"
|
||||
aria-valuenow="2"
|
||||
class="progress-bar"
|
||||
role="progressbar"
|
||||
style="width: 25%;"
|
||||
style="width: 22.22222222222222%;"
|
||||
>
|
||||
<!---->
|
||||
</div>
|
||||
|
@ -94,6 +94,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Invite your colleagues"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
|
@ -151,6 +152,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Create or import a repository"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
|
@ -200,6 +202,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Set-up CI/CD"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
|
@ -249,6 +252,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Try GitLab Ultimate for free"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
|
@ -303,6 +307,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Add code owners"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
|
@ -357,6 +362,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Enable require merge approvals"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
|
@ -422,6 +428,57 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Create an issue"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
<h6>
|
||||
Create an issue
|
||||
</h6>
|
||||
|
||||
<p
|
||||
class="gl-font-sm gl-text-gray-700"
|
||||
>
|
||||
Create/import issues (tickets) to collaborate on ideas and plan work.
|
||||
</p>
|
||||
|
||||
<a
|
||||
class="gl-link"
|
||||
href="http://example.com/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Create an issue
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="col gl-mb-6"
|
||||
>
|
||||
<div
|
||||
class="gl-card gl-pt-0"
|
||||
>
|
||||
<!---->
|
||||
|
||||
<div
|
||||
class="gl-card-body"
|
||||
>
|
||||
<div
|
||||
class="gl-text-right gl-h-5"
|
||||
>
|
||||
<!---->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Submit a merge request (MR)"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
|
@ -487,6 +544,7 @@ exports[`Learn GitLab Design B renders correctly 1`] = `
|
|||
class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content"
|
||||
>
|
||||
<img
|
||||
alt="Run a Security scan using CI/CD"
|
||||
src="http://example.com/images/illustration.svg"
|
||||
/>
|
||||
|
||||
|
|
|
@ -31,6 +31,10 @@ exports[`Learn GitLab Section Card renders correctly 1`] = `
|
|||
action="userAdded"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="issueCreated"
|
||||
value="[object Object]"
|
||||
/>
|
||||
<learn-gitlab-section-link-stub
|
||||
action="gitWrite"
|
||||
value="[object Object]"
|
||||
|
|
|
@ -26,13 +26,13 @@ describe('Learn GitLab Design A', () => {
|
|||
it('renders the progress percentage', () => {
|
||||
const text = wrapper.find('[data-testid="completion-percentage"]').text();
|
||||
|
||||
expect(text).toEqual('25% completed');
|
||||
expect(text).toBe('22% completed');
|
||||
});
|
||||
|
||||
it('renders the progress bar with correct values', () => {
|
||||
const progressBar = wrapper.find(GlProgressBar);
|
||||
const progressBar = wrapper.findComponent(GlProgressBar);
|
||||
|
||||
expect(progressBar.attributes('value')).toBe('2');
|
||||
expect(progressBar.attributes('max')).toBe('8');
|
||||
expect(progressBar.attributes('max')).toBe('9');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,13 +26,13 @@ describe('Learn GitLab Design B', () => {
|
|||
it('renders the progress percentage', () => {
|
||||
const text = wrapper.find('[data-testid="completion-percentage"]').text();
|
||||
|
||||
expect(text).toEqual('25% completed');
|
||||
expect(text).toBe('22% completed');
|
||||
});
|
||||
|
||||
it('renders the progress bar with correct values', () => {
|
||||
const progressBar = wrapper.find(GlProgressBar);
|
||||
const progressBar = wrapper.findComponent(GlProgressBar);
|
||||
|
||||
expect(progressBar.attributes('value')).toBe('2');
|
||||
expect(progressBar.attributes('max')).toBe('8');
|
||||
expect(progressBar.attributes('max')).toBe('9');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -39,4 +39,9 @@ export const testActions = {
|
|||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
},
|
||||
issueCreated: {
|
||||
url: 'http://example.com/',
|
||||
completed: false,
|
||||
svg: 'http://example.com/images/illustration.svg',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,9 +2,15 @@ import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
|
|||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
|
||||
import { IID_FAILURE, LAYER_VIEW, STAGE_VIEW } from '~/pipelines/components/graph/constants';
|
||||
import {
|
||||
IID_FAILURE,
|
||||
LAYER_VIEW,
|
||||
STAGE_VIEW,
|
||||
VIEW_TYPE_KEY,
|
||||
} from '~/pipelines/components/graph/constants';
|
||||
import PipelineGraph from '~/pipelines/components/graph/graph_component.vue';
|
||||
import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
|
||||
import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue';
|
||||
|
@ -21,6 +27,7 @@ const defaultProvide = {
|
|||
|
||||
describe('Pipeline graph wrapper', () => {
|
||||
Vue.use(VueApollo);
|
||||
useLocalStorageSpy();
|
||||
|
||||
let wrapper;
|
||||
const getAlert = () => wrapper.find(GlAlert);
|
||||
|
@ -216,7 +223,7 @@ describe('Pipeline graph wrapper', () => {
|
|||
});
|
||||
|
||||
describe('view dropdown', () => {
|
||||
describe('when feature flag is off', () => {
|
||||
describe('when pipelineGraphLayersView feature flag is off', () => {
|
||||
beforeEach(async () => {
|
||||
createComponentWithApollo();
|
||||
jest.runOnlyPendingTimers();
|
||||
|
@ -228,7 +235,7 @@ describe('Pipeline graph wrapper', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('when feature flag is on', () => {
|
||||
describe('when pipelineGraphLayersView feature flag is on', () => {
|
||||
let layersFn;
|
||||
beforeEach(async () => {
|
||||
layersFn = jest.spyOn(parsingUtils, 'listByLayers');
|
||||
|
@ -245,7 +252,7 @@ describe('Pipeline graph wrapper', () => {
|
|||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('appears', () => {
|
||||
it('appears when pipeline uses needs', () => {
|
||||
expect(getViewSelector().exists()).toBe(true);
|
||||
});
|
||||
|
||||
|
@ -259,6 +266,11 @@ describe('Pipeline graph wrapper', () => {
|
|||
expect(getStageColumnTitle().text()).toBe('');
|
||||
});
|
||||
|
||||
it('saves the view type to local storage', async () => {
|
||||
await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
|
||||
expect(localStorage.setItem.mock.calls).toEqual([[VIEW_TYPE_KEY, LAYER_VIEW]]);
|
||||
});
|
||||
|
||||
it('calls listByLayers only once no matter how many times view is switched', async () => {
|
||||
expect(layersFn).not.toHaveBeenCalled();
|
||||
await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
|
||||
|
@ -269,5 +281,53 @@ describe('Pipeline graph wrapper', () => {
|
|||
expect(layersFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when feature flag is on and local storage is set', () => {
|
||||
beforeEach(async () => {
|
||||
localStorage.setItem(VIEW_TYPE_KEY, LAYER_VIEW);
|
||||
|
||||
createComponentWithApollo({
|
||||
provide: {
|
||||
glFeatures: {
|
||||
pipelineGraphLayersView: true,
|
||||
},
|
||||
},
|
||||
mountFn: mount,
|
||||
});
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('reads the view type from localStorage when available', () => {
|
||||
expect(wrapper.find('[data-testid="pipeline-view-selector"] code').text()).toContain(
|
||||
'needs:',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when feature flag is on but pipeline does not use needs', () => {
|
||||
beforeEach(async () => {
|
||||
const nonNeedsResponse = { ...mockPipelineResponse };
|
||||
nonNeedsResponse.data.project.pipeline.usesNeeds = false;
|
||||
|
||||
createComponentWithApollo({
|
||||
provide: {
|
||||
glFeatures: {
|
||||
pipelineGraphLayersView: true,
|
||||
},
|
||||
},
|
||||
mountFn: mount,
|
||||
getPipelineDetailsHandler: jest.fn().mockResolvedValue(nonNeedsResponse),
|
||||
});
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('does not appear when pipeline does not use needs', () => {
|
||||
expect(getViewSelector().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ export const mockPipelineResponse = {
|
|||
__typename: 'Pipeline',
|
||||
id: 163,
|
||||
iid: '22',
|
||||
usesNeeds: true,
|
||||
downstream: null,
|
||||
upstream: null,
|
||||
stages: {
|
||||
|
@ -569,6 +570,7 @@ export const wrappedPipelineReturn = {
|
|||
__typename: 'Pipeline',
|
||||
id: 'gid://gitlab/Ci::Pipeline/175',
|
||||
iid: '38',
|
||||
usesNeeds: true,
|
||||
downstream: {
|
||||
__typename: 'PipelineConnection',
|
||||
nodes: [],
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue