Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-09 09:09:53 +00:00
parent 97576e3dfd
commit 1613500bf7
124 changed files with 970 additions and 916 deletions

1
.gitignore vendored
View File

@ -45,7 +45,6 @@ eslint-report.html
/config/redis.trace_chunks.yml
/config/unicorn.rb
/config/puma.rb
/config/puma_actioncable.rb
/config/secrets.yml
/config/sidekiq.yml
/config/registry.key

View File

@ -393,11 +393,10 @@ db:migrate-from-previous-major-version:
- sed -i -e "s/gem 'mimemagic', '~> 0.3.2'/gem 'ruby-magic', '~> 0.4.0'/" Gemfile
- run_timed_command "gem install bundler:1.17.3"
- run_timed_command "bundle update google-protobuf nokogiri grpc mimemagic bootsnap"
- run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS}"
- cp config/gitlab.yml.example config/gitlab.yml
- SETUP_DB=false USE_BUNDLE_INSTALL=true bash scripts/prepare_build.sh
- run_timed_command "bundle exec rake db:drop db:create db:structure:load db:migrate db:seed_fu"
- git checkout -f $CI_COMMIT_SHA
- run_timed_command "bundle install ${BUNDLE_INSTALL_FLAGS}"
- SETUP_DB=false USE_BUNDLE_INSTALL=true bash scripts/prepare_build.sh
- run_timed_command "bundle exec rake db:migrate"
db:rollback:

View File

@ -1 +1 @@
ed9300c34897316f98e93d909b964828cb4e5879
fef798978197809e268e5395838b4f4eeb4288e9

View File

@ -1,6 +1,16 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `
<p>${s__('AdminUsers|Reactivating a user will:')}</p>
<ul>
<li>${s__('AdminUsers|Restore user access to the account, including web, Git and API.')}</li>
</ul>
<p>${s__('AdminUsers|You can always deactivate their account again if needed.')}</p>
`;
export default {
components: {
@ -25,9 +35,14 @@ export default {
title: sprintf(s__('AdminUsers|Activate user %{username}?'), {
username: this.username,
}),
message: s__('AdminUsers|You can always deactivate their account again if needed.'),
okVariant: 'confirm',
okTitle: s__('AdminUsers|Activate'),
messageHtml,
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.activate,
attributes: [{ variant: 'confirm' }],
},
}),
};
},
@ -36,9 +51,7 @@ export default {
</script>
<template>
<div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<gl-dropdown-item>
<slot></slot>
</gl-dropdown-item>
</div>
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -1,21 +1,60 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `
<p>${s__('AdminUsers|Approved users can:')}</p>
<ul>
<li>${s__('AdminUsers|Log in')}</li>
<li>${s__('AdminUsers|Access Git repositories')}</li>
<li>${s__('AdminUsers|Access the API')}</li>
<li>${s__('AdminUsers|Be added to groups and projects')}</li>
</ul>
`;
export default {
components: {
GlDropdownItem,
},
props: {
username: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
},
computed: {
attributes() {
return {
'data-path': this.path,
'data-method': 'put',
'data-modal-attributes': JSON.stringify({
title: sprintf(s__('AdminUsers|Approve user %{username}?'), {
username: this.username,
}),
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.approve,
attributes: [{ variant: 'confirm', 'data-qa-selector': 'approve_user_confirm_button' }],
},
messageHtml,
}),
'data-qa-selector': 'approve_user_button',
};
},
},
};
</script>
<template>
<gl-dropdown-item :href="path" data-method="put">
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...attributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -1,7 +1,8 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { sprintf, s__ } from '~/locale';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `
@ -46,8 +47,13 @@ export default {
title: sprintf(s__('AdminUsers|Ban user %{username}?'), {
username: this.username,
}),
okVariant: 'warning',
okTitle: s__('AdminUsers|Ban user'),
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.ban,
attributes: [{ variant: 'confirm' }],
},
messageHtml,
}),
};
@ -57,9 +63,7 @@ export default {
</script>
<template>
<div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<gl-dropdown-item>
<slot></slot>
</gl-dropdown-item>
</div>
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `
@ -11,6 +12,7 @@ const messageHtml = `
<li>${s__('AdminUsers|Personal projects will be left')}</li>
<li>${s__('AdminUsers|Owned groups will be left')}</li>
</ul>
<p>${s__('AdminUsers|You can always unblock their account, their data will remain intact.')}</p>
`;
export default {
@ -34,8 +36,13 @@ export default {
'data-method': 'put',
'data-modal-attributes': JSON.stringify({
title: sprintf(s__('AdminUsers|Block user %{username}?'), { username: this.username }),
okVariant: 'confirm',
okTitle: s__('AdminUsers|Block'),
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.block,
attributes: [{ variant: 'confirm' }],
},
messageHtml,
}),
};
@ -45,9 +52,7 @@ export default {
</script>
<template>
<div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<gl-dropdown-item>
<slot></slot>
</gl-dropdown-item>
</div>
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `
@ -16,6 +17,9 @@ const messageHtml = `
)}</li>
<li>${s__('AdminUsers|Personal projects, group and user history will be left intact')}</li>
</ul>
<p>${s__(
'AdminUsers|You can always re-activate their account, their data will remain intact.',
)}</p>
`;
export default {
@ -41,8 +45,13 @@ export default {
title: sprintf(s__('AdminUsers|Deactivate user %{username}?'), {
username: this.username,
}),
okVariant: 'confirm',
okTitle: s__('AdminUsers|Deactivate'),
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.deactivate,
attributes: [{ variant: 'confirm' }],
},
messageHtml,
}),
};
@ -52,9 +61,7 @@ export default {
</script>
<template>
<div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<gl-dropdown-item>
<slot></slot>
</gl-dropdown-item>
</div>
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -1,21 +1,70 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `
<p>${s__('AdminUsers|Rejected users:')}</p>
<ul>
<li>${s__('AdminUsers|Cannot sign in or access instance information')}</li>
<li>${s__('AdminUsers|Will be deleted')}</li>
</ul>
<p>${sprintf(
s__(
'AdminUsers|For more information, please refer to the %{link_start}user account deletion documentation.%{link_end}',
),
{
link_start: `<a href="${helpPagePath('user/profile/account/delete_account', {
anchor: 'associated-records',
})}" target="_blank">`,
link_end: '</a>',
},
false,
)}</p>
`;
export default {
components: {
GlDropdownItem,
},
props: {
username: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
},
computed: {
modalAttributes() {
return {
'data-path': this.path,
'data-method': 'delete',
'data-modal-attributes': JSON.stringify({
title: sprintf(s__('AdminUsers|Reject user %{username}?'), {
username: this.username,
}),
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.reject,
attributes: [{ variant: 'danger' }],
},
messageHtml,
}),
};
},
},
};
</script>
<template>
<gl-dropdown-item :href="path" data-method="delete">
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = `<p>${s__(
@ -30,8 +31,13 @@ export default {
title: sprintf(s__('AdminUsers|Unban user %{username}?'), {
username: this.username,
}),
okVariant: 'info',
okTitle: s__('AdminUsers|Unban user'),
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.unban,
attributes: [{ variant: 'confirm' }],
},
messageHtml,
}),
};
@ -41,9 +47,7 @@ export default {
</script>
<template>
<div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<gl-dropdown-item>
<slot></slot>
</gl-dropdown-item>
</div>
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
export default {
components: {
@ -24,8 +25,13 @@ export default {
'data-modal-attributes': JSON.stringify({
title: sprintf(s__('AdminUsers|Unblock user %{username}?'), { username: this.username }),
message: s__('AdminUsers|You can always block their account again if needed.'),
okVariant: 'confirm',
okTitle: s__('AdminUsers|Unblock'),
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.unblock,
attributes: [{ variant: 'confirm' }],
},
}),
};
},
@ -34,9 +40,7 @@ export default {
</script>
<template>
<div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<gl-dropdown-item>
<slot></slot>
</gl-dropdown-item>
</div>
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { sprintf, s__, __ } from '~/locale';
import { I18N_USER_ACTIONS } from '../../constants';
export default {
components: {
@ -24,8 +25,13 @@ export default {
'data-modal-attributes': JSON.stringify({
title: sprintf(s__('AdminUsers|Unlock user %{username}?'), { username: this.username }),
message: __('Are you sure?'),
okVariant: 'confirm',
okTitle: s__('AdminUsers|Unlock'),
actionCancel: {
text: __('Cancel'),
},
actionPrimary: {
text: I18N_USER_ACTIONS.unlock,
attributes: [{ variant: 'confirm' }],
},
}),
};
},
@ -34,9 +40,7 @@ export default {
</script>
<template>
<div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<gl-dropdown-item>
<slot></slot>
</gl-dropdown-item>
</div>
<gl-dropdown-item button-class="js-confirm-modal-button" v-bind="{ ...modalAttributes }">
<slot></slot>
</gl-dropdown-item>
</template>

View File

@ -4,8 +4,7 @@ import { mapGetters, mapActions, mapState } from 'vuex';
import ListLabel from '~/boards/models/label';
import { TYPE_ITERATION, TYPE_MILESTONE, TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { getParameterByName } from '~/lib/utils/common_utils';
import { visitUrl } from '~/lib/utils/url_utility';
import { getParameterByName, visitUrl } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import { fullLabelId, fullBoardId } from '../boards_util';
import { formType } from '../constants';

View File

@ -1,6 +1,7 @@
import { TRACKING_CONTEXT_SCHEMA } from '~/experimentation/constants';
import { getExperimentData } from '~/experimentation/utils';
import { setCookie, getCookie, getParameterByName } from '~/lib/utils/common_utils';
import { setCookie, getCookie } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import Tracking from '~/tracking';
import { EXPERIMENT_NAME } from './constants';

View File

@ -1,6 +1,6 @@
<script>
import { GlButton, GlEmptyState, GlLoadingIcon, GlModal, GlLink } from '@gitlab/ui';
import { getParameterByName } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import eventHub from '~/pipelines/event_hub';
import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin';

View File

@ -44,7 +44,13 @@ const ExtendedImage = Image.extend({
},
];
},
}).configure({ inline: true });
});
export const tiptapExtension = ExtendedImage;
export const serializer = defaultMarkdownSerializer.nodes.image;
const serializer = defaultMarkdownSerializer.nodes.image;
export const configure = ({ renderMarkdown, uploadsPath }) => {
return {
tiptapExtension: ExtendedImage.configure({ inline: true, renderMarkdown, uploadsPath }),
serializer,
};
};

