Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
97576e3dfd
commit
1613500bf7
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ed9300c34897316f98e93d909b964828cb4e5879
|
||||
fef798978197809e268e5395838b4f4eeb4288e9
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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(``);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import initDeprecatedRemoveRowBehavior from '~/behaviors/deprecated_remove_row_behavior';
|
||||
import initBroadcastMessagesForm from './broadcast_message';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initBroadcastMessagesForm();
|
||||
initDeprecatedRemoveRowBehavior();
|
||||
});
|
||||
initBroadcastMessagesForm();
|
||||
initDeprecatedRemoveRowBehavior();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initCreateCluster from '~/create_cluster/init_create_cluster';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initCreateCluster(document, gon);
|
||||
});
|
||||
initCreateCluster(document, gon);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initNewCluster from '~/clusters/new_cluster';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initNewCluster();
|
||||
});
|
||||
initNewCluster();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initConfirmModal from '~/confirm_modal';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initConfirmModal();
|
||||
});
|
||||
initConfirmModal();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initConfirmModal from '~/confirm_modal';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initConfirmModal();
|
||||
});
|
||||
initConfirmModal();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initGroupsList from '~/groups';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initGroupsList();
|
||||
});
|
||||
initGroupsList();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initNewCluster from '~/clusters/new_cluster';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initNewCluster();
|
||||
});
|
||||
initNewCluster();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initNotificationsDropdown from '~/notifications';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initNotificationsDropdown();
|
||||
});
|
||||
initNotificationsDropdown();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import initCreateCluster from '~/create_cluster/init_create_cluster';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initCreateCluster(document, gon);
|
||||
});
|
||||
initCreateCluster(document, gon);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ export default {
|
|||
this.contentEditor ||
|
||||
createContentEditor({
|
||||
renderMarkdown: (markdown) => this.getContentHTML(markdown),
|
||||
uploadsPath: this.pageInfo.uploadsPath,
|
||||
tiptapOptions: {
|
||||
onUpdate: () => this.handleContentChange(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 @@
|
|||
–
|
||||
- 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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue