Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1b9f574b89
commit
af3904f9d0
|
@ -63,7 +63,7 @@ workflow:
|
|||
|
||||
variables:
|
||||
PG_VERSION: "12"
|
||||
DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-1.17-node-16.14-postgresql-${PG_VERSION}:rubygems-3.2-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36"
|
||||
DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-node-16.14-postgresql-${PG_VERSION}:rubygems-3.2-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36"
|
||||
RAILS_ENV: "test"
|
||||
NODE_ENV: "test"
|
||||
BUNDLE_WITHOUT: "production:development"
|
||||
|
@ -80,6 +80,7 @@ variables:
|
|||
CHROME_VERSION: "103"
|
||||
DOCKER_VERSION: "20.10.14"
|
||||
RUBY_VERSION: "2.7"
|
||||
GO_VERSION: "1.18"
|
||||
|
||||
TMP_TEST_FOLDER: "${CI_PROJECT_DIR}/tmp/tests"
|
||||
GITLAB_WORKHORSE_FOLDER: "gitlab-workhorse"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
workhorse:verify:
|
||||
extends: .workhorse:rules:workhorse
|
||||
image: ${GITLAB_DEPENDENCY_PROXY}golang:1.17
|
||||
image: ${GITLAB_DEPENDENCY_PROXY}golang:1.18
|
||||
stage: test
|
||||
needs: []
|
||||
script:
|
||||
|
@ -12,7 +12,7 @@ workhorse:verify:
|
|||
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-golang-${GO_VERSION}:git-2.36
|
||||
variables:
|
||||
GITALY_ADDRESS: "tcp://127.0.0.1:8075"
|
||||
GO_VERSION: "1.17"
|
||||
GO_VERSION: "1.18"
|
||||
stage: test
|
||||
needs:
|
||||
- setup-test-env
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<script>
|
||||
import MessagesTable from './messages_table.vue';
|
||||
|
||||
export default {
|
||||
name: 'BroadcastMessagesBase',
|
||||
components: {
|
||||
MessagesTable,
|
||||
},
|
||||
props: {
|
||||
messages: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<messages-table v-if="messages.length > 0" :messages="messages" />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,21 @@
|
|||
<script>
|
||||
import MessagesTableRow from './messages_table_row.vue';
|
||||
|
||||
export default {
|
||||
name: 'MessagesTable',
|
||||
components: {
|
||||
MessagesTableRow,
|
||||
},
|
||||
props: {
|
||||
messages: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<messages-table-row v-for="message in messages" :key="message.id" :message="message" />
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,16 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'MessagesTableRow',
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
{{ message.id }}
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,19 @@
|
|||
import Vue from 'vue';
|
||||
import BroadcastMessagesBase from './components/base.vue';
|
||||
|
||||
export default () => {
|
||||
const el = document.querySelector('#js-broadcast-messages');
|
||||
const { messages } = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
name: 'BroadcastMessagesBase',
|
||||
render(createElement) {
|
||||
return createElement(BroadcastMessagesBase, {
|
||||
props: {
|
||||
messages: JSON.parse(messages),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,5 +1,10 @@
|
|||
import initBroadcastMessages from '~/admin/broadcast_messages';
|
||||
import initDeprecatedRemoveRowBehavior from '~/behaviors/deprecated_remove_row_behavior';
|
||||
import initBroadcastMessagesForm from './broadcast_message';
|
||||
|
||||
initBroadcastMessagesForm();
|
||||
initDeprecatedRemoveRowBehavior();
|
||||
if (gon.features.vueBroadcastMessages) {
|
||||
initBroadcastMessages();
|
||||
} else {
|
||||
initBroadcastMessagesForm();
|
||||
initDeprecatedRemoveRowBehavior();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
GlFormInput,
|
||||
GlFormSelect,
|
||||
} from '@gitlab/ui';
|
||||
import { getDraft, clearDraft, updateDraft } from '~/lib/utils/autosave';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import { setUrlFragment } from '~/lib/utils/url_utility';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
|
@ -33,6 +34,29 @@ const MARKDOWN_LINK_TEXT = {
|
|||
org: '[[page-slug]]',
|
||||
};
|
||||
|
||||
function getPagePath(pageInfo) {
|
||||
return pageInfo.persisted ? pageInfo.path : pageInfo.createPath;
|
||||
}
|
||||
|
||||
const autosaveKey = (pageInfo, field) => {
|
||||
const path = pageInfo.persisted ? pageInfo.path : pageInfo.createPath;
|
||||
|
||||
return `${path}/${field}`;
|
||||
};
|
||||
|
||||
const titleAutosaveKey = (pageInfo) => autosaveKey(pageInfo, 'title');
|
||||
const formatAutosaveKey = (pageInfo) => autosaveKey(pageInfo, 'format');
|
||||
const contentAutosaveKey = (pageInfo) => autosaveKey(pageInfo, 'content');
|
||||
const commitAutosaveKey = (pageInfo) => autosaveKey(pageInfo, 'commit');
|
||||
|
||||
const getTitle = (pageInfo) => getDraft(titleAutosaveKey(pageInfo)) || pageInfo.title?.trim() || '';
|
||||
const getFormat = (pageInfo) =>
|
||||
getDraft(formatAutosaveKey(pageInfo)) || pageInfo.format || 'markdown';
|
||||
const getContent = (pageInfo) => getDraft(contentAutosaveKey(pageInfo)) || pageInfo.content || '';
|
||||
const getCommitMessage = (pageInfo) =>
|
||||
getDraft(commitAutosaveKey(pageInfo)) || pageInfo.commitMessage || '';
|
||||
const getIsFormDirty = (pageInfo) => Boolean(getDraft(titleAutosaveKey(pageInfo)));
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
title: {
|
||||
|
@ -87,13 +111,14 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
editingMode: 'source',
|
||||
title: this.pageInfo.title?.trim() || '',
|
||||
format: this.pageInfo.format || 'markdown',
|
||||
content: this.pageInfo.content || '',
|
||||
commitMessage: '',
|
||||
isDirty: false,
|
||||
title: getTitle(this.pageInfo),
|
||||
format: getFormat(this.pageInfo),
|
||||
content: getContent(this.pageInfo),
|
||||
commitMessage: getCommitMessage(this.pageInfo),
|
||||
contentEditorEmpty: false,
|
||||
isContentEditorActive: false,
|
||||
switchEditingControlDisabled: false,
|
||||
isFormDirty: getIsFormDirty(this.pageInfo),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -104,7 +129,7 @@ export default {
|
|||
return csrf.token;
|
||||
},
|
||||
formAction() {
|
||||
return this.pageInfo.persisted ? this.pageInfo.path : this.pageInfo.createPath;
|
||||
return getPagePath(this.pageInfo);
|
||||
},
|
||||
helpPath() {
|
||||
return setUrlFragment(
|
||||
|
@ -151,7 +176,7 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updateCommitMessage();
|
||||
if (!this.commitMessage) this.updateCommitMessage();
|
||||
|
||||
window.addEventListener('beforeunload', this.onPageUnload);
|
||||
},
|
||||
|
@ -160,6 +185,8 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
async handleFormSubmit(e) {
|
||||
this.isFormDirty = false;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
this.trackFormSubmit();
|
||||
|
@ -169,18 +196,33 @@ export default {
|
|||
await this.$nextTick();
|
||||
|
||||
e.target.submit();
|
||||
|
||||
this.isDirty = false;
|
||||
},
|
||||
|
||||
onPageUnload(event) {
|
||||
if (!this.isDirty) return undefined;
|
||||
updateDrafts() {
|
||||
updateDraft(titleAutosaveKey(this.pageInfo), this.title);
|
||||
updateDraft(formatAutosaveKey(this.pageInfo), this.format);
|
||||
updateDraft(contentAutosaveKey(this.pageInfo), this.content);
|
||||
updateDraft(commitAutosaveKey(this.pageInfo), this.commitMessage);
|
||||
},
|
||||
|
||||
event.preventDefault();
|
||||
clearDrafts() {
|
||||
clearDraft(titleAutosaveKey(this.pageInfo));
|
||||
clearDraft(formatAutosaveKey(this.pageInfo));
|
||||
clearDraft(contentAutosaveKey(this.pageInfo));
|
||||
clearDraft(commitAutosaveKey(this.pageInfo));
|
||||
},
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
event.returnValue = '';
|
||||
return '';
|
||||
handleContentEditorChange({ empty, markdown }) {
|
||||
this.contentEditorEmpty = empty;
|
||||
this.content = markdown;
|
||||
},
|
||||
|
||||
onPageUnload() {
|
||||
if (this.isFormDirty) {
|
||||
this.updateDrafts();
|
||||
} else {
|
||||
this.clearDrafts();
|
||||
}
|
||||
},
|
||||
|
||||
updateCommitMessage() {
|
||||
|
@ -222,10 +264,6 @@ export default {
|
|||
trackContentEditorLoaded() {
|
||||
this.track(CONTENT_EDITOR_LOADED_ACTION);
|
||||
},
|
||||
|
||||
checkDirty(markdown) {
|
||||
this.isDirty = this.pageInfo.content !== markdown;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -236,6 +274,7 @@ export default {
|
|||
method="post"
|
||||
class="wiki-form common-note-form gl-mt-3 js-quick-submit"
|
||||
@submit="handleFormSubmit"
|
||||
@input="isFormDirty = true"
|
||||
>
|
||||
<input :value="csrfToken" type="hidden" name="authenticity_token" />
|
||||
<input v-if="pageInfo.persisted" type="hidden" name="_method" value="put" />
|
||||
|
@ -306,7 +345,6 @@ export default {
|
|||
form-field-name="wiki[content]"
|
||||
@contentEditor="notifyContentEditorActive"
|
||||
@markdownField="notifyContentEditorInactive"
|
||||
@input="checkDirty"
|
||||
/>
|
||||
<div class="form-text gl-text-gray-600">
|
||||
<gl-sprintf
|
||||
|
@ -358,9 +396,14 @@ export default {
|
|||
:disabled="disableSubmitButton"
|
||||
>{{ submitButtonText }}</gl-button
|
||||
>
|
||||
<gl-button data-testid="wiki-cancel-button" :href="cancelFormPath" class="float-right">{{
|
||||
$options.i18n.cancel
|
||||
}}</gl-button>
|
||||
<gl-button
|
||||
data-testid="wiki-cancel-button"
|
||||
:href="cancelFormPath"
|
||||
class="float-right"
|
||||
@click="isFormDirty = false"
|
||||
>
|
||||
{{ $options.i18n.cancel }}</gl-button
|
||||
>
|
||||
</div>
|
||||
</gl-form>
|
||||
</template>
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<script>
|
||||
import { GlFormGroup, GlDropdownItem, GlSprintf } from '@gitlab/ui';
|
||||
import {
|
||||
GlCollapse,
|
||||
GlLink,
|
||||
GlFormGroup,
|
||||
GlFormTextarea,
|
||||
GlDropdownItem,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { mapState, mapActions, mapGetters } from 'vuex';
|
||||
import { __ } from '~/locale';
|
||||
import { __, s__ } from '~/locale';
|
||||
import RefSelector from '~/ref/components/ref_selector.vue';
|
||||
import { REF_TYPE_TAGS } from '~/ref/constants';
|
||||
import FormFieldContainer from './form_field_container.vue';
|
||||
|
@ -10,7 +17,10 @@ import FormFieldContainer from './form_field_container.vue';
|
|||
export default {
|
||||
name: 'TagFieldNew',
|
||||
components: {
|
||||
GlCollapse,
|
||||
GlFormGroup,
|
||||
GlFormTextarea,
|
||||
GlLink,
|
||||
RefSelector,
|
||||
FormFieldContainer,
|
||||
GlDropdownItem,
|
||||
|
@ -41,6 +51,14 @@ export default {
|
|||
this.updateShowCreateFrom(false);
|
||||
},
|
||||
},
|
||||
tagMessage: {
|
||||
get() {
|
||||
return this.release.tagMessage;
|
||||
},
|
||||
set(tagMessage) {
|
||||
this.updateReleaseTagMessage(tagMessage);
|
||||
},
|
||||
},
|
||||
createFromModel: {
|
||||
get() {
|
||||
return this.createFrom;
|
||||
|
@ -70,6 +88,7 @@ export default {
|
|||
methods: {
|
||||
...mapActions('editNew', [
|
||||
'updateReleaseTagName',
|
||||
'updateReleaseTagMessage',
|
||||
'updateCreateFrom',
|
||||
'fetchTagNotes',
|
||||
'updateShowCreateFrom',
|
||||
|
@ -113,9 +132,20 @@ export default {
|
|||
noRefSelected: __('No source selected'),
|
||||
searchPlaceholder: __('Search branches, tags, and commits'),
|
||||
dropdownHeader: __('Select source'),
|
||||
label: __('Create from'),
|
||||
description: __('Existing branch name, tag, or commit SHA'),
|
||||
},
|
||||
annotatedTag: {
|
||||
label: s__('CreateGitTag|Set tag message'),
|
||||
description: s__(
|
||||
'CreateGitTag|Add a message to the tag. Leaving this blank creates a %{linkStart}lightweight tag%{linkEnd}.',
|
||||
),
|
||||
},
|
||||
},
|
||||
tagMessageId: uniqueId('tag-message-'),
|
||||
|
||||
tagNameEnabledRefTypes: [REF_TYPE_TAGS],
|
||||
gitTagDocsLink: 'https://git-scm.com/book/en/v2/Git-Basics-Tagging/',
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -156,23 +186,45 @@ export default {
|
|||
</ref-selector>
|
||||
</form-field-container>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
v-if="showCreateFrom"
|
||||
:label="__('Create from')"
|
||||
:label-for="createFromSelectorId"
|
||||
data-testid="create-from-field"
|
||||
>
|
||||
<form-field-container>
|
||||
<ref-selector
|
||||
:id="createFromSelectorId"
|
||||
v-model="createFromModel"
|
||||
:project-id="projectId"
|
||||
:translations="$options.translations.createFrom"
|
||||
/>
|
||||
</form-field-container>
|
||||
<template #description>
|
||||
{{ __('Existing branch name, tag, or commit SHA') }}
|
||||
</template>
|
||||
</gl-form-group>
|
||||
<gl-collapse :visible="showCreateFrom">
|
||||
<div class="gl-pl-6 gl-border-l-1 gl-border-l-solid gl-border-gray-300">
|
||||
<gl-form-group
|
||||
v-if="showCreateFrom"
|
||||
:label="$options.translations.createFrom.label"
|
||||
:label-for="createFromSelectorId"
|
||||
data-testid="create-from-field"
|
||||
>
|
||||
<form-field-container>
|
||||
<ref-selector
|
||||
:id="createFromSelectorId"
|
||||
v-model="createFromModel"
|
||||
:project-id="projectId"
|
||||
:translations="$options.translations.createFrom"
|
||||
/>
|
||||
</form-field-container>
|
||||
<template #description>{{ $options.translations.createFrom.description }}</template>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
v-if="showCreateFrom"
|
||||
:label="$options.translations.annotatedTag.label"
|
||||
:label-for="$options.tagMessageId"
|
||||
data-testid="annotated-tag-message-field"
|
||||
>
|
||||
<gl-form-textarea :id="$options.tagMessageId" v-model="tagMessage" />
|
||||
<template #description>
|
||||
<gl-sprintf :message="$options.translations.annotatedTag.description">
|
||||
<template #link="{ content }">
|
||||
<gl-link
|
||||
:href="$options.gitTagDocsLink"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>{{ content }}</gl-link
|
||||
>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</template>
|
||||
</gl-form-group>
|
||||
</div>
|
||||
</gl-collapse>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -57,6 +57,9 @@ export const fetchRelease = async ({ commit, state }) => {
|
|||
export const updateReleaseTagName = ({ commit }, tagName) =>
|
||||
commit(types.UPDATE_RELEASE_TAG_NAME, tagName);
|
||||
|
||||
export const updateReleaseTagMessage = ({ commit }, tagMessage) =>
|
||||
commit(types.UPDATE_RELEASE_TAG_MESSAGE, tagMessage);
|
||||
|
||||
export const updateCreateFrom = ({ commit }, createFrom) =>
|
||||
commit(types.UPDATE_CREATE_FROM, createFrom);
|
||||
|
||||
|
|
|
@ -145,6 +145,7 @@ export const releaseCreateMutatationVariables = (state, getters) => {
|
|||
input: {
|
||||
...getters.releaseUpdateMutatationVariables.input,
|
||||
ref: state.createFrom,
|
||||
tagMessage: state.release.tagMessage,
|
||||
assets: {
|
||||
links: getters.releaseLinksToCreate.map(({ name, url, linkType }) => ({
|
||||
name: name.trim(),
|
||||
|
|
|
@ -5,6 +5,7 @@ export const RECEIVE_RELEASE_SUCCESS = 'RECEIVE_RELEASE_SUCCESS';
|
|||
export const RECEIVE_RELEASE_ERROR = 'RECEIVE_RELEASE_ERROR';
|
||||
|
||||
export const UPDATE_RELEASE_TAG_NAME = 'UPDATE_RELEASE_TAG_NAME';
|
||||
export const UPDATE_RELEASE_TAG_MESSAGE = 'UPDATE_RELEASE_TAG_MESSAGE';
|
||||
export const UPDATE_CREATE_FROM = 'UPDATE_CREATE_FROM';
|
||||
export const UPDATE_SHOW_CREATE_FROM = 'UPDATE_SHOW_CREATE_FROM';
|
||||
export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE';
|
||||
|
|
|
@ -10,6 +10,7 @@ export default {
|
|||
[types.INITIALIZE_EMPTY_RELEASE](state) {
|
||||
state.release = {
|
||||
tagName: state.tagName,
|
||||
tagMessage: '',
|
||||
name: '',
|
||||
description: '',
|
||||
milestones: [],
|
||||
|
@ -40,6 +41,9 @@ export default {
|
|||
[types.UPDATE_RELEASE_TAG_NAME](state, tagName) {
|
||||
state.release.tagName = tagName;
|
||||
},
|
||||
[types.UPDATE_RELEASE_TAG_MESSAGE](state, tagMessage) {
|
||||
state.release.tagMessage = tagMessage;
|
||||
},
|
||||
[types.UPDATE_CREATE_FROM](state, createFrom) {
|
||||
state.createFrom = createFrom;
|
||||
},
|
||||
|
|
|
@ -37,7 +37,7 @@ export default ({
|
|||
* When creating a new release, this is the default from the URL
|
||||
*/
|
||||
tagName,
|
||||
showCreateFrom: !tagName,
|
||||
showCreateFrom: false,
|
||||
|
||||
defaultBranch,
|
||||
createFrom: defaultBranch,
|
||||
|
|
|
@ -10,6 +10,8 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def index
|
||||
push_frontend_feature_flag(:vue_broadcast_messages, current_user)
|
||||
|
||||
@broadcast_messages = BroadcastMessage.order(ends_at: :desc).page(params[:page])
|
||||
@broadcast_message = BroadcastMessage.new
|
||||
end
|
||||
|
|
|
@ -50,16 +50,15 @@ module Projects
|
|||
track_event(:error_enable_cloudsql_services)
|
||||
flash[:error] = error_message(enable_response[:message])
|
||||
else
|
||||
permitted_params = params.permit(:gcp_project, :ref, :database_version, :tier)
|
||||
create_response = ::GoogleCloud::CreateCloudsqlInstanceService
|
||||
.new(project, current_user, create_service_params(permitted_params))
|
||||
.new(project, current_user, create_service_params)
|
||||
.execute
|
||||
|
||||
if create_response[:status] == :error
|
||||
track_event(:error_create_cloudsql_instance)
|
||||
flash[:warning] = error_message(create_response[:message])
|
||||
else
|
||||
track_event(:create_cloudsql_instance, permitted_params.to_s)
|
||||
track_event(:create_cloudsql_instance, permitted_params_create.to_s)
|
||||
flash[:notice] = success_message
|
||||
end
|
||||
end
|
||||
|
@ -69,17 +68,25 @@ module Projects
|
|||
|
||||
private
|
||||
|
||||
def enable_service_params
|
||||
{ google_oauth2_token: token_in_session }
|
||||
def permitted_params_create
|
||||
params.permit(:gcp_project, :ref, :database_version, :tier)
|
||||
end
|
||||
|
||||
def create_service_params(permitted_params)
|
||||
def enable_service_params
|
||||
{
|
||||
google_oauth2_token: token_in_session,
|
||||
gcp_project_id: permitted_params[:gcp_project],
|
||||
environment_name: permitted_params[:ref],
|
||||
database_version: permitted_params[:database_version],
|
||||
tier: permitted_params[:tier]
|
||||
gcp_project_id: permitted_params_create[:gcp_project],
|
||||
environment_name: permitted_params_create[:ref]
|
||||
}
|
||||
end
|
||||
|
||||
def create_service_params
|
||||
{
|
||||
google_oauth2_token: token_in_session,
|
||||
gcp_project_id: permitted_params_create[:gcp_project],
|
||||
environment_name: permitted_params_create[:ref],
|
||||
database_version: permitted_params_create[:database_version],
|
||||
tier: permitted_params_create[:tier]
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -667,6 +667,7 @@ class ApplicationSetting < ApplicationRecord
|
|||
attr_encrypted :arkose_labs_public_api_key, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
|
||||
attr_encrypted :arkose_labs_private_api_key, encryption_options_base_32_aes_256_gcm.merge(encode: false, encode_iv: false)
|
||||
attr_encrypted :cube_api_key, encryption_options_base_32_aes_256_gcm
|
||||
attr_encrypted :jitsu_administrator_password, encryption_options_base_32_aes_256_gcm
|
||||
|
||||
validates :disable_feed_token,
|
||||
inclusion: { in: [true, false], message: _('must be a boolean value') }
|
||||
|
|
|
@ -16,6 +16,7 @@ module Ci
|
|||
validates :file, presence: true, file_size: { maximum: FILE_SIZE_LIMIT }
|
||||
validates :checksum, :file_store, :name, :project_id, presence: true
|
||||
validates :name, uniqueness: { scope: :project }
|
||||
validates :metadata, json_schema: { filename: "ci_secure_file_metadata" }, allow_nil: true
|
||||
|
||||
after_initialize :generate_key_data
|
||||
before_validation :assign_checksum
|
||||
|
@ -23,6 +24,8 @@ module Ci
|
|||
scope :order_by_created_at, -> { order(created_at: :desc) }
|
||||
scope :project_id_in, ->(ids) { where(project_id: ids) }
|
||||
|
||||
serialize :metadata, Serializers::Json # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
default_value_for(:file_store) { Ci::SecureFileUploader.default_store }
|
||||
|
||||
mount_file_store_uploader Ci::SecureFileUploader
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
class ProjectAuthorization < ApplicationRecord
|
||||
BATCH_SIZE = 1000
|
||||
SLEEP_DELAY = 0.1
|
||||
|
||||
extend SuppressCompositePrimaryKeyWarning
|
||||
include FromUnion
|
||||
|
||||
|
@ -55,12 +57,16 @@ class ProjectAuthorization < ApplicationRecord
|
|||
end
|
||||
|
||||
private_class_method def self.add_delay_between_batches?(entire_size:, batch_size:)
|
||||
# The reason for adding a delay is to give the replica database enough time to
|
||||
# catch up with the primary when large batches of records are being added/removed.
|
||||
# Hance, we add a delay only if the GitLab installation has a replica database configured.
|
||||
entire_size > batch_size &&
|
||||
!::Gitlab::Database::LoadBalancing.primary_only? &&
|
||||
Feature.enabled?(:enable_minor_delay_during_project_authorizations_refresh)
|
||||
end
|
||||
|
||||
private_class_method def self.perform_delay
|
||||
sleep(0.1)
|
||||
sleep(SLEEP_DELAY)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ class ProjectSetting < ApplicationRecord
|
|||
validates :merge_commit_template, length: { maximum: Project::MAX_COMMIT_TEMPLATE_LENGTH }
|
||||
validates :squash_commit_template, length: { maximum: Project::MAX_COMMIT_TEMPLATE_LENGTH }
|
||||
validates :target_platforms, inclusion: { in: ALLOWED_TARGET_PLATFORMS }
|
||||
validates :suggested_reviewers_enabled, inclusion: { in: [true, false] }
|
||||
|
||||
validate :validates_mr_default_target_self
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module GoogleCloud
|
||||
class EnableCloudsqlService < ::GoogleCloud::BaseService
|
||||
def execute
|
||||
return no_projects_error if unique_gcp_project_ids.empty?
|
||||
create_or_replace_project_vars(environment_name, 'GCP_PROJECT_ID', gcp_project_id, ci_var_protected?)
|
||||
|
||||
unique_gcp_project_ids.each do |gcp_project_id|
|
||||
google_api_client.enable_cloud_sql_admin(gcp_project_id)
|
||||
|
@ -18,8 +18,8 @@ module GoogleCloud
|
|||
|
||||
private
|
||||
|
||||
def no_projects_error
|
||||
error("No GCP projects found. Configure a service account or GCP_PROJECT_ID CI variable.")
|
||||
def ci_var_protected?
|
||||
ProtectedBranch.protected?(project, environment_name) || ProtectedTag.protected?(project, environment_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"description": "CI Secure File Metadata",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"team_name": { "type": "string" },
|
||||
"team_id": { "type": "string" },
|
||||
"app_name": { "type": "string" },
|
||||
"app_id": { "type": "string" },
|
||||
"app_id_prefix": { "type": "string" },
|
||||
"xcode_managed": { "type": "boolean" },
|
||||
"entitlements": { "type": "object" },
|
||||
"devices": { "type": "array" },
|
||||
"certificate_ids": { "type": "array" },
|
||||
"issuer": { "type": "object" },
|
||||
"subject": { "type": "object" }
|
||||
},
|
||||
"additionalProperties": true,
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
}
|
|
@ -31,4 +31,4 @@
|
|||
.form-text.text-muted
|
||||
= _('Only required if not using role instance credentials.')
|
||||
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
||||
= f.submit _('Save changes'), pajamas_button: true
|
||||
|
|
|
@ -21,4 +21,4 @@
|
|||
.form-group
|
||||
= f.gitlab_ui_checkbox_component :user_deactivation_emails_enabled, _('Enable user deactivation emails'), help_text: _('Send emails to users upon account deactivation.')
|
||||
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
|
||||
= f.submit _('Save changes'), pajamas_button: true, data: { qa_selector: 'save_changes_button' }
|
||||
|
|
|
@ -26,4 +26,4 @@
|
|||
= s_('Gitpod|The URL to your Gitpod instance configured to read your GitLab projects, such as https://gitpod.example.com.')
|
||||
- link_start = '<a href="%{url}">'.html_safe % { url: help_page_path('integration/gitpod', anchor: 'enable-gitpod-in-your-user-settings') }
|
||||
= s_('Gitpod|To use the integration, each user must also enable Gitpod on their GitLab account. %{link_start}How do I enable it?%{link_end} ').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
|
||||
= f.submit _('Save changes'), class: 'gl-button btn btn-confirm'
|
||||
= f.submit _('Save changes'), pajamas_button: true
|
||||
|
|
|
@ -15,5 +15,5 @@
|
|||
- time_tracking_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: time_tracking_help_link }
|
||||
= f.gitlab_ui_checkbox_component :time_tracking_limit_to_hours, _('Limit display of time tracking units to hours.'), help_text: _('Display time tracking in issues in total hours only. %{link_start}What is time tracking?%{link_end}').html_safe % { link_start: time_tracking_help_link_start, link_end: '</a>'.html_safe }
|
||||
|
||||
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
|
||||
= f.submit _('Save changes'), pajamas_button: true
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
- targeted_broadcast_messages_enabled = Feature.enabled?(:role_targeted_broadcast_messages)
|
||||
|
||||
- if @broadcast_messages.any?
|
||||
.table-responsive
|
||||
%table.table.b-table.gl-table
|
||||
%thead
|
||||
%tr
|
||||
%th= _('Status')
|
||||
%th= _('Preview')
|
||||
%th= _('Starts')
|
||||
%th= _('Ends')
|
||||
- if targeted_broadcast_messages_enabled
|
||||
%th= _('Target roles')
|
||||
%th= _('Target Path')
|
||||
%th= _('Type')
|
||||
%th
|
||||
%tbody
|
||||
- @broadcast_messages.each do |message|
|
||||
%tr
|
||||
%td
|
||||
= broadcast_message_status(message)
|
||||
%td
|
||||
= broadcast_message(message, preview: true)
|
||||
%td
|
||||
= message.starts_at
|
||||
%td
|
||||
= message.ends_at
|
||||
- if targeted_broadcast_messages_enabled
|
||||
%td
|
||||
= target_access_levels_display(message.target_access_levels)
|
||||
%td
|
||||
= message.target_path
|
||||
%td
|
||||
= message.broadcast_type.capitalize
|
||||
%td.gl-white-space-nowrap<
|
||||
= link_to sprite_icon('pencil', css_class: 'gl-icon'), edit_admin_broadcast_message_path(message), title: _('Edit'), class: 'btn btn-icon gl-button'
|
||||
= link_to sprite_icon('remove', css_class: 'gl-icon'), admin_broadcast_message_path(message), method: :delete, remote: true, title: _('Remove'), class: 'js-remove-tr btn btn-icon gl-button btn-danger gl-ml-3'
|
||||
= paginate @broadcast_messages, theme: 'gitlab'
|
|
@ -1,49 +1,15 @@
|
|||
- breadcrumb_title _("Messages")
|
||||
- page_title _("Broadcast Messages")
|
||||
- targeted_broadcast_messages_enabled = Feature.enabled?(:role_targeted_broadcast_messages)
|
||||
- vue_app_enabled = Feature.enabled?(:vue_broadcast_messages, current_user)
|
||||
|
||||
%h1.page-title.gl-font-size-h-display
|
||||
= _('Broadcast Messages')
|
||||
%p.light
|
||||
= _('Use banners and notifications to notify your users about scheduled maintenance, recent upgrades, and more.')
|
||||
|
||||
= render 'form'
|
||||
|
||||
%br.clearfix
|
||||
|
||||
- if @broadcast_messages.any?
|
||||
.table-responsive
|
||||
%table.table.b-table.gl-table
|
||||
%thead
|
||||
%tr
|
||||
%th= _('Status')
|
||||
%th= _('Preview')
|
||||
%th= _('Starts')
|
||||
%th= _('Ends')
|
||||
- if targeted_broadcast_messages_enabled
|
||||
%th= _('Target roles')
|
||||
%th= _('Target Path')
|
||||
%th= _('Type')
|
||||
%th
|
||||
%tbody
|
||||
- @broadcast_messages.each do |message|
|
||||
%tr
|
||||
%td
|
||||
= broadcast_message_status(message)
|
||||
%td
|
||||
= broadcast_message(message, preview: true)
|
||||
%td
|
||||
= message.starts_at
|
||||
%td
|
||||
= message.ends_at
|
||||
- if targeted_broadcast_messages_enabled
|
||||
%td
|
||||
= target_access_levels_display(message.target_access_levels)
|
||||
%td
|
||||
= message.target_path
|
||||
%td
|
||||
= message.broadcast_type.capitalize
|
||||
%td.gl-white-space-nowrap<
|
||||
= link_to sprite_icon('pencil', css_class: 'gl-icon'), edit_admin_broadcast_message_path(message), title: _('Edit'), class: 'btn btn-icon gl-button'
|
||||
= link_to sprite_icon('remove', css_class: 'gl-icon'), admin_broadcast_message_path(message), method: :delete, remote: true, title: _('Remove'), class: 'js-remove-tr btn btn-icon gl-button btn-danger gl-ml-3'
|
||||
= paginate @broadcast_messages, theme: 'gitlab'
|
||||
- if vue_app_enabled
|
||||
#js-broadcast-messages{ data: { messages: @broadcast_messages.to_json } }
|
||||
- else
|
||||
= render 'form'
|
||||
%br.clearfix
|
||||
= render 'table'
|
||||
|
|
|
@ -137,7 +137,7 @@
|
|||
.row.js-hide-when-nothing-matches-search
|
||||
.col-lg-12
|
||||
%hr
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-confirm gl-mr-3 js-password-prompt-btn'
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'gl-mr-3 js-password-prompt-btn', pajamas_button: true
|
||||
= link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-default btn-cancel'
|
||||
|
||||
#password-prompt-modal
|
||||
|
|
|
@ -16,3 +16,4 @@
|
|||
= f.submit _('Save changes'), class: "btn gl-button btn-confirm rspec-save-merge-request-changes", data: { qa_selector: 'save_merge_request_changes_button' }
|
||||
|
||||
= render_if_exists 'projects/settings/merge_requests/merge_request_approvals_settings', expanded: true
|
||||
= render_if_exists 'projects/settings/merge_requests/suggested_reviewers_settings', expanded: true
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: vue_broadcast_messages
|
||||
introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98127"
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368847
|
||||
milestone: '15.4'
|
||||
type: development
|
||||
group: group::optimize
|
||||
default_enabled: false
|
|
@ -379,6 +379,8 @@
|
|||
- 5
|
||||
- - process_commit
|
||||
- 3
|
||||
- - product_analytics_initialize_analytics
|
||||
- 1
|
||||
- - project_cache
|
||||
- 1
|
||||
- - project_destroy
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddJitsuTrackingColumnsToApplicationSettings < Gitlab::Database::Migration[2.0]
|
||||
def change
|
||||
# rubocop:disable Migration/AddLimitToTextColumns
|
||||
# limit is added in 20220818125703_add_jitsu_tracking_columns_to_application_settings_text_limits.rb
|
||||
add_column :application_settings, :jitsu_host, :text
|
||||
add_column :application_settings, :jitsu_project_xid, :text
|
||||
add_column :application_settings, :clickhouse_connection_string, :text
|
||||
add_column :application_settings, :jitsu_administrator_email, :text
|
||||
add_column :application_settings, :encrypted_jitsu_administrator_password, :binary
|
||||
add_column :application_settings, :encrypted_jitsu_administrator_password_iv, :binary
|
||||
# rubocop:enable Migration/AddLimitToTextColumns
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddJitsuTrackingColumnsToApplicationSettingsTextLimits < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_text_limit :application_settings, :jitsu_host, 255
|
||||
add_text_limit :application_settings, :jitsu_project_xid, 255
|
||||
add_text_limit :application_settings, :clickhouse_connection_string, 1024
|
||||
add_text_limit :application_settings, :jitsu_administrator_email, 255
|
||||
end
|
||||
|
||||
def down
|
||||
remove_text_limit :application_settings, :jitsu_host
|
||||
remove_text_limit :application_settings, :jitsu_project_xid
|
||||
remove_text_limit :application_settings, :clickhouse_connection_string
|
||||
remove_text_limit :application_settings, :jitsu_administrator_email
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSuggestedReviewersEnabledToProjectSettings < Gitlab::Database::Migration[2.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def change
|
||||
add_column :project_settings, :suggested_reviewers_enabled, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSecureFilesMetadata < Gitlab::Database::Migration[2.0]
|
||||
def change
|
||||
add_column :ci_secure_files, :metadata, :jsonb
|
||||
add_column :ci_secure_files, :expires_at, :datetime_with_timezone
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
ebcf446aa6579d93c57c2e96e8b670a43bcb6e20216f33a7f535e1bed50ace62
|
|
@ -0,0 +1 @@
|
|||
b60f36cd83174ce257baba4a74f0fcba6cd462fa2af6530ff5a3341536058e12
|
|
@ -0,0 +1 @@
|
|||
ff995d7a3c23959c4d4e6c6d0adfd338be36f6c07c98bacd26f282d84b2fa33d
|
|
@ -0,0 +1 @@
|
|||
2e20cfa3c1ebe77968ba923b381e0c95cb427613f2bfbed212ced4023bd4334e
|
|
@ -11473,6 +11473,12 @@ CREATE TABLE application_settings (
|
|||
cube_api_base_url text,
|
||||
encrypted_cube_api_key bytea,
|
||||
encrypted_cube_api_key_iv bytea,
|
||||
jitsu_host text,
|
||||
jitsu_project_xid text,
|
||||
clickhouse_connection_string text,
|
||||
jitsu_administrator_email text,
|
||||
encrypted_jitsu_administrator_password bytea,
|
||||
encrypted_jitsu_administrator_password_iv bytea,
|
||||
dashboard_limit_enabled boolean DEFAULT false NOT NULL,
|
||||
dashboard_limit integer DEFAULT 0 NOT NULL,
|
||||
dashboard_notification_limit integer DEFAULT 0 NOT NULL,
|
||||
|
@ -11514,12 +11520,16 @@ CREATE TABLE application_settings (
|
|||
CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)),
|
||||
CONSTRAINT check_a5704163cc CHECK ((char_length(secret_detection_revocation_token_types_url) <= 255)),
|
||||
CONSTRAINT check_d03919528d CHECK ((char_length(container_registry_vendor) <= 255)),
|
||||
CONSTRAINT check_d4865d70f3 CHECK ((char_length(clickhouse_connection_string) <= 1024)),
|
||||
CONSTRAINT check_d820146492 CHECK ((char_length(spam_check_endpoint_url) <= 255)),
|
||||
CONSTRAINT check_dea8792229 CHECK ((char_length(jitsu_host) <= 255)),
|
||||
CONSTRAINT check_e2dd6e290a CHECK ((char_length(jira_connect_application_key) <= 255)),
|
||||
CONSTRAINT check_e5024c8801 CHECK ((char_length(elasticsearch_username) <= 255)),
|
||||
CONSTRAINT check_e5aba18f02 CHECK ((char_length(container_registry_version) <= 255)),
|
||||
CONSTRAINT check_ec3ca9aa8d CHECK ((char_length(jitsu_administrator_email) <= 255)),
|
||||
CONSTRAINT check_ef6176834f CHECK ((char_length(encrypted_cloud_license_auth_token_iv) <= 255)),
|
||||
CONSTRAINT check_f6563bc000 CHECK ((char_length(arkose_labs_verify_api_url) <= 255))
|
||||
CONSTRAINT check_f6563bc000 CHECK ((char_length(arkose_labs_verify_api_url) <= 255)),
|
||||
CONSTRAINT check_fc732c181e CHECK ((char_length(jitsu_project_xid) <= 255))
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN application_settings.content_validation_endpoint_url IS 'JiHu-specific column';
|
||||
|
@ -13357,6 +13367,8 @@ CREATE TABLE ci_secure_files (
|
|||
file text NOT NULL,
|
||||
checksum bytea NOT NULL,
|
||||
key_data text,
|
||||
metadata jsonb,
|
||||
expires_at timestamp with time zone,
|
||||
CONSTRAINT check_320790634d CHECK ((char_length(file) <= 255)),
|
||||
CONSTRAINT check_402c7b4a56 CHECK ((char_length(name) <= 255)),
|
||||
CONSTRAINT check_7279b4e293 CHECK ((char_length(key_data) <= 128))
|
||||
|
@ -20040,6 +20052,7 @@ CREATE TABLE project_settings (
|
|||
enforce_auth_checks_on_uploads boolean DEFAULT true NOT NULL,
|
||||
selective_code_owner_removals boolean DEFAULT false NOT NULL,
|
||||
show_diff_preview_in_email boolean DEFAULT true NOT NULL,
|
||||
suggested_reviewers_enabled boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
|
||||
CONSTRAINT check_b09644994b CHECK ((char_length(squash_commit_template) <= 500)),
|
||||
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)),
|
||||
|
|
|
@ -505,6 +505,7 @@ Parameters:
|
|||
| `bio` | No | User's biography |
|
||||
| `can_create_group` | No | User can create groups - true or false |
|
||||
| `color_scheme_id` | No | User's color scheme for the file viewer (see [the user preference docs](../user/profile/preferences.md#syntax-highlighting-theme) for more information) |
|
||||
| `commit_email` | No | User's commit email, `_private` to use the private commit email. |
|
||||
| `email` | No | Email |
|
||||
| `extern_uid` | No | External UID |
|
||||
| `external` | No | Flags the user as external - true or false (default) |
|
||||
|
|
|
@ -69,6 +69,7 @@ as it can cause the pipeline to behave unexpectedly.
|
|||
| `CI_JOB_JWT_V2` | 14.6 | all | A newly formatted RS256 JSON web token to increase compatibility. Similar to `CI_JOB_JWT`, except the issuer (`iss`) claim is changed from `gitlab.com` to `https://gitlab.com`, `sub` has changed from `job_id` to a string that contains the project path, and an `aud` claim is added. Format is subject to change. Be aware, the `aud` field is a constant value. Trusting JWTs in multiple relying parties can lead to [one RP sending a JWT to another one and acting maliciously as a job](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72555#note_769112331). **Note:** The `CI_JOB_JWT_V2` variable is available for testing, but the full feature is planned to be generally available when [issue 360657](https://gitlab.com/gitlab-org/gitlab/-/issues/360657) is complete.|
|
||||
| `CI_JOB_MANUAL` | 8.12 | all | Only available if the job was started manually. `true` when available. |
|
||||
| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job. |
|
||||
| `CI_JOB_NAME_SLUG` | 15.4 | all | `CI_JOB_NAME_SLUG` in lowercase, shortened to 63 bytes, and with everything except `0-9` and `a-z` replaced with `-`. No leading / trailing `-`. Use in paths. |
|
||||
| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the job's stage. |
|
||||
| `CI_JOB_STATUS` | all | 13.5 | The status of the job as each runner stage is executed. Use with [`after_script`](../yaml/index.md#after_script). Can be `success`, `failed`, or `canceled`. |
|
||||
| `CI_JOB_TOKEN` | 9.0 | 1.2 | A token to authenticate with [certain API endpoints](../jobs/ci_job_token.md). The token is valid as long as the job is running. |
|
||||
|
|
|
@ -77,12 +77,14 @@ To create a release in the Releases page:
|
|||
|
||||
1. On the top bar, select **Main menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Deployments > Releases** and select **New release**.
|
||||
1. From the [**Tag name**](release_fields.md#tag-name) dropdown, either:
|
||||
1. From the [**Tag name**](release_fields.md#tag-name) dropdown list, either:
|
||||
- Select an existing Git tag. Selecting an existing tag that is already associated with a release
|
||||
results in a validation error.
|
||||
- Enter a new Git tag name.
|
||||
1. From the **Create from** dropdown, select a branch or commit SHA to use when creating the
|
||||
new tag.
|
||||
1. From the **Create from** dropdown list, select a branch or commit SHA to use when
|
||||
creating the new tag.
|
||||
1. Optional. In the **Set tag message** text box, enter a message to create an
|
||||
[annotated tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging#_annotated_tags).
|
||||
1. Optional. Enter additional information about the release, including:
|
||||
- [Title](release_fields.md#title).
|
||||
- [Milestones](#associate-milestones-with-a-release).
|
||||
|
|
|
@ -57,3 +57,5 @@ module API
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
API::Support::GitAccessActor.prepend_mod_with('API::Support::GitAccessActor')
|
||||
|
|
|
@ -51,6 +51,7 @@ module API
|
|||
optional :bio, type: String, desc: 'The biography of the user'
|
||||
optional :location, type: String, desc: 'The location of the user'
|
||||
optional :public_email, type: String, desc: 'The public email of the user'
|
||||
optional :commit_email, type: String, desc: 'The commit email, _private for private commit email'
|
||||
optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator'
|
||||
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
|
||||
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
|
||||
|
|
|
@ -118,6 +118,7 @@ module Gitlab
|
|||
def predefined_variables(job)
|
||||
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||
variables.append(key: 'CI_JOB_NAME', value: job.name)
|
||||
variables.append(key: 'CI_JOB_NAME_SLUG', value: job_name_slug(job))
|
||||
variables.append(key: 'CI_JOB_STAGE', value: job.stage_name)
|
||||
variables.append(key: 'CI_JOB_MANUAL', value: 'true') if job.action?
|
||||
variables.append(key: 'CI_PIPELINE_TRIGGERED', value: 'true') if job.trigger_request
|
||||
|
@ -145,6 +146,10 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def job_name_slug(job)
|
||||
job.name && Gitlab::Utils.slugify(job.name)
|
||||
end
|
||||
|
||||
def ci_node_total_value(job)
|
||||
parallel = job.options&.dig(:parallel)
|
||||
parallel = parallel.dig(:total) if parallel.is_a?(Hash)
|
||||
|
|
|
@ -8,7 +8,7 @@ module Sidebars
|
|||
def configure_menu_items
|
||||
add_item(packages_registry_menu_item)
|
||||
add_item(container_registry_menu_item)
|
||||
add_item(harbor_registry__menu_item)
|
||||
add_item(harbor_registry_menu_item)
|
||||
add_item(dependency_proxy_menu_item)
|
||||
true
|
||||
end
|
||||
|
@ -49,8 +49,10 @@ module Sidebars
|
|||
)
|
||||
end
|
||||
|
||||
def harbor_registry__menu_item
|
||||
if Feature.disabled?(:harbor_registry_integration) || context.group.harbor_integration.nil?
|
||||
def harbor_registry_menu_item
|
||||
if Feature.disabled?(:harbor_registry_integration) ||
|
||||
context.group.harbor_integration.nil? ||
|
||||
!context.group.harbor_integration.activated?
|
||||
return nil_menu_item(:harbor_registry)
|
||||
end
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ module Sidebars
|
|||
add_item(packages_registry_menu_item)
|
||||
add_item(container_registry_menu_item)
|
||||
add_item(infrastructure_registry_menu_item)
|
||||
add_item(harbor_registry__menu_item)
|
||||
add_item(harbor_registry_menu_item)
|
||||
true
|
||||
end
|
||||
|
||||
|
@ -65,8 +65,10 @@ module Sidebars
|
|||
)
|
||||
end
|
||||
|
||||
def harbor_registry__menu_item
|
||||
if Feature.disabled?(:harbor_registry_integration, context.project) || context.project.harbor_integration.nil?
|
||||
def harbor_registry_menu_item
|
||||
if Feature.disabled?(:harbor_registry_integration, context.project) ||
|
||||
context.project.harbor_integration.nil? ||
|
||||
!context.project.harbor_integration.activated?
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :harbor_registry)
|
||||
end
|
||||
|
||||
|
|
|
@ -6039,6 +6039,9 @@ msgstr ""
|
|||
msgid "Below you will find all the groups that are public."
|
||||
msgstr ""
|
||||
|
||||
msgid "Beta"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bi-weekly code coverage"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11162,6 +11165,12 @@ msgstr ""
|
|||
msgid "Create, update, or delete a merge request."
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateGitTag|Add a message to the tag. Leaving this blank creates a %{linkStart}lightweight tag%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateGitTag|Set tag message"
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateGroup|You don’t have permission to create a subgroup in this group."
|
||||
msgstr ""
|
||||
|
||||
|
@ -31375,6 +31384,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|Enable merged results pipelines"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Enable suggested reviewers"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Encourage"
|
||||
msgstr ""
|
||||
|
||||
|
@ -38801,6 +38813,15 @@ msgstr ""
|
|||
msgid "SuggestedColors|Titanium yellow"
|
||||
msgstr ""
|
||||
|
||||
msgid "SuggestedReviewers|Get suggestions for reviewers based on GitLab's machine learning tool."
|
||||
msgstr ""
|
||||
|
||||
msgid "SuggestedReviewers|Suggested reviewers"
|
||||
msgstr ""
|
||||
|
||||
msgid "SuggestedReviewers|Suggestions appear in the Reviewer section of the right sidebar"
|
||||
msgstr ""
|
||||
|
||||
msgid "Suggestion is not applicable as the suggestion was not found."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ RSpec.describe 'Admin Broadcast Messages' do
|
|||
before do
|
||||
admin = create(:admin)
|
||||
sign_in(admin)
|
||||
stub_feature_flags(vue_broadcast_messages: false)
|
||||
gitlab_enable_admin_mode_sign_in(admin)
|
||||
create(
|
||||
:broadcast_message,
|
||||
|
|
|
@ -11,6 +11,7 @@ RSpec.describe 'User creates release', :js do
|
|||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:new_page_url) { new_project_release_path(project) }
|
||||
let(:tag_name) { 'new-tag' }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
@ -33,6 +34,8 @@ RSpec.describe 'User creates release', :js do
|
|||
end
|
||||
|
||||
it 'defaults the "Create from" dropdown to the project\'s default branch' do
|
||||
select_new_tag_name(tag_name)
|
||||
|
||||
expect(page.find('[data-testid="create-from-field"] .ref-selector button')).to have_content(project.default_branch)
|
||||
end
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"id",
|
||||
"username",
|
||||
"email",
|
||||
"commit_email",
|
||||
"name",
|
||||
"state",
|
||||
"avatar_url",
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import BroadcastMessagesBase from '~/admin/broadcast_messages/components/base.vue';
|
||||
import MessagesTable from '~/admin/broadcast_messages/components/messages_table.vue';
|
||||
import { MOCK_MESSAGES } from '../mock_data';
|
||||
|
||||
describe('BroadcastMessagesBase', () => {
|
||||
let wrapper;
|
||||
|
||||
const findTable = () => wrapper.findComponent(MessagesTable);
|
||||
|
||||
function createComponent(props = {}) {
|
||||
wrapper = shallowMount(BroadcastMessagesBase, {
|
||||
propsData: {
|
||||
messages: MOCK_MESSAGES,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders the table when there are existing messages', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findTable().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render the table when there are no existing messages', () => {
|
||||
createComponent({ messages: [] });
|
||||
|
||||
expect(findTable().exists()).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import MessagesTableRow from '~/admin/broadcast_messages/components/messages_table_row.vue';
|
||||
import { MOCK_MESSAGE } from '../mock_data';
|
||||
|
||||
describe('MessagesTableRow', () => {
|
||||
let wrapper;
|
||||
|
||||
function createComponent(props = {}) {
|
||||
wrapper = shallowMount(MessagesTableRow, {
|
||||
propsData: {
|
||||
message: MOCK_MESSAGE,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders the message ID', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.text()).toBe(`${MOCK_MESSAGE.id}`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import MessagesTable from '~/admin/broadcast_messages/components/messages_table.vue';
|
||||
import MessagesTableRow from '~/admin/broadcast_messages/components/messages_table_row.vue';
|
||||
import { MOCK_MESSAGES } from '../mock_data';
|
||||
|
||||
describe('MessagesTable', () => {
|
||||
let wrapper;
|
||||
|
||||
const findRows = () => wrapper.findAllComponents(MessagesTableRow);
|
||||
|
||||
function createComponent(props = {}) {
|
||||
wrapper = shallowMount(MessagesTable, {
|
||||
propsData: {
|
||||
messages: MOCK_MESSAGES,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders a table row for each message', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findRows()).toHaveLength(MOCK_MESSAGES.length);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
export const MOCK_MESSAGE = {
|
||||
id: 100,
|
||||
};
|
||||
|
||||
export const MOCK_MESSAGES = [MOCK_MESSAGE, { id: 200 }, { id: 300 }];
|
|
@ -44,13 +44,6 @@ describe('WikiForm', () => {
|
|||
await nextTick();
|
||||
};
|
||||
|
||||
const dispatchBeforeUnload = () => {
|
||||
const e = new Event('beforeunload');
|
||||
jest.spyOn(e, 'preventDefault');
|
||||
window.dispatchEvent(e);
|
||||
return e;
|
||||
};
|
||||
|
||||
const pageInfoNew = {
|
||||
persisted: false,
|
||||
uploadsPath: '/project/path/-/wikis/attachments',
|
||||
|
@ -191,14 +184,6 @@ describe('WikiForm', () => {
|
|||
expect(wrapper.text()).toContain(text);
|
||||
});
|
||||
|
||||
it('starts with no unload warning', () => {
|
||||
createWrapper();
|
||||
|
||||
const e = dispatchBeforeUnload();
|
||||
expect(typeof e.returnValue).not.toBe('string');
|
||||
expect(e.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each`
|
||||
persisted | titleHelpText | titleHelpLink
|
||||
${true} | ${'You can move this page by adding the path to the beginning of the title.'} | ${'/help/user/project/wiki/index#move-a-wiki-page'}
|
||||
|
@ -228,22 +213,11 @@ describe('WikiForm', () => {
|
|||
await findMarkdownEditor().vm.$emit('input', ' Lorem ipsum dolar sit! ');
|
||||
});
|
||||
|
||||
it('sets before unload warning', () => {
|
||||
const e = dispatchBeforeUnload();
|
||||
|
||||
expect(e.preventDefault).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('form submit', () => {
|
||||
beforeEach(async () => {
|
||||
await triggerFormSubmit();
|
||||
});
|
||||
|
||||
it('when form submitted, unsets before unload warning', () => {
|
||||
const e = dispatchBeforeUnload();
|
||||
expect(e.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('triggers wiki format tracking event', () => {
|
||||
expect(trackingSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
@ -341,11 +315,6 @@ describe('WikiForm', () => {
|
|||
await triggerFormSubmit();
|
||||
});
|
||||
|
||||
it('unsets before unload warning on form submit', async () => {
|
||||
const e = dispatchBeforeUnload();
|
||||
expect(e.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('triggers tracking events on form submit', async () => {
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, SAVED_USING_CONTENT_EDITOR_ACTION, {
|
||||
label: WIKI_CONTENT_EDITOR_TRACKING_LABEL,
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
import { GlDropdownItem, GlFormGroup, GlSprintf } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { __ } from '~/locale';
|
||||
import TagFieldNew from '~/releases/components/tag_field_new.vue';
|
||||
import createStore from '~/releases/stores';
|
||||
import createEditNewModule from '~/releases/stores/modules/edit_new';
|
||||
|
||||
const TEST_TAG_NAME = 'test-tag-name';
|
||||
const TEST_TAG_MESSAGE = 'Test tag message';
|
||||
const TEST_PROJECT_ID = '1234';
|
||||
const TEST_CREATE_FROM = 'test-create-from';
|
||||
const NONEXISTENT_TAG_NAME = 'nonexistent-tag';
|
||||
|
@ -47,6 +50,8 @@ describe('releases/components/tag_field_new', () => {
|
|||
store,
|
||||
stubs: {
|
||||
RefSelector: RefSelectorStub,
|
||||
GlFormGroup,
|
||||
GlSprintf,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -61,9 +66,11 @@ describe('releases/components/tag_field_new', () => {
|
|||
});
|
||||
|
||||
store.state.editNew.createFrom = TEST_CREATE_FROM;
|
||||
store.state.editNew.showCreateFrom = true;
|
||||
|
||||
store.state.editNew.release = {
|
||||
tagName: TEST_TAG_NAME,
|
||||
tagMessage: '',
|
||||
assets: {
|
||||
links: [],
|
||||
},
|
||||
|
@ -86,6 +93,9 @@ describe('releases/components/tag_field_new', () => {
|
|||
|
||||
const findCreateNewTagOption = () => wrapper.findComponent(GlDropdownItem);
|
||||
|
||||
const findAnnotatedTagMessageFormGroup = () =>
|
||||
wrapper.find('[data-testid="annotated-tag-message-field"]');
|
||||
|
||||
describe('"Tag name" field', () => {
|
||||
describe('rendering and behavior', () => {
|
||||
beforeEach(() => createComponent());
|
||||
|
@ -124,6 +134,10 @@ describe('releases/components/tag_field_new', () => {
|
|||
expect(findCreateFromFormGroup().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('hides the "Tag message" field', () => {
|
||||
expect(findAnnotatedTagMessageFormGroup().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('fetches the release notes for the tag', () => {
|
||||
const expectedUrl = `/api/v4/projects/1234/repository/tags/${updatedTagName}`;
|
||||
expect(mock.history.get).toContainEqual(expect.objectContaining({ url: expectedUrl }));
|
||||
|
@ -230,4 +244,34 @@ describe('releases/components/tag_field_new', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('"Annotated Tag" field', () => {
|
||||
beforeEach(() => {
|
||||
createComponent(mountExtended);
|
||||
});
|
||||
|
||||
it('renders a label', () => {
|
||||
expect(wrapper.findByRole('textbox', { name: 'Set tag message' }).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a description', () => {
|
||||
expect(trimText(findAnnotatedTagMessageFormGroup().text())).toContain(
|
||||
'Add a message to the tag. Leaving this blank creates a lightweight tag.',
|
||||
);
|
||||
});
|
||||
|
||||
it('updates the store', async () => {
|
||||
await findAnnotatedTagMessageFormGroup().find('textarea').setValue(TEST_TAG_MESSAGE);
|
||||
|
||||
expect(store.state.editNew.release.tagMessage).toBe(TEST_TAG_MESSAGE);
|
||||
});
|
||||
|
||||
it('shows a link', () => {
|
||||
const link = wrapper.findByRole('link', {
|
||||
name: 'lightweight tag',
|
||||
});
|
||||
|
||||
expect(link.attributes('href')).toBe('https://git-scm.com/book/en/v2/Git-Basics-Tagging/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -169,6 +169,15 @@ describe('Release edit/new actions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('updateReleaseTagMessage', () => {
|
||||
it(`commits ${types.UPDATE_RELEASE_TAG_MESSAGE} with the updated tag name`, () => {
|
||||
const newMessage = 'updated-tag-message';
|
||||
return testAction(actions.updateReleaseTagMessage, newMessage, state, [
|
||||
{ type: types.UPDATE_RELEASE_TAG_MESSAGE, payload: newMessage },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateReleasedAt', () => {
|
||||
it(`commits ${types.UPDATE_RELEASED_AT} with the updated date`, () => {
|
||||
const newDate = new Date();
|
||||
|
|
|
@ -332,6 +332,7 @@ describe('Release edit/new getters', () => {
|
|||
it('returns all the data needed for the releaseCreate GraphQL query', () => {
|
||||
const state = {
|
||||
createFrom: 'main',
|
||||
release: { tagMessage: 'hello' },
|
||||
};
|
||||
|
||||
const otherGetters = {
|
||||
|
@ -352,6 +353,7 @@ describe('Release edit/new getters', () => {
|
|||
const expectedVariables = {
|
||||
input: {
|
||||
name: 'release.name',
|
||||
tagMessage: 'hello',
|
||||
ref: 'main',
|
||||
assets: {
|
||||
links: [
|
||||
|
|
|
@ -26,6 +26,7 @@ describe('Release edit/new mutations', () => {
|
|||
|
||||
expect(state.release).toEqual({
|
||||
tagName: 'v1.3',
|
||||
tagMessage: '',
|
||||
name: '',
|
||||
description: '',
|
||||
milestones: [],
|
||||
|
@ -90,6 +91,16 @@ describe('Release edit/new mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(`${types.UPDATE_RELEASE_TAG_MESSAGE}`, () => {
|
||||
it("updates the release's tag message", () => {
|
||||
state.release = release;
|
||||
const newMessage = 'updated-tag-message';
|
||||
mutations[types.UPDATE_RELEASE_TAG_MESSAGE](state, newMessage);
|
||||
|
||||
expect(state.release.tagMessage).toBe(newMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${types.UPDATE_RELEASED_AT}`, () => {
|
||||
it("updates the release's released at date", () => {
|
||||
state.release = release;
|
||||
|
|
|
@ -10,6 +10,7 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be_with_reload(:job) do
|
||||
create(:ci_build,
|
||||
name: 'rspec:test 1',
|
||||
pipeline: pipeline,
|
||||
user: user,
|
||||
yaml_variables: [{ key: 'YAML_VARIABLE', value: 'value' }]
|
||||
|
@ -24,13 +25,15 @@ RSpec.describe Gitlab::Ci::Variables::Builder, :clean_gitlab_redis_cache do
|
|||
let(:predefined_variables) do
|
||||
[
|
||||
{ key: 'CI_JOB_NAME',
|
||||
value: job.name },
|
||||
value: 'rspec:test 1' },
|
||||
{ key: 'CI_JOB_NAME_SLUG',
|
||||
value: 'rspec-test-1' },
|
||||
{ key: 'CI_JOB_STAGE',
|
||||
value: job.stage_name },
|
||||
{ key: 'CI_NODE_TOTAL',
|
||||
value: '1' },
|
||||
{ key: 'CI_BUILD_NAME',
|
||||
value: job.name },
|
||||
value: 'rspec:test 1' },
|
||||
{ key: 'CI_BUILD_STAGE',
|
||||
value: job.stage_name },
|
||||
{ key: 'CI',
|
||||
|
|
|
@ -207,6 +207,16 @@ RSpec.describe Sidebars::Groups::Menus::PackagesRegistriesMenu do
|
|||
|
||||
it_behaves_like 'the menu entry is available'
|
||||
end
|
||||
|
||||
context 'when config harbor registry setting is not activated' do
|
||||
before do
|
||||
harbor_integration.update!(active: false)
|
||||
end
|
||||
|
||||
let(:harbor_registry_enabled) { true }
|
||||
|
||||
it_behaves_like 'the menu entry is not available'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -166,6 +166,15 @@ RSpec.describe Sidebars::Projects::Menus::PackagesRegistriesMenu do
|
|||
is_expected.not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when config harbor registry setting is not activated' do
|
||||
it 'does not add the menu item to the list' do
|
||||
stub_feature_flags(harbor_registry_integration: true)
|
||||
project.harbor_integration.update!(active: false)
|
||||
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,9 +86,9 @@ RSpec.describe Ci::Bridge do
|
|||
describe '#scoped_variables' do
|
||||
it 'returns a hash representing variables' do
|
||||
variables = %w[
|
||||
CI_JOB_NAME CI_JOB_STAGE CI_COMMIT_SHA CI_COMMIT_SHORT_SHA
|
||||
CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME CI_COMMIT_REF_SLUG
|
||||
CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH
|
||||
CI_JOB_NAME CI_JOB_NAME_SLUG CI_JOB_STAGE CI_COMMIT_SHA
|
||||
CI_COMMIT_SHORT_SHA CI_COMMIT_BEFORE_SHA CI_COMMIT_REF_NAME
|
||||
CI_COMMIT_REF_SLUG CI_PROJECT_ID CI_PROJECT_NAME CI_PROJECT_PATH
|
||||
CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE
|
||||
CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
|
||||
CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED
|
||||
|
|
|
@ -2668,6 +2668,7 @@ RSpec.describe Ci::Build do
|
|||
{ key: 'CI_JOB_JWT_V1', value: 'ci.job.jwt', public: false, masked: true },
|
||||
{ key: 'CI_JOB_JWT_V2', value: 'ci.job.jwtv2', public: false, masked: true },
|
||||
{ key: 'CI_JOB_NAME', value: 'test', public: true, masked: false },
|
||||
{ key: 'CI_JOB_NAME_SLUG', value: 'test', public: true, masked: false },
|
||||
{ key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false },
|
||||
{ key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false },
|
||||
{ key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false },
|
||||
|
|
|
@ -101,10 +101,21 @@ RSpec.describe ProjectAuthorization do
|
|||
end
|
||||
|
||||
before do
|
||||
stub_const("#{described_class.name}::BATCH_SIZE", per_batch_size)
|
||||
# Configure as if a replica database is enabled
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive(:primary_only?).and_return(false)
|
||||
stub_feature_flags(enable_minor_delay_during_project_authorizations_refresh: true)
|
||||
end
|
||||
|
||||
shared_examples_for 'inserts the rows in batches, as per the `per_batch` size, without a delay between each batch' do
|
||||
specify do
|
||||
expect(described_class).not_to receive(:sleep)
|
||||
|
||||
described_class.insert_all_in_batches(attributes, per_batch_size)
|
||||
|
||||
expect(user.project_authorizations.pluck(:user_id, :project_id, :access_level)).to match_array(attributes.map(&:values))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the total number of records to be inserted is greater than the batch size' do
|
||||
let(:per_batch_size) { 2 }
|
||||
|
||||
|
@ -116,19 +127,21 @@ RSpec.describe ProjectAuthorization do
|
|||
|
||||
expect(user.project_authorizations.pluck(:user_id, :project_id, :access_level)).to match_array(attributes.map(&:values))
|
||||
end
|
||||
|
||||
context 'when the GitLab installation does not have a replica database configured' do
|
||||
before do
|
||||
# Configure as if a replica database is not enabled
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive(:primary_only?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'inserts the rows in batches, as per the `per_batch` size, without a delay between each batch'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the total number of records to be inserted is less than the batch size' do
|
||||
let(:per_batch_size) { 5 }
|
||||
|
||||
it 'inserts the rows in batches, as per the `per_batch` size, without a delay between each batch' do
|
||||
expect(described_class).to receive(:insert_all).once.and_call_original
|
||||
expect(described_class).not_to receive(:sleep)
|
||||
|
||||
described_class.insert_all_in_batches(attributes, per_batch_size)
|
||||
|
||||
expect(user.project_authorizations.pluck(:user_id, :project_id, :access_level)).to match_array(attributes.map(&:values))
|
||||
end
|
||||
it_behaves_like 'inserts the rows in batches, as per the `per_batch` size, without a delay between each batch'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -142,7 +155,8 @@ RSpec.describe ProjectAuthorization do
|
|||
let(:user_ids) { [user_1.id, user_2.id, user_3.id] }
|
||||
|
||||
before do
|
||||
stub_const("#{described_class.name}::BATCH_SIZE", per_batch_size)
|
||||
# Configure as if a replica database is enabled
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive(:primary_only?).and_return(false)
|
||||
stub_feature_flags(enable_minor_delay_during_project_authorizations_refresh: true)
|
||||
end
|
||||
|
||||
|
@ -153,6 +167,20 @@ RSpec.describe ProjectAuthorization do
|
|||
create(:project_authorization, user: user_4, project: project)
|
||||
end
|
||||
|
||||
shared_examples_for 'removes the project authorizations of the specified users in the current project, without a delay between each batch' do
|
||||
specify do
|
||||
expect(described_class).not_to receive(:sleep)
|
||||
|
||||
described_class.delete_all_in_batches_for_project(
|
||||
project: project,
|
||||
user_ids: user_ids,
|
||||
per_batch: per_batch_size
|
||||
)
|
||||
|
||||
expect(project.project_authorizations.pluck(:user_id)).not_to include(*user_ids)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the total number of records to be removed is greater than the batch size' do
|
||||
let(:per_batch_size) { 2 }
|
||||
|
||||
|
@ -167,22 +195,21 @@ RSpec.describe ProjectAuthorization do
|
|||
|
||||
expect(project.project_authorizations.pluck(:user_id)).not_to include(*user_ids)
|
||||
end
|
||||
|
||||
context 'when the GitLab installation does not have a replica database configured' do
|
||||
before do
|
||||
# Configure as if a replica database is not enabled
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive(:primary_only?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'removes the project authorizations of the specified users in the current project, without a delay between each batch'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the total number of records to be removed is less than the batch size' do
|
||||
let(:per_batch_size) { 5 }
|
||||
|
||||
it 'removes the project authorizations of the specified users in the current project, without a delay between each batch' do
|
||||
expect(described_class).not_to receive(:sleep)
|
||||
|
||||
described_class.delete_all_in_batches_for_project(
|
||||
project: project,
|
||||
user_ids: user_ids,
|
||||
per_batch: per_batch_size
|
||||
)
|
||||
|
||||
expect(project.project_authorizations.pluck(:user_id)).not_to include(*user_ids)
|
||||
end
|
||||
it_behaves_like 'removes the project authorizations of the specified users in the current project, without a delay between each batch'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -196,7 +223,8 @@ RSpec.describe ProjectAuthorization do
|
|||
let(:project_ids) { [project_1.id, project_2.id, project_3.id] }
|
||||
|
||||
before do
|
||||
stub_const("#{described_class.name}::BATCH_SIZE", per_batch_size)
|
||||
# Configure as if a replica database is enabled
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive(:primary_only?).and_return(false)
|
||||
stub_feature_flags(enable_minor_delay_during_project_authorizations_refresh: true)
|
||||
end
|
||||
|
||||
|
@ -207,26 +235,8 @@ RSpec.describe ProjectAuthorization do
|
|||
create(:project_authorization, user: user, project: project_4)
|
||||
end
|
||||
|
||||
context 'when the total number of records to be removed is greater than the batch size' do
|
||||
let(:per_batch_size) { 2 }
|
||||
|
||||
it 'removes the project authorizations of the specified users in the current project, with a delay between each batch' do
|
||||
expect(described_class).to receive(:sleep).twice
|
||||
|
||||
described_class.delete_all_in_batches_for_user(
|
||||
user: user,
|
||||
project_ids: project_ids,
|
||||
per_batch: per_batch_size
|
||||
)
|
||||
|
||||
expect(user.project_authorizations.pluck(:project_id)).not_to include(*project_ids)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the total number of records to be removed is less than the batch size' do
|
||||
let(:per_batch_size) { 5 }
|
||||
|
||||
it 'removes the project authorizations of the specified users in the current project, without a delay between each batch' do
|
||||
shared_examples_for 'removes the project authorizations of the specified projects from the current user, without a delay between each batch' do
|
||||
specify do
|
||||
expect(described_class).not_to receive(:sleep)
|
||||
|
||||
described_class.delete_all_in_batches_for_user(
|
||||
|
@ -238,5 +248,36 @@ RSpec.describe ProjectAuthorization do
|
|||
expect(user.project_authorizations.pluck(:project_id)).not_to include(*project_ids)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the total number of records to be removed is greater than the batch size' do
|
||||
let(:per_batch_size) { 2 }
|
||||
|
||||
it 'removes the project authorizations of the specified projects from the current user, with a delay between each batch' do
|
||||
expect(described_class).to receive(:sleep).twice
|
||||
|
||||
described_class.delete_all_in_batches_for_user(
|
||||
user: user,
|
||||
project_ids: project_ids,
|
||||
per_batch: per_batch_size
|
||||
)
|
||||
|
||||
expect(user.project_authorizations.pluck(:project_id)).not_to include(*project_ids)
|
||||
end
|
||||
|
||||
context 'when the GitLab installation does not have a replica database configured' do
|
||||
before do
|
||||
# Configure as if a replica database is not enabled
|
||||
allow(::Gitlab::Database::LoadBalancing).to receive(:primary_only?).and_return(true)
|
||||
end
|
||||
|
||||
it_behaves_like 'removes the project authorizations of the specified projects from the current user, without a delay between each batch'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the total number of records to be removed is less than the batch size' do
|
||||
let(:per_batch_size) { 5 }
|
||||
|
||||
it_behaves_like 'removes the project authorizations of the specified projects from the current user, without a delay between each batch'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,10 @@ RSpec.describe ProjectSetting, type: :model do
|
|||
it { is_expected.not_to allow_value(nil).for(:target_platforms) }
|
||||
it { is_expected.to allow_value([]).for(:target_platforms) }
|
||||
|
||||
it { is_expected.not_to allow_value(nil).for(:suggested_reviewers_enabled) }
|
||||
it { is_expected.to allow_value(true).for(:suggested_reviewers_enabled) }
|
||||
it { is_expected.to allow_value(false).for(:suggested_reviewers_enabled) }
|
||||
|
||||
it 'allows any combination of the allowed target platforms' do
|
||||
valid_target_platform_combinations.each do |target_platforms|
|
||||
expect(subject).to allow_value(target_platforms).for(:target_platforms)
|
||||
|
|
|
@ -161,6 +161,7 @@ project_setting:
|
|||
- target_platforms
|
||||
- selective_code_owner_removals
|
||||
- show_diff_preview_in_email
|
||||
- suggested_reviewers_enabled
|
||||
|
||||
build_service_desk_setting: # service_desk_setting
|
||||
unexposed_attributes:
|
||||
|
|
|
@ -4,15 +4,29 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe GoogleCloud::EnableCloudsqlService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:params) do
|
||||
{
|
||||
google_oauth2_token: 'mock-token',
|
||||
gcp_project_id: 'mock-gcp-project-id',
|
||||
environment_name: 'main'
|
||||
}
|
||||
end
|
||||
|
||||
subject(:result) { described_class.new(project).execute }
|
||||
subject(:result) { described_class.new(project, user, params).execute }
|
||||
|
||||
context 'when a project does not have any GCP_PROJECT_IDs configured' do
|
||||
it 'returns error' do
|
||||
message = 'No GCP projects found. Configure a service account or GCP_PROJECT_ID CI variable.'
|
||||
it 'creates GCP_PROJECT_ID project var' do
|
||||
expect_next_instance_of(GoogleApi::CloudPlatform::Client) do |instance|
|
||||
expect(instance).to receive(:enable_cloud_sql_admin).with('mock-gcp-project-id')
|
||||
expect(instance).to receive(:enable_compute).with('mock-gcp-project-id')
|
||||
expect(instance).to receive(:enable_service_networking).with('mock-gcp-project-id')
|
||||
end
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message]).to eq(message)
|
||||
expect(result[:status]).to eq(:success)
|
||||
expect(project.variables.count).to eq(1)
|
||||
expect(project.variables.first.key).to eq('GCP_PROJECT_ID')
|
||||
expect(project.variables.first.value).to eq('mock-gcp-project-id')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -30,6 +44,9 @@ RSpec.describe GoogleCloud::EnableCloudsqlService do
|
|||
|
||||
it 'enables cloudsql, compute and service networking Google APIs', :aggregate_failures do
|
||||
expect_next_instance_of(GoogleApi::CloudPlatform::Client) do |instance|
|
||||
expect(instance).to receive(:enable_cloud_sql_admin).with('mock-gcp-project-id')
|
||||
expect(instance).to receive(:enable_compute).with('mock-gcp-project-id')
|
||||
expect(instance).to receive(:enable_service_networking).with('mock-gcp-project-id')
|
||||
expect(instance).to receive(:enable_cloud_sql_admin).with('prj-prod')
|
||||
expect(instance).to receive(:enable_compute).with('prj-prod')
|
||||
expect(instance).to receive(:enable_service_networking).with('prj-prod')
|
||||
|
@ -44,6 +61,9 @@ RSpec.describe GoogleCloud::EnableCloudsqlService do
|
|||
context 'when Google APIs raise an error' do
|
||||
it 'returns error result' do
|
||||
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |instance|
|
||||
allow(instance).to receive(:enable_cloud_sql_admin).with('mock-gcp-project-id')
|
||||
allow(instance).to receive(:enable_compute).with('mock-gcp-project-id')
|
||||
allow(instance).to receive(:enable_service_networking).with('mock-gcp-project-id')
|
||||
allow(instance).to receive(:enable_cloud_sql_admin).with('prj-prod')
|
||||
allow(instance).to receive(:enable_compute).with('prj-prod')
|
||||
allow(instance).to receive(:enable_service_networking).with('prj-prod')
|
||||
|
|
|
@ -35,6 +35,34 @@ RSpec.shared_examples 'edits content using the content editor' do
|
|||
attach_file('content_editor_image', Rails.root.join('spec', 'fixtures', fixture_name), make_visible: true)
|
||||
end
|
||||
|
||||
def wait_until_hidden_field_is_updated(value)
|
||||
expect(page).to have_field('wiki[content]', with: value, type: 'hidden')
|
||||
end
|
||||
|
||||
it 'saves page content in local storage if the user navigates away' do
|
||||
switch_to_content_editor
|
||||
|
||||
expect(page).to have_css(content_editor_testid)
|
||||
|
||||
type_in_content_editor ' Typing text in the content editor'
|
||||
|
||||
wait_until_hidden_field_is_updated /Typing text in the content editor/
|
||||
|
||||
refresh
|
||||
|
||||
expect(page).to have_text('Typing text in the content editor')
|
||||
|
||||
refresh # also retained after second refresh
|
||||
|
||||
expect(page).to have_text('Typing text in the content editor')
|
||||
|
||||
click_link 'Cancel' # draft is deleted on cancel
|
||||
|
||||
page.go_back
|
||||
|
||||
expect(page).not_to have_text('Typing text in the content editor')
|
||||
end
|
||||
|
||||
describe 'formatting bubble menu' do
|
||||
it 'shows a formatting bubble menu for a regular paragraph and headings' do
|
||||
switch_to_content_editor
|
||||
|
|
|
@ -147,6 +147,18 @@ RSpec.shared_examples 'User creates wiki page' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'saves page content in local storage if the user navigates away', :js do
|
||||
fill_in(:wiki_title, with: "Test title")
|
||||
fill_in(:wiki_content, with: "This is a test")
|
||||
fill_in(:wiki_message, with: "Test commit message")
|
||||
|
||||
refresh
|
||||
|
||||
expect(page).to have_field(:wiki_title, with: "Test title")
|
||||
expect(page).to have_field(:wiki_content, with: "This is a test")
|
||||
expect(page).to have_field(:wiki_message, with: "Test commit message")
|
||||
end
|
||||
|
||||
it 'creates a wiki page with Org markup', :aggregate_failures, :js do
|
||||
org_content = <<~ORG
|
||||
* Heading
|
||||
|
|
|
@ -78,6 +78,18 @@ RSpec.shared_examples 'User updates wiki page' do
|
|||
expect(page).to have_content('My awesome wiki!')
|
||||
end
|
||||
|
||||
it 'saves page content in local storage if the user navigates away', :js do
|
||||
fill_in(:wiki_title, with: "Test title")
|
||||
fill_in(:wiki_content, with: "This is a test")
|
||||
fill_in(:wiki_message, with: "Test commit message")
|
||||
|
||||
refresh
|
||||
|
||||
expect(page).to have_field(:wiki_title, with: "Test title")
|
||||
expect(page).to have_field(:wiki_content, with: "This is a test")
|
||||
expect(page).to have_field(:wiki_message, with: "Test commit message")
|
||||
end
|
||||
|
||||
it 'updates the commit message as the title is changed', :js do
|
||||
fill_in(:wiki_title, with: '& < > \ \ { } &')
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ RSpec.shared_examples 'requires valid Google Oauth2 token' do
|
|||
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
|
||||
allow(client).to receive(:validate_token).and_return(true)
|
||||
allow(client).to receive(:list_projects).and_return(mock_gcp_projects) if mock_gcp_projects
|
||||
allow(client).to receive(:create_cloudsql_instance)
|
||||
end
|
||||
|
||||
allow_next_instance_of(BranchesFinder) do |finder|
|
||||
|
|
|
@ -3,20 +3,22 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'admin/broadcast_messages/index' do
|
||||
let(:role_targeted_broadcast_messages) { true }
|
||||
let(:vue_broadcast_messages) { false }
|
||||
|
||||
let_it_be(:message) { create(:broadcast_message, broadcast_type: 'banner', target_access_levels: [Gitlab::Access::GUEST, Gitlab::Access::DEVELOPER]) }
|
||||
|
||||
before do
|
||||
assign(:broadcast_messages, BroadcastMessage.page(1))
|
||||
assign(:broadcast_message, BroadcastMessage.new)
|
||||
|
||||
stub_feature_flags(role_targeted_broadcast_messages: role_targeted_broadcast_messages)
|
||||
stub_feature_flags(vue_broadcast_messages: vue_broadcast_messages)
|
||||
|
||||
render
|
||||
end
|
||||
|
||||
describe 'Target roles select and table column' do
|
||||
let(:feature_flag_state) { true }
|
||||
|
||||
let_it_be(:message) { create(:broadcast_message, broadcast_type: 'banner', target_access_levels: [Gitlab::Access::GUEST, Gitlab::Access::DEVELOPER]) }
|
||||
|
||||
before do
|
||||
assign(:broadcast_messages, BroadcastMessage.page(1))
|
||||
assign(:broadcast_message, BroadcastMessage.new)
|
||||
|
||||
stub_feature_flags(role_targeted_broadcast_messages: feature_flag_state)
|
||||
|
||||
render
|
||||
end
|
||||
|
||||
it 'rendered' do
|
||||
expect(rendered).to have_content('Target roles')
|
||||
expect(rendered).to have_content('Owner')
|
||||
|
@ -24,7 +26,7 @@ RSpec.describe 'admin/broadcast_messages/index' do
|
|||
end
|
||||
|
||||
context 'when feature flag is off' do
|
||||
let(:feature_flag_state) { false }
|
||||
let(:role_targeted_broadcast_messages) { false }
|
||||
|
||||
it 'is not rendered' do
|
||||
expect(rendered).not_to have_content('Target roles')
|
||||
|
@ -33,4 +35,18 @@ RSpec.describe 'admin/broadcast_messages/index' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Vue application' do
|
||||
it 'is not rendered' do
|
||||
expect(rendered).not_to have_selector('#js-broadcast-messages')
|
||||
end
|
||||
|
||||
context 'when feature flag is on' do
|
||||
let(:vue_broadcast_messages) { true }
|
||||
|
||||
it 'is rendered' do
|
||||
expect(rendered).to have_selector('#js-broadcast-messages')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1 +1 @@
|
|||
golang 1.17.9
|
||||
golang 1.18.6
|
||||
|
|
Loading…
Reference in New Issue