View File

@ -26,29 +26,6 @@ import { ContentEditor } from './content_editor';
import createMarkdownSerializer from './markdown_serializer';
import trackInputRulesAndShortcuts from './track_input_rules_and_shortcuts';
const builtInContentEditorExtensions = [
Blockquote,
Bold,
BulletList,
Code,
CodeBlockHighlight,
Document,
Dropcursor,
Gapcursor,
HardBreak,
Heading,
History,
HorizontalRule,
Image,
Italic,
Link,
ListItem,
OrderedList,
Paragraph,
Strike,
Text,
];
const collectTiptapExtensions = (extensions = []) =>
extensions.map(({ tiptapExtension }) => tiptapExtension);
@ -63,11 +40,39 @@ const createTiptapEditor = ({ extensions = [], ...options } = {}) =>
...options,
});
export const createContentEditor = ({ renderMarkdown, extensions = [], tiptapOptions } = {}) => {
export const createContentEditor = ({
renderMarkdown,
uploadsPath,
extensions = [],
tiptapOptions,
} = {}) => {
if (!isFunction(renderMarkdown)) {
throw new Error(PROVIDE_SERIALIZER_OR_RENDERER_ERROR);
}
const builtInContentEditorExtensions = [
Blockquote,
Bold,
BulletList,
Code,
CodeBlockHighlight,
Document,
Dropcursor,
Gapcursor,
HardBreak,
Heading,
History,
HorizontalRule,
Image.configure({ uploadsPath, renderMarkdown }),
Italic,
Link,
ListItem,
OrderedList,
Paragraph,
Strike,
Text,
];
const allExtensions = [...builtInContentEditorExtensions, ...extensions];
const tiptapExtensions = collectTiptapExtensions(allExtensions).map(trackInputRulesAndShortcuts);
const tiptapEditor = createTiptapEditor({ extensions: tiptapExtensions, ...tiptapOptions });

View File

@ -1,4 +1,5 @@
import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import { __, n__, sprintf } from '~/locale';
import { DIFF_COMPARE_BASE_VERSION_INDEX, DIFF_COMPARE_HEAD_VERSION_INDEX } from '../constants';

View File

@ -4,8 +4,8 @@
import { isEqual, isFunction, omitBy } from 'lodash';
import Visibility from 'visibilityjs';
import createFlash from '~/flash';
import { getParameterByName } from '../../lib/utils/common_utils';
import Poll from '../../lib/utils/poll';
import { getParameterByName } from '../../lib/utils/url_utility';
import { s__ } from '../../locale';
import tabs from '../../vue_shared/components/navigation_tabs.vue';
import tablePagination from '../../vue_shared/components/pagination/table_pagination.vue';

View File

@ -3,11 +3,8 @@ import { GlAlert, GlBadge, GlButton, GlModalDirective, GlSprintf } from '@gitlab
import { isEmpty } from 'lodash';
import { mapState, mapActions } from 'vuex';
import {
buildUrlWithCurrentLocation,
getParameterByName,
historyPushState,
} from '~/lib/utils/common_utils';
import { buildUrlWithCurrentLocation, historyPushState } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import ConfigureFeatureFlagsModal from './configure_feature_flags_modal.vue';
import EmptyState from './empty_state.vue';

View File

@ -2,7 +2,6 @@ import { last } from 'lodash';
import recentSearchesStorageKeys from 'ee_else_ce/filtered_search/recent_searches_storage_keys';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import createFlash from '~/flash';
import { getParameterByName } from '~/lib/utils/common_utils';
import {
ENTER_KEY_CODE,
BACKSPACE_KEY_CODE,
@ -12,7 +11,7 @@ import {
} from '~/lib/utils/keycodes';
import { __ } from '~/locale';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
import { visitUrl, getUrlParamsArray } from '../lib/utils/url_utility';
import { visitUrl, getUrlParamsArray, getParameterByName } from '../lib/utils/url_utility';
import FilteredSearchContainer from './container';
import DropdownUtils from './dropdown_utils';
import eventHub from './event_hub';

View File

@ -1,9 +1,8 @@
<script>
import { GlLoadingIcon, GlModal } from '@gitlab/ui';
import createFlash from '~/flash';
import { getParameterByName } from '~/lib/utils/common_utils';
import { HIDDEN_CLASS } from '~/lib/utils/constants';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { mergeUrlParams, getParameterByName } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale';
import { COMMON_STR, CONTENT_LIST_CLASS } from '../constants';

View File

@ -1,6 +1,6 @@
<script>
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
import { getParameterByName } from '../../lib/utils/common_utils';
import { getParameterByName } from '../../lib/utils/url_utility';
import eventHub from '../event_hub';
export default {

View File

@ -1,6 +1,7 @@
import $ from 'jquery';
import FilterableList from '~/filterable_list';
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
import { normalizeHeaders } from '../lib/utils/common_utils';
import { getParameterByName } from '../lib/utils/url_utility';
import eventHub from './event_hub';
export default class GroupFilterableList extends FilterableList {
@ -45,7 +46,7 @@ export default class GroupFilterableList extends FilterableList {
onFilterInput() {
const queryData = {};
const $form = $(this.form);
const archivedParam = getParameterByName('archived', window.location.href);
const archivedParam = getParameterByName('archived');
const filterGroupsParam = $form.find(`[name="${this.filterInputField}"]`).val();
if (filterGroupsParam) {

View File

@ -415,7 +415,11 @@ export default {
const parentPath = getPathParent(this.file.path);
const path = `${parentPath ? `${parentPath}/` : ''}${file.name}`;
return this.addTempImage({ name: path, rawPath: content }).then(({ name: fileName }) => {
return this.addTempImage({
name: path,
rawPath: URL.createObjectURL(file),
content: atob(content.split('base64,')[1]),
}).then(({ name: fileName }) => {
this.editor.replaceSelectedText(`![${fileName}](./${fileName})`);
});
});

View File

@ -76,11 +76,11 @@ export const createTempEntry = (
return file;
};
export const addTempImage = ({ dispatch, getters }, { name, rawPath = '' }) =>
export const addTempImage = ({ dispatch, getters }, { name, rawPath = '', content = '' }) =>
dispatch('createTempEntry', {
name: getters.getAvailableFileName(name),
type: 'blob',
content: rawPath.split('base64,')[1],
content,
rawPath,
openFile: false,
makeFileActive: false,

View File

@ -252,10 +252,10 @@ export function extractMarkdownImagesFromEntries(mdFile, entries) {
.trim();
const imageContent = entries[imagePath]?.content || entries[imagePath]?.raw;
const imageRawPath = entries[imagePath]?.rawPath;
if (!isAbsolute(path) && imageContent) {
const ext = path.includes('.') ? path.split('.').pop().trim() : 'jpeg';
const src = `data:image/${ext};base64,${imageContent}`;
const src = imageRawPath;
i += 1;
const key = `{{${prefix}${i}}}`;
images[key] = { alt, src, title };

View File

@ -8,9 +8,9 @@ import {
import { toNumber, omit } from 'lodash';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { scrollToElement, historyPushState, getParameterByName } from '~/lib/utils/common_utils';
import { scrollToElement, historyPushState } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { setUrlParams, urlParamsToObject } from '~/lib/utils/url_utility';
import { setUrlParams, urlParamsToObject, getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import initManualOrdering from '~/manual_ordering';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
@ -78,10 +78,7 @@ export default {
isBulkEditing: false,
issuables: [],
loading: false,
page:
getParameterByName('page', window.location.href) !== null
? toNumber(getParameterByName('page'))
: 1,
page: getParameterByName('page') !== null ? toNumber(getParameterByName('page')) : 1,
selection: {},
totalItems: 0,
};

View File

@ -50,8 +50,8 @@ import {
getSortOptions,
} from '~/issues_list/utils';
import axios from '~/lib/utils/axios_utils';
import { getParameterByName } from '~/lib/utils/common_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import {
DEFAULT_NONE_ANY,
OPERATOR_IS_ONLY,

View File

@ -254,21 +254,6 @@ export const debounceByAnimationFrame = (fn) => {
};
};
/**
this will take in the `name` of the param you want to parse in the url
if the name does not exist this function will return `null`
otherwise it will return the value of the param key provided
*/
export const getParameterByName = (name, urlToParse) => {
const url = urlToParse || window.location.href;
const parsedName = name.replace(/[[\]]/g, '\\$&');
const regex = new RegExp(`[?&]${parsedName}(=([^&#]*)|&|#|$)`);
const results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
};
const handleSelectedRange = (range, restrictToNode) => {
// Make sure this range is within the restricting container
if (restrictToNode && !range.intersectsNode(restrictToNode)) return null;

View File

@ -107,6 +107,25 @@ export function getParameterValues(sParam, url = window.location) {
}, []);
}
/**
* This function accepts the `name` of the param to parse in the url
* if the name does not exist this function will return `null`
* otherwise it will return the value of the param key provided
*
* @param {String} name
* @param {String?} urlToParse
* @returns value of the parameter as string
*/
export const getParameterByName = (name, urlToParse) => {
const url = urlToParse || window.location.href;
const parsedName = name.replace(/[[\]]/g, '\\$&');
const regex = new RegExp(`[?&]${parsedName}(=([^&#]*)|&|#|$)`);
const results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeUrlParameter(results[2]);
};
/**
* Merges a URL to a set of params replacing value for
* those already present.

View File

@ -1,9 +1,8 @@
<script>
import { GlFilteredSearchToken } from '@gitlab/ui';
import { mapState } from 'vuex';
import { getParameterByName } from '~/lib/utils/common_utils';
// eslint-disable-next-line import/no-deprecated
import { setUrlParams, urlParamsToObject } from '~/lib/utils/url_utility';
import { getParameterByName, setUrlParams, urlParamsToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import {
SEARCH_TOKEN_TYPE,

View File

@ -1,6 +1,6 @@
import { isUndefined } from 'lodash';
import { getParameterByName, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { getParameterByName, setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import {
FIELDS,

View File

@ -1,6 +1,6 @@
import createFlash from '~/flash';
import { getParameterByName } from '~/lib/utils/common_utils';
import { initRails } from '~/lib/utils/rails_ujs';
import { getParameterByName } from '~/lib/utils/url_utility';
import { __, sprintf } from '~/locale';
const PARAMETER_NAME = 'leave';

View File

@ -1,8 +1,7 @@
import $ from 'jquery';
import 'vendor/jquery.endless-scroll';
import axios from '~/lib/utils/axios_utils';
import { getParameterByName } from '~/lib/utils/common_utils';
import { removeParams } from '~/lib/utils/url_utility';
import { removeParams, getParameterByName } from '~/lib/utils/url_utility';
const ENDLESS_SCROLL_BOTTOM_PX = 400;
const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000;

View File

@ -1,7 +1,5 @@
import initDeprecatedRemoveRowBehavior from '~/behaviors/deprecated_remove_row_behavior';
import initBroadcastMessagesForm from './broadcast_message';
document.addEventListener('DOMContentLoaded', () => {
initBroadcastMessagesForm();
initDeprecatedRemoveRowBehavior();
});
initBroadcastMessagesForm();
initDeprecatedRemoveRowBehavior();

View File

@ -1,5 +1,3 @@
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
new ClustersBundle(); // eslint-disable-line no-new

View File

@ -1,5 +1,3 @@
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
new ClustersBundle(); // eslint-disable-line no-new

View File

@ -1,5 +1,3 @@
import initCreateCluster from '~/create_cluster/init_create_cluster';
document.addEventListener('DOMContentLoaded', () => {
initCreateCluster(document, gon);
});
initCreateCluster(document, gon);

View File

@ -1,8 +1,6 @@
import initClustersListApp from '~/clusters_list';
import PersistentUserCallout from '~/persistent_user_callout';
document.addEventListener('DOMContentLoaded', () => {
const callout = document.querySelector('.gcp-signup-offer');
PersistentUserCallout.factory(callout);
initClustersListApp();
});
const callout = document.querySelector('.gcp-signup-offer');
PersistentUserCallout.factory(callout);
initClustersListApp();

View File

@ -1,5 +1,3 @@
import initNewCluster from '~/clusters/new_cluster';
document.addEventListener('DOMContentLoaded', () => {
initNewCluster();
});
initNewCluster();

View File

@ -2,8 +2,6 @@ import ClustersBundle from '~/clusters/clusters_bundle';
import initIntegrationForm from '~/clusters/forms/show';
import initClusterHealth from '~/pages/projects/clusters/show/cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
initClusterHealth();
initIntegrationForm();
});
new ClustersBundle(); // eslint-disable-line no-new
initClusterHealth();
initIntegrationForm();

View File

@ -1,5 +1,3 @@
import initConfirmModal from '~/confirm_modal';
document.addEventListener('DOMContentLoaded', () => {
initConfirmModal();
});
initConfirmModal();

View File

@ -1,5 +1,3 @@
import initConfirmModal from '~/confirm_modal';
document.addEventListener('DOMContentLoaded', () => {
initConfirmModal();
});
initConfirmModal();

View File

@ -1,5 +1,3 @@
import initGroupsList from '~/groups';
document.addEventListener('DOMContentLoaded', () => {
initGroupsList();
});
initGroupsList();

View File

@ -2,8 +2,6 @@ import Milestone from '~/milestone';
import Sidebar from '~/right_sidebar';
import MountMilestoneSidebar from '~/sidebar/mount_milestone_sidebar';
document.addEventListener('DOMContentLoaded', () => {
new Milestone(); // eslint-disable-line no-new
new Sidebar(); // eslint-disable-line no-new
new MountMilestoneSidebar(); // eslint-disable-line no-new
});
new Milestone(); // eslint-disable-line no-new
new Sidebar(); // eslint-disable-line no-new
new MountMilestoneSidebar(); // eslint-disable-line no-new

View File

@ -1,5 +1,3 @@
import ProjectsList from '~/projects_list';
document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new
});
new ProjectsList(); // eslint-disable-line no-new

View File

@ -1,5 +1,3 @@
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
new ClustersBundle(); // eslint-disable-line no-new

View File

@ -1,5 +1,3 @@
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
new ClustersBundle(); // eslint-disable-line no-new

View File

@ -1,7 +1,5 @@
import initIntegrationForm from '~/clusters/forms/show/index';
import initCreateCluster from '~/create_cluster/init_create_cluster';
document.addEventListener('DOMContentLoaded', () => {
initCreateCluster(document, gon);
initIntegrationForm();
});
initCreateCluster(document, gon);
initIntegrationForm();

View File

@ -1,5 +1,3 @@
import initNewCluster from '~/clusters/new_cluster';
document.addEventListener('DOMContentLoaded', () => {
initNewCluster();
});
initNewCluster();

View File

@ -1,7 +1,5 @@
import ClustersBundle from '~/clusters/clusters_bundle';
import initClusterHealth from '~/pages/projects/clusters/show/cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
initClusterHealth();
});
new ClustersBundle(); // eslint-disable-line no-new
initClusterHealth();

View File

@ -1,7 +1,5 @@
import initDeleteMilestoneModal from '~/pages/milestones/shared/delete_milestone_modal_init';
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
document.addEventListener('DOMContentLoaded', () => {
initMilestonesShow();
initDeleteMilestoneModal();
});
initMilestonesShow();
initDeleteMilestoneModal();

View File

@ -1,5 +1,3 @@
import initNotificationsDropdown from '~/notifications';
document.addEventListener('DOMContentLoaded', () => {
initNotificationsDropdown();
});
initNotificationsDropdown();

View File

@ -1,7 +1,5 @@
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import BuildArtifacts from '~/build_artifacts';
document.addEventListener('DOMContentLoaded', () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
new BuildArtifacts(); // eslint-disable-line no-new
});
new ShortcutsNavigation(); // eslint-disable-line no-new
new BuildArtifacts(); // eslint-disable-line no-new

View File

@ -1,7 +1,5 @@
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import BlobViewer from '~/blob/viewer/index';
document.addEventListener('DOMContentLoaded', () => {
new ShortcutsNavigation(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
});
new ShortcutsNavigation(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new

View File

@ -1,5 +1,3 @@
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
new ClustersBundle(); // eslint-disable-line no-new

View File

@ -1,5 +1,3 @@
import ClustersBundle from '~/clusters/clusters_bundle';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
});
new ClustersBundle(); // eslint-disable-line no-new

View File

@ -1,5 +1,3 @@
import initCreateCluster from '~/create_cluster/init_create_cluster';
document.addEventListener('DOMContentLoaded', () => {
initCreateCluster(document, gon);
});
initCreateCluster(document, gon);

View File

@ -3,9 +3,7 @@ import initIntegrationForm from '~/clusters/forms/show';
import initGkeNamespace from '~/create_cluster/gke_cluster_namespace';
import initClusterHealth from './cluster_health';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
initGkeNamespace();
initClusterHealth();
initIntegrationForm();
});
new ClustersBundle(); // eslint-disable-line no-new
initGkeNamespace();
initClusterHealth();
initIntegrationForm();

View File

@ -1,7 +1,5 @@
import initMergeConflicts from '~/merge_conflicts/merge_conflicts_bundle';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
document.addEventListener('DOMContentLoaded', () => {
initSidebarBundle();
initMergeConflicts();
});
initSidebarBundle();
initMergeConflicts();

View File

@ -2,8 +2,6 @@ import NoEmojiValidator from '~/emoji/no_emoji_validator';
import LengthValidator from '~/pages/sessions/new/length_validator';
import UsernameValidator from '~/pages/sessions/new/username_validator';
document.addEventListener('DOMContentLoaded', () => {
new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
});
new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new

View File

@ -264,6 +264,7 @@ export default {
this.contentEditor ||
createContentEditor({
renderMarkdown: (markdown) => this.getContentHTML(markdown),
uploadsPath: this.pageInfo.uploadsPath,
tiptapOptions: {
onUpdate: () => this.handleContentChange(),
},

View File

@ -2,7 +2,7 @@
import { GlEmptyState, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import { isEqual } from 'lodash';
import createFlash from '~/flash';
import { getParameterByName } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';

View File

@ -1,8 +1,7 @@
<script>
import { GlButton, GlFormInput, GlFormGroup, GlSprintf } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { getParameterByName } from '~/lib/utils/common_utils';
import { isSameOriginUrl } from '~/lib/utils/url_utility';
import { isSameOriginUrl, getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue';
import { BACK_URL_PARAM } from '~/releases/constants';

View File

@ -1,7 +1,7 @@
<script>
import { GlEmptyState, GlLink, GlButton } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import { getParameterByName } from '~/lib/utils/common_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import ReleaseBlock from './release_block.vue';
import ReleaseSkeletonLoader from './release_skeleton_loader.vue';

View File

@ -2,9 +2,9 @@
import { GlButton } from '@gitlab/ui';
import allReleasesQuery from 'shared_queries/releases/all_releases.query.graphql';
import createFlash from '~/flash';
import { historyPushState, getParameterByName } from '~/lib/utils/common_utils';
import { historyPushState } from '~/lib/utils/common_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
import { setUrlParams } from '~/lib/utils/url_utility';
import { setUrlParams, getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { PAGE_SIZE, DEFAULT_SORT } from '~/releases/constants';
import { convertAllReleasesGraphQLResponse } from '~/releases/util';

View File

@ -3,12 +3,8 @@ import { GlBadge, GlButton } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapState, mapActions } from 'vuex';
import EmptyState from '~/feature_flags/components/empty_state.vue';
import {
buildUrlWithCurrentLocation,
getParameterByName,
historyPushState,
} from '~/lib/utils/common_utils';
import { objectToQuery } from '~/lib/utils/url_utility';
import { buildUrlWithCurrentLocation, historyPushState } from '~/lib/utils/common_utils';
import { objectToQuery, getParameterByName } from '~/lib/utils/url_utility';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import UserListsTable from './user_lists_table.vue';

View File

@ -24,15 +24,8 @@ class ContainerRepository < ApplicationRecord
scope :for_group_and_its_subgroups, ->(group) do
project_scope = Project
.for_group_and_its_subgroups(group)
project_scope =
if Feature.enabled?(:read_container_registry_access_level, group, default_enabled: :yaml)
project_scope.with_feature_enabled(:container_registry)
else
project_scope.with_container_registry
end
project_scope = project_scope.select(:id)
.with_feature_enabled(:container_registry)
.select(:id)
joins("INNER JOIN (#{project_scope.to_sql}) projects on projects.id=container_repositories.project_id")
end

View File

@ -406,8 +406,9 @@ class Project < ApplicationRecord
:wiki_access_level, :snippets_access_level, :builds_access_level,
:repository_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level,
:operations_enabled?, :operations_access_level, :security_and_compliance_access_level,
:container_registry_access_level,
:container_registry_access_level, :container_registry_enabled?,
to: :project_feature, allow_nil: true
alias_method :container_registry_enabled, :container_registry_enabled?
delegate :show_default_award_emojis, :show_default_award_emojis=,
:show_default_award_emojis?,
to: :project_setting, allow_nil: true
@ -547,7 +548,6 @@ class Project < ApplicationRecord
scope :include_project_feature, -> { includes(:project_feature) }
scope :with_integration, ->(integration) { joins(integration).eager_load(integration) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :with_container_registry, -> { where(container_registry_enabled: true) }
scope :inside_path, ->(path) do
# We need routes alias rs for JOIN so it does not conflict with
# includes(:route) which we use in ProjectsFinder.
@ -2621,15 +2621,6 @@ class Project < ApplicationRecord
!!read_attribute(:merge_requests_author_approval)
end
def container_registry_enabled
if Feature.enabled?(:read_container_registry_access_level, self.namespace, default_enabled: :yaml)
project_feature.container_registry_enabled?
else
read_attribute(:container_registry_enabled)
end
end
alias_method :container_registry_enabled?, :container_registry_enabled
def ci_forward_deployment_enabled?
return false unless ci_cd_settings

View File

@ -51,11 +51,7 @@ class ProjectPolicy < BasePolicy
desc "Container registry is disabled"
condition(:container_registry_disabled, scope: :subject) do
if ::Feature.enabled?(:read_container_registry_access_level, @subject&.namespace, default_enabled: :yaml)
!access_allowed_to?(:container_registry)
else
!project.container_registry_enabled
end
!access_allowed_to?(:container_registry)
end
desc "Project has an external wiki"

View File

@ -45,7 +45,7 @@ module Packages
channel: CHANNEL
)
{
'contextPath' => expose_url(path.delete_suffix(INDEX_YAML_SUFFIX))
'contextPath' => path.delete_suffix(INDEX_YAML_SUFFIX)
}
end
end

View File

@ -7,7 +7,9 @@ module Ci
Ci::ExpirePipelineCacheService.new.execute(pipeline, delete: true)
pipeline.destroy!
pipeline.cancel_running if pipeline.cancelable? && ::Feature.enabled?(:cancel_pipelines_prior_to_destroy, default_enabled: :yaml)
pipeline.reset.destroy!
ServiceResponse.success(message: 'Pipeline not found')
rescue ActiveRecord::RecordNotFound

View File

@ -3,14 +3,22 @@
module Keys
class DestroyService < ::Keys::BaseService
def execute(key)
key.destroy if destroy_possible?(key)
return unless destroy_possible?(key)
destroy(key)
end
private
# overridden in EE::Keys::DestroyService
def destroy_possible?(key)
true
end
def destroy(key)
key.destroy
end
end
end
Keys::DestroyService.prepend_mod_with('Keys::DestroyService')
Keys::DestroyService.prepend_mod

View File

@ -1,10 +1,11 @@
- page_title _("Fork project")
- if @forked_project && !@forked_project.saved?
.gl-alert.gl-alert-danger.gl-mt-5
= sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon')
%h4.gl-alert-title
= sprite_icon('fork')
= _("Fork Error!")
= render 'shared/global_alert',
title: _('Fork Error!'),
variant: :danger,
alert_class: 'gl-mt-5',
is_contained: true,
dismissible: false do
.gl-alert-body
%p
= _("You tried to fork %{link_to_the_project} but it failed for the following reason:").html_safe % { link_to_the_project: link_to_project(@project) }
@ -14,9 +15,9 @@
&ndash;
- error = @forked_project.errors.full_messages.first
- if error.include?("already been taken")
= _("Name has already been taken")
= _('Name has already been taken')
- else
= error
.gl-alert-actions
= link_to _("Try to fork again"), new_project_fork_path(@project), title: _("Fork"), class: "btn gl-alert-action btn-info btn-md gl-button"
= link_to _('Try to fork again'), new_project_fork_path(@project), title: _("Fork"), class: "btn gl-alert-action btn-info btn-md gl-button"

View File

@ -1,63 +0,0 @@
#!/bin/sh
set -e
cd $(dirname $0)/..
app_root=$(pwd)
puma_pidfile="$app_root/tmp/pids/puma_actioncable.pid"
puma_config="$app_root/config/puma_actioncable.rb"
spawn_puma()
{
exec bundle exec puma --config "${puma_config}" --environment "$RAILS_ENV" "$@"
}
get_puma_pid()
{
pid=$(cat "${puma_pidfile}")
if [ -z "$pid" ] ; then
echo "Could not find a PID in $puma_pidfile"
exit 1
fi
echo "${pid}"
}
start()
{
spawn_puma -d
}
start_foreground()
{
spawn_puma
}
stop()
{
get_puma_pid
kill -INT "$(get_puma_pid)"
}
reload()
{
kill -USR2 "$(get_puma_pid)"
}
case "$1" in
start)
start
;;
start_foreground)
start_foreground
;;
stop)
stop
;;
reload)
reload
;;
*)
echo "Usage: RAILS_ENV=your_env $0 {start|start_foreground|stop|reload}"
;;
esac

View File

@ -1,10 +0,0 @@
# frozen_string_literal: true
require ::File.expand_path('../../config/environment', __FILE__)
Rails.application.eager_load!
ACTION_CABLE_SERVER = true
use ActionDispatch::RequestId, header: Rails.application.config.action_dispatch.request_id_header
run ActionCable.server

View File

@ -1,8 +1,8 @@
---
name: read_container_registry_access_level
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55071
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332751
milestone: '14.0'
name: cancel_pipelines_prior_to_destroy
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65586
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335338
milestone: '14.1'
type: development
group: group::package
group: group::continuous integration
default_enabled: false

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true
Gitlab::Database::ConnectionTimer.configure do |config|
config.interval = Rails.application.config_for(:database)[:force_reconnect_interval]
configuration_hash = ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash
config.interval = configuration_hash[:force_reconnect_interval]
end
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(Gitlab::Database::PostgresqlAdapter::ForceDisconnectableMixin)

View File

@ -1,94 +0,0 @@
# frozen_string_literal: true
# -----------------------------------------------------------------------
# This file is used by the GDK to generate a default config/puma_actioncable.rb file
# Note that `/home/git` will be substituted for the actual GDK root
# directory when this file is generated
# -----------------------------------------------------------------------
# Load "path" as a rackup file.
#
# The default is "cable/config.ru".
#
rackup 'cable/config.ru'
pidfile '/home/git/gitlab/tmp/pids/puma_actioncable.pid'
state_path '/home/git/gitlab/tmp/pids/puma_actioncable.state'
## Uncomment the lines if you would like to write puma stdout & stderr streams
## to a different location than rails logs.
## When using GitLab Development Kit, by default, these logs will be consumed
## by runit and can be accessed using `gdk tail rails-actioncable`
# stdout_redirect '/home/git/gitlab/log/puma_actioncable.stdout.log',
# '/home/git/gitlab/log/puma_actioncable.stderr.log',
# true
# Configure "min" to be the minimum number of threads to use to answer
# requests and "max" the maximum.
#
# The default is "0, 16".
#
threads 1, 4
# By default, workers accept all requests and queue them to pass to handlers.
# When false, workers accept the number of simultaneous requests configured.
#
# Queueing requests generally improves performance, but can cause deadlocks if
# the app is waiting on a request to itself. See https://github.com/puma/puma/issues/612
#
# When set to false this may require a reverse proxy to handle slow clients and
# queue requests before they reach puma. This is due to disabling HTTP keepalive
queue_requests false
# Bind the server to "url". "tcp://", "unix://" and "ssl://" are the only
# accepted protocols.
bind 'unix:///home/git/gitlab_actioncable.socket'
workers 1
require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
on_restart do
# Signal application hooks that we're about to restart
Gitlab::Cluster::LifecycleEvents.do_before_master_restart
end
before_fork do
# Signal to the puma killer
Gitlab::Cluster::PumaWorkerKillerInitializer.start @config.options unless ENV['DISABLE_PUMA_WORKER_KILLER']
# Signal application hooks that we're about to fork
Gitlab::Cluster::LifecycleEvents.do_before_fork
end
Gitlab::Cluster::LifecycleEvents.set_puma_options @config.options
on_worker_boot do
# Signal application hooks of worker start
Gitlab::Cluster::LifecycleEvents.do_worker_start
end
# Preload the application before starting the workers; this conflicts with
# phased restart feature. (off by default)
preload_app!
tag 'gitlab-actioncable-puma-worker'
# Verifies that all workers have checked in to the master process within
# the given timeout. If not the worker process will be restarted. Default
# value is 60 seconds.
#
worker_timeout 60
# https://github.com/puma/puma/blob/master/5.0-Upgrade.md#lower-latency-better-throughput
wait_for_less_busy_worker ENV.fetch('PUMA_WAIT_FOR_LESS_BUSY_WORKER', 0.001).to_f
# https://github.com/puma/puma/blob/master/5.0-Upgrade.md#nakayoshi_fork
nakayoshi_fork unless ENV['DISABLE_PUMA_NAKAYOSHI_FORK'] == 'true'
# Use json formatter
require_relative "/home/git/gitlab/lib/gitlab/puma_logging/json_formatter"
json_formatter = Gitlab::PumaLogging::JSONFormatter.new
log_formatter do |str|
json_formatter.call(str)
end

View File

@ -164,6 +164,7 @@ The following user actions are recorded:
- A user's personal access token was successfully created or revoked ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6)
- A failed attempt to create or revoke a user's personal access token ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276921) in GitLab 13.6)
- Administrator added or removed ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/323905) in GitLab 14.1)
- Removed SSH key ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220127) in GitLab 14.1)
Instance events can also be accessed via the [Instance Audit Events API](../api/audit_events.md#instance-audit-events).

View File

@ -136,6 +136,9 @@ The following metrics are available:
| `service_desk_thank_you_email` | Counter | 14.0 | Total number of email responses to new service desk emails | |
| `service_desk_new_note_email` | Counter | 14.0 | Total number of email notifications on new service desk comment | |
| `email_receiver_error` | Counter | 14.1 | Total number of errors when processing incoming emails | |
| `gitlab_snowplow_events_total` | Counter | 14.1 | Total number of GitLab Snowplow product intelligence events emitted | |
| `gitlab_snowplow_failed_events_total` | Counter | 14.1 | Total number of GitLab Snowplow product intelligence events emission failures | |
| `gitlab_snowplow_successful_events_total` | Counter | 14.1 | Total number of GitLab Snowplow product intelligence events emission successes | |
## Metrics controlled by a feature flag

View File

@ -9,25 +9,35 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6861) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.6.
Custom project templates are useful for organizations that need to create many similar types of
[projects](../project/index.md).
Projects created from these templates serve as a common starting point.
[Group owners](../permissions.md#group-members-permissions) can set a subgroup to
be the source of project templates that are selectable when a new project is created
in the group. These templates can be selected when you go to **New project > Create from template**
in the group and select the **Group** tab.
## Setting up group-level project templates
Every project in the subgroup, but not nested subgroups, can be selected by members
of the group when a new project is created.
To use a custom project template for a new project:
Repository and database information that is copied over to each new project is identical to the
data exported with the [GitLab Project Import/Export](../project/settings/import_export.md).
1. [Create a `templates` subgroup](subgroups/index.md).
1. [Add repositories (projects) to that new subgroup](index.md#add-projects-to-a-group),
as your templates.
1. Edit your group's settings to look to your _templates_ subgroup for templates:
To set custom project templates at the instance level, see [Custom instance-level project templates](../admin_area/custom_project_templates.md).
1. In the left menu, select **Settings > General**. If you don't have access to the
group's settings, you may not have sufficient privileges (for example, you may need developer
or higher permissions).
1. Scroll to **Custom project templates** and select **Expand**. If no **Custom project templates**
section displays, make sure you've created a subgroup and added a project (repository) to it.
1. Select the **templates** subgroup.
## Set up group-level project templates
To set up custom project templates in a group, add the subgroup that contains the
project templates to the group settings:
1. In the group, create a [subgroup](subgroups/index.md).
1. [Add projects to the new subgroup](index.md#add-projects-to-a-group) as your templates.
1. In the left menu for the group, go to **Settings > General**.
1. Expand **Custom project templates** and select the subgroup.
If all enabled [project features](../project/settings/index.md#sharing-and-permissions)
(except for GitLab Pages) are set to **Everyone With Access**, then every project
template in the subgroup is available to every member of the group.
Any projects added to the subgroup later can be selected the next time a group member
creates a new project.
### Example structure
@ -54,25 +64,6 @@ gitlab.com/acmeco/
...
```
### Adjust Settings
Users can configure a GitLab group that serves as template source under a group's
**Settings > General > Custom project templates**.
NOTE:
GitLab administrators can [set project templates for an entire GitLab instance](../admin_area/custom_project_templates.md).
Within this section, you can configure the group where all the custom project templates are sourced.
If all enabled [project features](../project/settings/index.md#sharing-and-permissions)
(except for GitLab Pages) are set to **Everyone With Access**, then every project template directly
under the group namespace is available to every signed-in user. However, private projects are
available only if the user is a member of the project. Also note that only direct subgroups can be
set as the template source. Projects of nested subgroups of a selected template source cannot be
used.
Repository and database information that are copied over to each new project are identical to the
data exported with the [GitLab Project Import/Export](../project/settings/import_export.md).
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues

View File

@ -11,11 +11,21 @@ disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/merge
> Redesign [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1979) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8 and [feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/10685) in 12.0.
You can configure your merge requests so that they must be approved before
they can be merged. You can do this by creating [rules](rules.md) or by specifying
a list of users who act as [code owners](../../code_owners.md) for specific files.
they can be merged. While [GitLab Free](https://about.gitlab.com/pricing/) allows
all users with Developer or greater [permissions](../../../permissions.md) to
approve merge requests, these approvals are [optional](#optional-approvals).
[GitLab Premium](https://about.gitlab.com/pricing/) and
[GitLab Ultimate](https://about.gitlab.com/pricing/) provide additional
flexibility:
You can configure merge request approvals for each project. In higher GitLab tiers,
Administrators of self-managed GitLab instances can configure approvals
- Create required [rules](rules.md) about the number and type of approvers before work can merge.
- Specify a list of users who act as [code owners](../../code_owners.md) for specific files,
and require their approval before work can merge.
You can configure merge request approvals on a per-project basis. Administrators of
[GitLab Premium](https://about.gitlab.com/pricing/) and
[GitLab Ultimate](https://about.gitlab.com/pricing/) self-managed GitLab instances
can also configure approvals
[for the entire instance](../../../admin_area/merge_requests_approvals.md).
## How approvals work

View File

@ -5,7 +5,7 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, concepts
---
# Merge request approval rules **(FREE)**
# Merge request approval rules **(PREMIUM)**
Approval rules define how many [approvals](index.md) a merge request must receive before it can
be merged, and which users should do the approving. You can define approval rules:
@ -173,6 +173,7 @@ Some users (like managers) may not need permission to push or merge code, but st
oversight on proposed work. To enable approval permissions for these users without
granting them push access:
1. [Create a protected branch](../../protected_branches.md)
1. [Create a new group](../../../group/index.md#create-a-group).
1. [Add the user to the group](../../../group/index.md#add-users-to-a-group),
and select the Reporter role for the user.
@ -180,7 +181,7 @@ granting them push access:
based on the Reporter role.
1. Go to your project and select **Settings > General**.
1. Expand **Merge request (MR) approvals**.
1. Select **Add approval rule** or **Update approval rule**.
1. Select **Add approval rule** or **Update approval rule** and target the protected branch.
1. [Add the group](../../../group/index.md#create-a-group) to the permission list.
![Update approval rule](img/update_approval_rule_v13_10.png)
@ -203,7 +204,7 @@ on a merge request, you can either add or remove approvers:
Administrators can change the [merge request approvals settings](settings.md#prevent-overrides-of-default-approvals)
to prevent users from overriding approval rules for merge requests.
## Configure optional approval rules
## Configure optional approval rules **(PREMIUM)**
Merge request approvals can be optional for projects where approvals are
appreciated, but not required. To make an approval rule optional:

View File

@ -53,13 +53,7 @@ module API
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
expose(:container_registry_enabled) do |project, options|
if ::Feature.enabled?(:read_container_registry_access_level, project.namespace, default_enabled: :yaml)
project.feature_available?(:container_registry, options[:current_user])
else
project.read_attribute(:container_registry_enabled)
end
end
expose(:container_registry_enabled) { |project, options| project.feature_available?(:container_registry, options[:current_user]) }
expose :service_desk_enabled
expose :service_desk_address

View File

@ -18,7 +18,7 @@ module Backup
def initialize(progress, filename: nil)
@progress = progress
@config = YAML.load_file(File.join(Rails.root, 'config', 'database.yml'))[Rails.env]
@config = ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash
@db_file_name = filename || File.join(Gitlab.config.backup.path, 'db', 'database.sql.gz')
end
@ -30,9 +30,9 @@ module Backup
compress_rd.close
dump_pid =
case config["adapter"]
case config[:adapter]
when "postgresql" then
progress.print "Dumping PostgreSQL database #{config['database']} ... "
progress.print "Dumping PostgreSQL database #{database} ... "
pg_env
pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
pgsql_args << '--if-exists'
@ -47,7 +47,7 @@ module Backup
end
end
Process.spawn('pg_dump', *pgsql_args, config['database'], out: compress_wr)
Process.spawn('pg_dump', *pgsql_args, database, out: compress_wr)
end
compress_wr.close
@ -68,9 +68,9 @@ module Backup
decompress_wr.close
status, errors =
case config["adapter"]
case config[:adapter]
when "postgresql" then
progress.print "Restoring PostgreSQL database #{config['database']} ... "
progress.print "Restoring PostgreSQL database #{database} ... "
pg_env
execute_and_track_errors(pg_restore_cmd, decompress_rd)
end
@ -93,6 +93,10 @@ module Backup
protected
def database
@config[:database]
end
def ignore_error?(line)
IGNORED_ERRORS_REGEXP.match?(line)
end
@ -128,17 +132,17 @@ module Backup
def pg_env
args = {
'username' => 'PGUSER',
'host' => 'PGHOST',
'port' => 'PGPORT',
'password' => 'PGPASSWORD',
username: 'PGUSER',
host: 'PGHOST',
port: 'PGPORT',
password: 'PGPASSWORD',
# SSL
'sslmode' => 'PGSSLMODE',
'sslkey' => 'PGSSLKEY',
'sslcert' => 'PGSSLCERT',
'sslrootcert' => 'PGSSLROOTCERT',
'sslcrl' => 'PGSSLCRL',
'sslcompression' => 'PGSSLCOMPRESSION'
sslmode: 'PGSSLMODE',
sslkey: 'PGSSLKEY',
sslcert: 'PGSSLCERT',
sslrootcert: 'PGSSLROOTCERT',
sslcrl: 'PGSSLCRL',
sslcompression: 'PGSSLCOMPRESSION'
}
args.each do |opt, arg|
# This enables the use of different PostgreSQL settings in
@ -161,7 +165,7 @@ module Backup
private
def pg_restore_cmd
['psql', config['database']]
['psql', database]
end
end
end

View File

@ -72,8 +72,19 @@ module Gitlab
Gitlab::Application.config.database_configuration[Rails.env].include?(database_name.to_s)
end
def self.main_database?(name)
# The database is `main` if it is a first entry in `database.yml`
# Rails internally names them `primary` to avoid confusion
# with broad `primary` usage we use `main` instead
#
# TODO: The explicit `== 'main'` is needed in a transition period till
# the `database.yml` is not migrated into `main:` syntax
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65243
ActiveRecord::Base.configurations.primary?(name.to_s) || name.to_s == 'main'
end
def self.ci_database?(name)
name == CI_DATABASE_NAME
name.to_s == CI_DATABASE_NAME
end
def self.username

View File

@ -13,6 +13,7 @@ module Gitlab
return unless enabled?
tracker.track_struct_event(category, action, label, property, value, context, (Time.now.to_f * 1000).to_i)
increment_total_events_counter
end
private
@ -33,9 +34,46 @@ module Gitlab
def emitter
SnowplowTracker::AsyncEmitter.new(
Gitlab::CurrentSettings.snowplow_collector_hostname,
protocol: 'https'
protocol: 'https',
on_success: method(:increment_successful_events_emissions),
on_failure: method(:failure_callback)
)
end
def failure_callback(success_count, failures)
increment_successful_events_emissions(success_count)
increment_failed_events_emissions(failures.size)
log_failures(failures)
end
def increment_failed_events_emissions(value)
Gitlab::Metrics.counter(
:gitlab_snowplow_failed_events_total,
'Number of failed Snowplow events emissions'
).increment({}, value.to_i)
end
def increment_successful_events_emissions(value)
Gitlab::Metrics.counter(
:gitlab_snowplow_successful_events_total,
'Number of successful Snowplow events emissions'
).increment({}, value.to_i)
end
def increment_total_events_counter
Gitlab::Metrics.counter(
:gitlab_snowplow_events_total,
'Number of Snowplow events'
).increment
end
def log_failures(failures)
hostname = Gitlab::CurrentSettings.snowplow_collector_hostname
failures.each do |failure|
Gitlab::AppLogger.error("#{failure["se_ca"]} #{failure["se_ac"]} failed to be reported to collector at #{hostname}")
end
end
end
end
end

View File

@ -2529,6 +2529,9 @@ msgstr ""
msgid "AdminUsers|Approve user"
msgstr ""
msgid "AdminUsers|Approve user %{username}?"
msgstr ""
msgid "AdminUsers|Approved users can:"
msgstr ""
@ -2694,6 +2697,9 @@ msgstr ""
msgid "AdminUsers|Reject request"
msgstr ""
msgid "AdminUsers|Reject user %{username}?"
msgstr ""
msgid "AdminUsers|Rejected users:"
msgstr ""
@ -2757,9 +2763,6 @@ msgstr ""
msgid "AdminUsers|Unblock user %{username}?"
msgstr ""
msgid "AdminUsers|Unlock"
msgstr ""
msgid "AdminUsers|Unlock user %{username}?"
msgstr ""
@ -15801,9 +15804,6 @@ msgstr ""
msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
msgid "GroupSettings|New runners registration token has been generated!"
msgstr ""
@ -15831,7 +15831,7 @@ msgstr ""
msgid "GroupSettings|Projects will be permanently deleted after a %{waiting_period}-day delay. This delay can be %{link_start}customized by an admin%{link_end} in instance settings. Inherited by subgroups."
msgstr ""
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
msgid "GroupSettings|Select a subgroup to use as the source for custom project templates for this group."
msgstr ""
msgid "GroupSettings|The Auto DevOps pipeline runs if no alternative CI configuration file is found."
@ -15840,6 +15840,9 @@ msgstr ""
msgid "GroupSettings|The default name for the initial branch of new repositories created in the group."
msgstr ""
msgid "GroupSettings|The projects in this subgroup can be selected as templates for new projects created in the group. %{link_start}Learn more.%{link_end}"
msgstr ""
msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
msgstr ""
@ -29437,6 +29440,9 @@ msgstr ""
msgid "Select strategy activation method"
msgstr ""
msgid "Select subgroup"
msgstr ""
msgid "Select subscription"
msgstr ""

View File

@ -37,8 +37,16 @@ module QA
choose_test_namespace(full_path)
set_path(full_path, name)
import_project(full_path)
wait_for_success
go_to_project(name)
end
# TODO: refactor to use 'go to project' button instead of generic main menu
def go_to_project(name)
Page::Main::Menu.perform(&:go_to_projects)
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(name)
end
end
private
@ -84,13 +92,6 @@ module QA
end
end
def go_to_project(name)
Page::Main::Menu.perform(&:go_to_projects)
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(name)
end
end
def already_imported(full_path)
within_repo_path(full_path) do
has_element?(:project_path_content) && has_element?(:go_to_project_button)

View File

@ -9,21 +9,23 @@ module QA
include Members
include Visibility
attr_accessor :repository_storage # requires admin access
attr_writer :initialize_with_readme,
:auto_devops_enabled,
:github_personal_access_token,
:github_repository_path
attr_accessor :repository_storage, # requires admin access
:initialize_with_readme,
:auto_devops_enabled,
:github_personal_access_token,
:github_repository_path
attribute :id
attribute :name
attribute :add_name_uuid
attribute :description
attribute :standalone
attribute :runners_token
attribute :visibility
attribute :template_name
attribute :import
attributes :id,
:name,
:add_name_uuid,
:description,
:standalone,
:runners_token,
:visibility,
:template_name,
:import,
:import_status,
:import_error
attribute :group do
Group.fabricate!

View File

@ -1,10 +1,16 @@
# frozen_string_literal: true
require 'securerandom'
require 'github_api'
module QA
module Resource
class ProjectImportedFromGithub < Resource::Project
attribute :github_repo_id do
github_repository_path.split('/').yield_self do |path|
github_client.repos.get(user: path[0], repo: path[1]).id
end
end
def fabricate!
self.import = true
@ -16,10 +22,44 @@ module QA
end
Page::Project::Import::Github.perform do |import_page|
import_page.add_personal_access_token(@github_personal_access_token)
import_page.import!(@github_repository_path, @name)
import_page.add_personal_access_token(github_personal_access_token)
import_page.import!(github_repository_path, name)
import_page.go_to_project(name)
end
end
def fabricate_via_api!
super
rescue ResourceURLMissingError
"#{Runtime::Scenario.gitlab_address}/#{group.full_path}/#{name}"
end
def api_post_path
'/import/github'
end
def api_post_body
{
repo_id: github_repo_id,
new_name: name,
target_namespace: group.full_path,
personal_access_token: github_personal_access_token,
ci_cd_only: false
}
end
def transform_api_resource(api_resource)
api_resource
end
private
# Github client
#
# @return [Github::Client]
def github_client
@github_client ||= Github.new(oauth_token: github_personal_access_token)
end
end
end
end

View File

@ -0,0 +1,133 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage', :github, :requires_admin do
describe 'Project import' do
let!(:api_client) { Runtime::API::Client.as_admin }
let!(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } }
let!(:user) do
Resource::User.fabricate_via_api! do |resource|
resource.api_client = api_client
resource.hard_delete_on_api_removal = true
end
end
let(:imported_project) do
Resource::ProjectImportedFromGithub.fabricate_via_api! do |project|
project.name = 'imported-project'
project.group = group
project.github_personal_access_token = Runtime::Env.github_access_token
project.github_repository_path = 'gitlab-qa-github/test-project'
project.api_client = api_client
end
end
before do
group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
end
after do
user.remove_via_api!
end
it 'imports Github repo via api', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1858' do
imported_project # import the project
expect { imported_project.reload!.import_status }.to eventually_eq('finished').within(duration: 90)
aggregate_failures do
verify_repository_import
verify_commits_import
verify_labels_import
verify_issues_import
verify_milestones_import
verify_wikis_import
verify_merge_requests_import
end
end
def verify_repository_import
expect(imported_project.api_response).to include(
description: 'A new repo for test',
import_error: nil
)
end
def verify_commits_import
expect(imported_project.commits.length).to eq(20)
end
def verify_labels_import
labels = imported_project.labels.map { |label| label.slice(:name, :color) }
expect(labels).to include(
{ name: 'bug', color: '#d73a4a' },
{ name: 'custom new label', color: '#fc8f91' },
{ name: 'documentation', color: '#0075ca' },
{ name: 'duplicate', color: '#cfd3d7' },
{ name: 'enhancement', color: '#a2eeef' },
{ name: 'good first issue', color: '#7057ff' },
{ name: 'help wanted', color: '#008672' },
{ name: 'invalid', color: '#e4e669' },
{ name: 'question', color: '#d876e3' },
{ name: 'wontfix', color: '#ffffff' }
)
end
def verify_issues_import
issues = imported_project.issues
expect(issues.length).to eq(1)
expect(issues.first).to include(
title: 'This is a sample issue',
description: "*Created by: gitlab-qa-github*\n\nThis is a sample first comment",
labels: ['custom new label', 'good first issue', 'help wanted'],
user_notes_count: 1
)
end
def verify_milestones_import
milestones = imported_project.milestones
expect(milestones.length).to eq(1)
expect(milestones.first).to include(title: 'v1.0', description: nil, state: 'active')
end
def verify_wikis_import
wikis = imported_project.wikis
expect(wikis.length).to eq(1)
expect(wikis.first).to include(title: 'Home', format: 'markdown')
end
def verify_merge_requests_import
merge_requests = imported_project.merge_requests
merge_request = Resource::MergeRequest.init do |mr|
mr.project = imported_project
mr.iid = merge_requests.first[:iid]
mr.api_client = api_client
end.reload!
mr_comments = merge_request.comments.map { |comment| comment[:body] } # rubocop:disable Rails/Pluck
expect(merge_requests.length).to eq(1)
expect(merge_request.api_resource).to include(
title: 'Improve readme',
state: 'opened',
target_branch: 'main',
source_branch: 'improve-readme',
labels: %w[bug documentation],
description: <<~DSC.strip
*Created by: gitlab-qa-github*\n\nThis improves the README file a bit.\r\n\r\nTODO:\r\n\r\n \r\n\r\n- [ ] Do foo\r\n- [ ] Make bar\r\n - [ ] Think about baz
DSC
)
expect(mr_comments).to eq(
[
"*Created by: gitlab-qa-github*\n\n[PR comment by @sliaquat] Nice work! ",
"*Created by: gitlab-qa-github*\n\n[Single diff comment] Nice addition",
"*Created by: gitlab-qa-github*\n\n[Single diff comment] Good riddance"
]
)
end
end
end
end

View File

@ -18,7 +18,6 @@ module QA
project.group = group
project.github_personal_access_token = Runtime::Env.github_access_token
project.github_repository_path = 'gitlab-qa-github/test-project'
project.api_client = api_client
end
end
@ -33,102 +32,13 @@ module QA
it 'imports a GitHub repo', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1762' do
Flow::Login.sign_in(as: user)
imported_project.reload! # import the project and reload all fields
imported_project # import the project
aggregate_failures do
verify_repository_import
verify_commits_import
verify_labels_import
verify_issues_import
verify_milestones_import
verify_wikis_import
verify_merge_requests_import
Page::Project::Show.perform do |project|
expect(project).to have_content(imported_project.name)
expect(project).to have_content('This test project is used for automated GitHub import by GitLab QA.')
end
end
def verify_repository_import
expect(imported_project.api_response).to include(
description: 'A new repo for test',
import_status: 'finished',
import_error: nil
)
end
def verify_commits_import
expect(imported_project.commits.length).to eq(20)
end
def verify_labels_import
labels = imported_project.labels.map { |label| label.slice(:name, :color) }
expect(labels).to include(
{ name: 'bug', color: '#d73a4a' },
{ name: 'custom new label', color: '#fc8f91' },
{ name: 'documentation', color: '#0075ca' },
{ name: 'duplicate', color: '#cfd3d7' },
{ name: 'enhancement', color: '#a2eeef' },
{ name: 'good first issue', color: '#7057ff' },
{ name: 'help wanted', color: '#008672' },
{ name: 'invalid', color: '#e4e669' },
{ name: 'question', color: '#d876e3' },
{ name: 'wontfix', color: '#ffffff' }
)
end
def verify_issues_import
issues = imported_project.issues
expect(issues.length).to eq(1)
expect(issues.first).to include(
title: 'This is a sample issue',
description: "*Created by: gitlab-qa-github*\n\nThis is a sample first comment",
labels: ['custom new label', 'good first issue', 'help wanted'],
user_notes_count: 1
)
end
def verify_milestones_import
milestones = imported_project.milestones
expect(milestones.length).to eq(1)
expect(milestones.first).to include(title: 'v1.0', description: nil, state: 'active')
end
def verify_wikis_import
wikis = imported_project.wikis
expect(wikis.length).to eq(1)
expect(wikis.first).to include(title: 'Home', format: 'markdown')
end
def verify_merge_requests_import
merge_requests = imported_project.merge_requests
merge_request = Resource::MergeRequest.init do |mr|
mr.project = imported_project
mr.iid = merge_requests.first[:iid]
mr.api_client = api_client
end.reload!
mr_comments = merge_request.comments.map { |comment| comment[:body] } # rubocop:disable Rails/Pluck
expect(merge_requests.length).to eq(1)
expect(merge_request.api_resource).to include(
title: 'Improve readme',
state: 'opened',
target_branch: 'main',
source_branch: 'improve-readme',
labels: %w[bug documentation],
description: <<~DSC.strip
*Created by: gitlab-qa-github*\n\nThis improves the README file a bit.\r\n\r\nTODO:\r\n\r\n \r\n\r\n- [ ] Do foo\r\n- [ ] Make bar\r\n - [ ] Think about baz
DSC
)
expect(mr_comments).to eq(
[
"*Created by: gitlab-qa-github*\n\n[PR comment by @sliaquat] Nice work! ",
"*Created by: gitlab-qa-github*\n\n[Single diff comment] Nice addition",
"*Created by: gitlab-qa-github*\n\n[Single diff comment] Good riddance"
]
)
end
end
end
end

View File

@ -438,32 +438,46 @@ RSpec.describe 'Pipeline', :js do
end
end
context 'deleting pipeline' do
context 'when user can not delete' do
before do
visit_pipeline
shared_context 'delete pipeline' do
context 'deleting pipeline' do
context 'when user can not delete' do
before do
visit_pipeline
end
it { expect(page).not_to have_button('Delete') }
end
it { expect(page).not_to have_button('Delete') }
end
context 'when deleting' do
before do
group.add_owner(user)
context 'when deleting' do
before do
group.add_owner(user)
visit_pipeline
visit_pipeline
click_button 'Delete'
click_button 'Delete pipeline'
end
click_button 'Delete'
click_button 'Delete pipeline'
end
it 'redirects to pipeline overview page', :sidekiq_might_not_need_inline do
expect(page).to have_content('The pipeline has been deleted')
expect(current_path).to eq(project_pipelines_path(project))
it 'redirects to pipeline overview page', :sidekiq_inline do
expect(page).to have_content('The pipeline has been deleted')
expect(current_path).to eq(project_pipelines_path(project))
end
end
end
end
context 'when cancel_pipelines_prior_to_destroy is enabled' do
include_context 'delete pipeline'
end
context 'when cancel_pipelines_prior_to_destroy is disabled' do
before do
stub_feature_flags(cancel_pipelines_prior_to_destroy: false)
end
include_context 'delete pipeline'
end
context 'when pipeline ref does not exist in repository anymore' do
let(:pipeline) do
create(:ci_empty_pipeline, project: project,

View File

@ -39,37 +39,12 @@ describe('Action components', () => {
await nextTick();
const div = wrapper.find('div');
expect(div.attributes('data-path')).toBe('/test');
expect(div.attributes('data-modal-attributes')).toContain('John Doe');
expect(wrapper.attributes('data-path')).toBe('/test');
expect(wrapper.attributes('data-modal-attributes')).toContain('John Doe');
expect(findDropdownItem().exists()).toBe(true);
});
});
describe('LINK_ACTIONS', () => {
it.each`
action | method
${'Approve'} | ${'put'}
${'Reject'} | ${'delete'}
`(
'renders a dropdown item link with method "$method" for "$action"',
async ({ action, method }) => {
initComponent({
component: Actions[action],
props: {
path: '/test',
},
});
await nextTick();
const item = wrapper.find(GlDropdownItem);
expect(item.attributes('href')).toBe('/test');
expect(item.attributes('data-method')).toContain(method);
},
);
});
describe('DELETE_ACTION_COMPONENTS', () => {
const oncallSchedules = [{ name: 'schedule1' }, { name: 'schedule2' }];
it.each(DELETE_ACTIONS)('renders a dropdown item for "%s"', async (action) => {

View File

@ -6,7 +6,7 @@ import { I18N_USER_ACTIONS } from '~/admin/users/constants';
import { generateUserPaths } from '~/admin/users/utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { CONFIRMATION_ACTIONS, DELETE_ACTIONS, LINK_ACTIONS, LDAP, EDIT } from '../constants';
import { CONFIRMATION_ACTIONS, DELETE_ACTIONS, LDAP, EDIT } from '../constants';
import { users, paths } from '../mock_data';
describe('AdminUserActions component', () => {
@ -62,7 +62,7 @@ describe('AdminUserActions component', () => {
describe('actions dropdown', () => {
describe('when there are actions', () => {
const actions = [EDIT, ...LINK_ACTIONS];
const actions = [EDIT, ...CONFIRMATION_ACTIONS];
beforeEach(() => {
initComponent({ actions });
@ -72,19 +72,6 @@ describe('AdminUserActions component', () => {
expect(findActionsDropdown().exists()).toBe(true);
});
describe('when there are actions that should render as links', () => {
beforeEach(() => {
initComponent({ actions: LINK_ACTIONS });
});
it.each(LINK_ACTIONS)('renders an action component item for "%s"', (action) => {
const component = wrapper.find(Actions[capitalizeFirstCharacter(action)]);
expect(component.props('path')).toBe(userPaths[action]);
expect(component.text()).toBe(I18N_USER_ACTIONS[action]);
});
});
describe('when there are actions that require confirmation', () => {
beforeEach(() => {
initComponent({ actions: CONFIRMATION_ACTIONS });

View File

@ -14,8 +14,16 @@ export const EDIT = 'edit';
export const LDAP = 'ldapBlocked';
export const LINK_ACTIONS = [APPROVE, REJECT];
export const CONFIRMATION_ACTIONS = [ACTIVATE, BLOCK, DEACTIVATE, UNLOCK, UNBLOCK, BAN, UNBAN];
export const CONFIRMATION_ACTIONS = [
ACTIVATE,
BLOCK,
DEACTIVATE,
UNLOCK,
UNBLOCK,
BAN,
UNBAN,
APPROVE,
REJECT,
];
export const DELETE_ACTIONS = [DELETE, DELETE_WITH_CONTRIBUTIONS];

View File

@ -12,8 +12,8 @@ import { createStore } from '~/boards/stores';
import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
visitUrl: jest.fn().mockName('visitUrlMock'),
stripFinalUrlSegment: jest.requireActual('~/lib/utils/url_utility').stripFinalUrlSegment,
}));
const currentBoard = {

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