Commit 6b75320f authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 4226aca4
......@@ -26,7 +26,7 @@ export default {
if (log.append) {
if (isNewJobLogActive()) {
state.trace = updateIncrementalTrace(log.lines, state.trace);
state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
} else {
state.trace += log.html;
}
......@@ -35,9 +35,9 @@ export default {
// When the job still does not have a trace
// the trace response will not have a defined
// html or size. We keep the old value otherwise these
// will be set to `undefined`
// will be set to `null`
if (isNewJobLogActive()) {
state.trace = logLinesParser(log.lines) || state.trace;
state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
} else {
state.trace = log.html || state.trace;
}
......
......@@ -147,13 +147,15 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => {
const firstNew = newLog[0];
if (last.offset === firstNew.offset || (last.line && last.line.offset === firstNew.offset)) {
cloneOldLog.splice(lastIndex);
} else if (last.lines && last.lines.length) {
const lastNestedIndex = last.lines.length - 1;
const lastNested = last.lines[lastNestedIndex];
if (lastNested.offset === firstNew.offset) {
last.lines.splice(lastNestedIndex);
if (last && firstNew) {
if (last.offset === firstNew.offset || (last.line && last.line.offset === firstNew.offset)) {
cloneOldLog.splice(lastIndex);
} else if (last.lines && last.lines.length) {
const lastNestedIndex = last.lines.length - 1;
const lastNested = last.lines[lastNestedIndex];
if (lastNested.offset === firstNew.offset) {
last.lines.splice(lastNestedIndex);
}
}
}
......@@ -170,7 +172,7 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => {
* @param array oldLog
* @param array newLog
*/
export const updateIncrementalTrace = (newLog, oldParsed = []) => {
export const updateIncrementalTrace = (newLog = [], oldParsed = []) => {
const parsedLog = findOffsetAndRemove(newLog, oldParsed);
return logLinesParser(newLog, parsedLog);
......
import initRegistryImages from '~/registry';
document.addEventListener('DOMContentLoaded', initRegistryImages);
......@@ -2,17 +2,19 @@
import { mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import store from '../stores';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import CollapsibleContainer from './collapsible_container.vue';
import ProjectEmptyState from './project_empty_state.vue';
import GroupEmptyState from './group_empty_state.vue';
import { s__, sprintf } from '../../locale';
export default {
name: 'RegistryListApp',
components: {
clipboardButton,
CollapsibleContainer,
GlEmptyState,
GlLoadingIcon,
ProjectEmptyState,
GroupEmptyState,
},
props: {
characterError: {
......@@ -38,19 +40,27 @@ export default {
},
personalAccessTokensHelpLink: {
type: String,
required: true,
required: false,
default: null,
},
registryHostUrlWithPort: {
type: String,
required: true,
required: false,
default: null,
},
repositoryUrl: {
type: String,
required: true,
},
isGroupPage: {
type: Boolean,
default: false,
required: false,
},
twoFactorAuthHelpLink: {
type: String,
required: true,
required: false,
default: null,
},
},
store,
......@@ -91,37 +101,10 @@ export default {
false,
);
},
notLoggedInToRegistryText() {
return sprintf(
s__(`ContainerRegistry|If you are not already logged in, you need to authenticate to
the Container Registry by using your GitLab username and password. If you have
%{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a
%{personalAccessTokensDocLinkStart}Personal Access Token
%{personalAccessTokensDocLinkEnd}instead of a password.`),
{
twofaDocLinkStart: `<a href="${this.twoFactorAuthHelpLink}" target="_blank">`,
twofaDocLinkEnd: '</a>',
personalAccessTokensDocLinkStart: `<a href="${this.personalAccessTokensHelpLink}" target="_blank">`,
personalAccessTokensDocLinkEnd: '</a>',
},
false,
);
},
dockerLoginCommand() {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `docker login ${this.registryHostUrlWithPort}`;
},
dockerBuildCommand() {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `docker build -t ${this.repositoryUrl} .`;
},
dockerPushCommand() {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `docker push ${this.repositoryUrl}`;
},
},
created() {
this.setMainEndpoint(this.endpoint);
this.setIsDeleteDisabled(this.isGroupPage);
},
mounted() {
if (!this.characterError) {
......@@ -129,7 +112,7 @@ export default {
}
},
methods: {
...mapActions(['setMainEndpoint', 'fetchRepos']),
...mapActions(['setMainEndpoint', 'fetchRepos', 'setIsDeleteDisabled']),
},
};
</script>
......@@ -152,57 +135,19 @@ export default {
<p v-html="introText"></p>
<collapsible-container v-for="item in repos" :key="item.id" :repo="item" />
</div>
<gl-empty-state
v-else
:title="s__('ContainerRegistry|There are no container images stored for this project')"
:svg-path="noContainersImage"
class="container-message"
>
<template #description>
<p class="js-no-container-images-text" v-html="noContainerImagesText"></p>
<h5>{{ s__('ContainerRegistry|Quick Start') }}</h5>
<p class="js-not-logged-in-to-registry-text" v-html="notLoggedInToRegistryText"></p>
<div class="input-group append-bottom-10">
<input :value="dockerLoginCommand" type="text" class="form-control monospace" readonly />
<span class="input-group-append">
<clipboard-button
:text="dockerLoginCommand"
:title="s__('ContainerRegistry|Copy login command')"
class="input-group-text"
/>
</span>
</div>
<p>
{{
s__(
'ContainerRegistry|You can add an image to this registry with the following commands:',
)
}}
</p>
<div class="input-group append-bottom-10">
<input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly />
<span class="input-group-append">
<clipboard-button
:text="dockerBuildCommand"
:title="s__('ContainerRegistry|Copy build command')"
class="input-group-text"
/>
</span>
</div>
<div class="input-group">
<input :value="dockerPushCommand" type="text" class="form-control monospace" readonly />
<span class="input-group-append">
<clipboard-button
:text="dockerPushCommand"
:title="s__('ContainerRegistry|Copy push command')"
class="input-group-text"
/>
</span>
</div>
</template>
</gl-empty-state>
<project-empty-state
v-else-if="!isGroupPage"
:no-containers-image="noContainersImage"
:help-page-path="helpPagePath"
:repository-url="repositoryUrl"
:two-factor-auth-help-link="twoFactorAuthHelpLink"
:personal-access-tokens-help-link="personalAccessTokensHelpLink"
:registry-host-url-with-port="registryHostUrlWithPort"
/>
<group-empty-state
v-else-if="isGroupPage"
:no-containers-image="noContainersImage"
:help-page-path="helpPagePath"
/>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import { mapActions, mapGetters } from 'vuex';
import { GlLoadingIcon, GlButton, GlTooltipDirective, GlModal, GlModalDirective } from '@gitlab/ui';
import createFlash from '../../flash';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
......@@ -35,9 +35,13 @@ export default {
};
},
computed: {
...mapGetters(['isDeleteDisabled']),
iconName() {
return this.isOpen ? 'angle-up' : 'angle-right';
},
canDeleteRepo() {
return this.repo.canDelete && !this.isDeleteDisabled;
},
},
methods: {
...mapActions(['fetchRepos', 'fetchList', 'deleteItem']),
......@@ -80,7 +84,7 @@ export default {
<div class="controls d-none d-sm-block float-right">
<gl-button
v-if="repo.canDelete"
v-if="canDeleteRepo"
v-gl-tooltip
v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove repository')"
......@@ -98,7 +102,7 @@ export default {
<gl-loading-icon v-if="repo.isLoading" size="md" class="append-bottom-20" />
<div v-else-if="!repo.isLoading && isOpen" class="container-image-tags">
<table-registry v-if="repo.list.length" :repo="repo" />
<table-registry v-if="repo.list.length" :repo="repo" :can-delete-repo="canDeleteRepo" />
<div v-else class="nothing-here-block">
{{ s__('ContainerRegistry|No tags in Container Registry for this container image.') }}
......
<script>
import { GlEmptyState } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
export default {
name: 'GroupEmptyState',
components: {
GlEmptyState,
},
props: {
noContainersImage: {
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
},
computed: {
noContainerImagesText() {
return sprintf(
s__(
`ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}`,
),
{
docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`,
docLinkEnd: '</a>',
},
false,
);
},
},
};
</script>
<template>
<gl-empty-state
:title="s__('ContainerRegistry|There are no container images available in this group')"
:svg-path="noContainersImage"
class="container-message"
>
<template #description>
<p class="js-no-container-images-text" v-html="noContainerImagesText"></p>
</template>
</gl-empty-state>
</template>
<script>
import { GlEmptyState } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { s__, sprintf } from '~/locale';
export default {
name: 'ProjectEmptyState',
components: {
ClipboardButton,
GlEmptyState,
},
props: {
noContainersImage: {
type: String,
required: true,
},
repositoryUrl: {
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
twoFactorAuthHelpLink: {
type: String,
required: true,
},
personalAccessTokensHelpLink: {
type: String,
required: true,
},
registryHostUrlWithPort: {
type: String,
required: true,
},
},
computed: {
dockerBuildCommand() {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `docker build -t ${this.repositoryUrl} .`;
},
dockerPushCommand() {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `docker push ${this.repositoryUrl}`;
},
dockerLoginCommand() {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `docker login ${this.registryHostUrlWithPort}`;
},
noContainerImagesText() {
return sprintf(
s__(`ContainerRegistry|With the Container Registry, every project can have its own space to
store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`),
{
docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`,
docLinkEnd: '</a>',
},
false,
);
},
notLoggedInToRegistryText() {
return sprintf(
s__(`ContainerRegistry|If you are not already logged in, you need to authenticate to
the Container Registry by using your GitLab username and password. If you have
%{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a
%{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd}
instead of a password.`),
{
twofaDocLinkStart: `<a href="${this.twoFactorAuthHelpLink}" target="_blank">`,
twofaDocLinkEnd: '</a>',
personalAccessTokensDocLinkStart: `<a href="${this.personalAccessTokensHelpLink}" target="_blank">`,
personalAccessTokensDocLinkEnd: '</a>',
},
false,
);
},
},
};
</script>
<template>
<gl-empty-state
:title="s__('ContainerRegistry|There are no container images stored for this project')"
:svg-path="noContainersImage"
class="container-message"
>
<template #description>
<p class="js-no-container-images-text" v-html="noContainerImagesText"></p>
<h5>{{ s__('ContainerRegistry|Quick Start') }}</h5>
<p class="js-not-logged-in-to-registry-text" v-html="notLoggedInToRegistryText"></p>
<div class="input-group append-bottom-10">
<input :value="dockerLoginCommand" type="text" class="form-control monospace" readonly />
<span class="input-group-append">
<clipboard-button
:text="dockerLoginCommand"
:title="s__('ContainerRegistry|Copy login command')"
class="input-group-text"
/>
</span>
</div>
<p></p>
<p>
{{
s__(
'ContainerRegistry|You can add an image to this registry with the following commands:',
)
}}
</p>
<div class="input-group append-bottom-10">
<input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly />
<span class="input-group-append">
<clipboard-button
:text="dockerBuildCommand"
:title="s__('ContainerRegistry|Copy build command')"
class="input-group-text"
/>
</span>
</div>
<div class="input-group">
<input :value="dockerPushCommand" type="text" class="form-control monospace" readonly />
<span class="input-group-append">
<clipboard-button
:text="dockerPushCommand"
:title="s__('ContainerRegistry|Copy push command')"
class="input-group-text"
/>
</span>
</div>
</template>
</gl-empty-state>
</template>
<script>
import { mapActions } from 'vuex';
import { mapActions, mapGetters } from 'vuex';
import {
GlButton,
GlFormCheckbox,
......@@ -35,6 +35,11 @@ export default {
type: Object,
required: true,
},
canDeleteRepo: {
type: Boolean,
default: false,
required: false,
},
},
data() {
return {
......@@ -45,6 +50,7 @@ export default {
};
},
computed: {
...mapGetters(['isDeleteDisabled']),
bulkDeletePath() {
return this.repo.tagsPath ? this.repo.tagsPath.replace('?format=json', '/bulk_destroy') : '';
},
......@@ -165,6 +171,9 @@ export default {
}
}
},
canDeleteRow(item) {
return item && item.canDelete && !this.isDeleteDisabled;
},
},
};
</script>
......@@ -175,7 +184,7 @@ export default {
<tr>
<th>
<gl-form-checkbox
v-if="repo.canDelete"
v-if="canDeleteRepo"
class="js-select-all-checkbox"
:checked="selectAllChecked"
@change="onSelectAllChange"
......@@ -187,7 +196,7 @@ export default {
<th>{{ s__('ContainerRegistry|Last Updated') }}</th>
<th>
<gl-button
v-if="repo.canDelete"
v-if="canDeleteRepo"
v-gl-tooltip
v-gl-modal="modalId"
:disabled="!itemsToBeDeleted || itemsToBeDeleted.length === 0"
......@@ -208,7 +217,7 @@ export default {
<tr v-for="(item, index) in repo.list" :key="item.tag" class="registry-image-row">
<td class="check">
<gl-form-checkbox
v-if="item.canDelete"
v-if="canDeleteRow(item)"
class="js-select-checkbox"
:checked="itemsToBeDeleted && itemsToBeDeleted.includes(index)"
@change="updateItemsToBeDeleted(index)"
......@@ -244,7 +253,7 @@ export default {
<td class="content action-buttons">
<gl-button
v-if="item.canDelete"
v-if="canDeleteRow(item)"
v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove tag')"
:aria-label="s__('ContainerRegistry|Remove tag')"
......
......@@ -13,29 +13,24 @@ export default () =>
data() {
const { dataset } = document.querySelector(this.$options.el);
return {
characterError: Boolean(dataset.characterError),
containersErrorImage: dataset.containersErrorImage,
endpoint: dataset.endpoint,
helpPagePath: dataset.helpPagePath,
noContainersImage: dataset.noContainersImage,
personalAccessTokensHelpLink: dataset.personalAccessTokensHelpLink,
registryHostUrlWithPort: dataset.registryHostUrlWithPort,
repositoryUrl: dataset.repositoryUrl,
twoFactorAuthHelpLink: dataset.twoFactorAuthHelpLink,
registryData: {
endpoint: dataset.endpoint,
characterError: Boolean(dataset.characterError),
helpPagePath: dataset.helpPagePath,
noContainersImage: dataset.noContainersImage,
containersErrorImage: dataset.containersErrorImage,
repositoryUrl: dataset.repositoryUrl,
isGroupPage: dataset.isGroupPage,
personalAccessTokensHelpLink: dataset.personalAccessTokensHelpLink,
registryHostUrlWithPort: dataset.registryHostUrlWithPort,
twoFactorAuthHelpLink: dataset.twoFactorAuthHelpLink,
},
};
},
render(createElement) {
return createElement('registry-app', {
props: {
characterError: this.characterError,
containersErrorImage: this.containersErrorImage,
endpoint: this.endpoint,
helpPagePath: this.helpPagePath,
noContainersImage: this.noContainersImage,
personalAccessTokensHelpLink: this.personalAccessTokensHelpLink,
registryHostUrlWithPort: this.registryHostUrlWithPort,
repositoryUrl: this.repositoryUrl,
twoFactorAuthHelpLink: this.twoFactorAuthHelpLink,
...this.registryData,
},
});
},
......
......@@ -20,7 +20,6 @@ export const fetchRepos = ({ commit, state }) => {
export const fetchList = ({ commit }, { repo, page }) => {
commit(types.TOGGLE_REGISTRY_LIST_LOADING, repo);
return axios
.get(repo.tagsPath, { params: { page } })
.then(response => {
......@@ -40,6 +39,7 @@ export const multiDeleteItems = (_, { path, items }) =>
axios.delete(path, { params: { ids: items } });
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const setIsDeleteDisabled = ({ commit }, data) => commit(types.SET_IS_DELETE_DISABLED, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
......
export const isLoading = state => state.isLoading;
export const repos = state => state.repos;
export const isDeleteDisabled = state => state.isDeleteDisabled;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const SET_MAIN_ENDPOINT = 'SET_MAIN_ENDPOINT';
export const SET_IS_DELETE_DISABLED = 'SET_IS_DELETE_DISABLED';
export const SET_REPOS_LIST = 'SET_REPOS_LIST';
export const TOGGLE_MAIN_LOADING = 'TOGGLE_MAIN_LOADING';
......
......@@ -6,6 +6,10 @@ export default {
Object.assign(state, { endpoint });
},
[types.SET_IS_DELETE_DISABLED](state, isDeleteDisabled) {
Object.assign(state, { isDeleteDisabled });
},
[types.SET_REPOS_LIST](state, list) {
Object.assign(state, {
repos: list.map(el => ({
......@@ -17,6 +21,7 @@ export default {
location: el.location,
name: el.path,
tagsPath: el.tags_path,
projectId: el.project_id,
})),
});
},
......
export default () => ({
isLoading: false,
endpoint: '', // initial endpoint to fetch the repos list
isDeleteDisabled: false, // controls the delete buttons in the registry
/**
* Each object in `repos` has the following strucure:
* {
......
......@@ -13,7 +13,7 @@ module Boards
end
def board_parent
@board_parent ||= board.parent
@board_parent ||= board.resource_parent
end
def record_not_found(exception)
......
......@@ -9,7 +9,7 @@ module Boards
skip_before_action :authenticate_user!, only: [:index]
def index
lists = Boards::Lists::ListService.new(board.parent, current_user).execute(board)
lists = Boards::Lists::ListService.new(board.resource_parent, current_user).execute(board)
List.preload_preferences_for_user(lists, current_user)
......@@ -17,7 +17,7 @@ module Boards
end
def create
list = Boards::Lists::CreateService.new(board.parent, current_user, create_list_params).execute(board)
list = Boards::Lists::CreateService.new(board.resource_parent, current_user, create_list_params).execute(board)
if list.valid?
render json: serialize_as_json(list)
......
......@@ -35,7 +35,7 @@ module MilestoneActions
render json: tabs_json("shared/milestones/_labels_tab", {
labels: milestone_labels.map do |label|
label.present(issuable_subject: @milestone.parent)
label.present(issuable_subject: @milestone.resource_parent)
end
})
end
......
......@@ -44,7 +44,7 @@ class Groups::MilestonesController < Groups::ApplicationController
# all projects milestones states at once.
milestones, update_params = get_milestones_for_update
milestones.each do |milestone|
Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone)
Milestones::UpdateService.new(milestone.resource_parent, current_user, update_params).execute(milestone)
end
redirect_to milestone_path
......
# frozen_string_literal: true
module Groups
module Registry
class RepositoriesController < Groups::ApplicationController
before_action :verify_container_registry_enabled!
before_action :authorize_read_container_image!
def index
track_event(:list_repositories)
respond_to do |format|
format.html
format.json do
@images = group.container_repositories.with_api_entity_associations
render json: ContainerRepositoriesSerializer
.new(current_user: current_user)
.represent(@images)
end
end
end
private
def verify_container_registry_enabled!
render_404 unless Gitlab.config.registry.enabled
end
def authorize_read_container_image!
return render_404 unless can?(current_user, :read_container_image, group)
end
end
end
end
......@@ -290,7 +290,8 @@ module ApplicationSettingsHelper
:snowplow_cookie_domain,
:snowplow_enabled,
:snowplow_site_id,
:push_event_hooks_limit
:push_event_hooks_limit,
:push_event_activities_limit
]
end
......
......@@ -76,10 +76,10 @@ module GitlabRoutingHelper
end
def edit_milestone_path(entity, *args)
if entity.parent.is_a?(Group)
edit_group_milestone_path(entity.parent, entity, *args)
if entity.resource_parent.is_a?(Group)
edit_group_milestone_path(entity.resource_parent, entity, *args)
else
edit_project_milestone_path(entity.parent, entity, *args)
edit_project_milestone_path(entity.resource_parent, entity, *args)
end
end
......
......@@ -15,6 +15,16 @@ module GroupsHelper
%w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
end
def group_packages_nav_link_paths
%w[
groups/container_registries#index
]
end
def group_container_registry_nav?
Gitlab.config.registry.enabled && can?(current_user, :read_container_image, @group)
end
def group_sidebar_links
@group_sidebar_links ||= get_group_sidebar_links
end
......
......@@ -45,8 +45,8 @@ module TodosHelper
end
def todo_parent_path(todo)
if todo.parent.is_a?(Group)
link_to todo.parent.name, group_path(todo.parent)
if todo.resource_parent.is_a?(Group)
link_to todo.resource_parent.name, group_path(todo.resource_parent)
else
link_to_project(todo.project)
end
......@@ -64,7 +64,7 @@ module TodosHelper
if todo.for_commit?
project_commit_path(todo.project, todo.target, path_options)
else
path = [todo.parent, todo.target]
path = [todo.resource_parent, todo.target]
path.unshift(:pipelines) if todo.build_failed?
......
......@@ -217,6 +217,9 @@ class ApplicationSetting < ApplicationRecord
validates :push_event_hooks_limit,
numericality: { greater_than_or_equal_to: 0 }
validates :push_event_activities_limit,
numericality: { greater_than_or_equal_to: 0 }
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
......
......@@ -83,6 +83,7 @@ module ApplicationSettingImplementation
project_export_enabled: true,
protected_ci_variables: false,
push_event_hooks_limit: 3,
push_event_activities_limit: 3,
raw_blob_request_limit: 300,
recaptcha_enabled: false,
login_recaptcha_protection_enabled: false,
......
......@@ -16,10 +16,9 @@ class Board < ApplicationRecord
!group
end
def parent
@parent ||= group || project
def resource_parent
@resource_parent ||= group || project
end
alias_method :resource_parent, :parent
def group_board?
group_id.present?
......
......@@ -11,6 +11,7 @@ class ContainerRepository < ApplicationRecord
delegate :client, to: :registry
scope :ordered, -> { order(:name) }
scope :with_api_entity_associations, -> { preload(:project) }
# rubocop: disable CodeReuse/ServiceClass
def registry
......
......@@ -11,7 +11,7 @@ class GlobalMilestone
delegate :title, :state, :due_date, :start_date, :participants, :project,
:group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title,
:milestoneish_id, :parent, to: :milestone
:milestoneish_id, :resource_parent, to: :milestone
def to_hash
{
......
......@@ -23,6 +23,8 @@ class GpgSignature < ApplicationRecord
validates :project_id, presence: true
validates :gpg_key_primary_keyid, presence: true
scope :by_commit_sha, ->(shas) { where(commit_sha: shas) }
def self.with_key_and_subkeys(gpg_key)
subkey_ids = gpg_key.subkeys.pluck(:id)
......
......@@ -136,6 +136,7 @@ class MergeRequestDiff < ApplicationRecord
# All diff information is collected from repository after object is created.
# It allows you to override variables like head_commit_sha before getting diff.
after_create :save_git_content, unless: :importing?
after_create_commit :set_as_latest_diff
after_save :update_external_diff_store, if: -> { !importing? && saved_change_to_external_diff? }
......@@ -150,10 +151,6 @@ class MergeRequestDiff < ApplicationRecord
# Collect information about commits and diff from repository
# and save it to the database as serialized data
def save_git_content
MergeRequest
.where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id)
.update_all(latest_merge_request_diff_id: self.id)
ensure_commit_shas
save_commits
save_diffs
......@@ -168,6 +165,12 @@ class MergeRequestDiff < ApplicationRecord
keep_around_commits
end
def set_as_latest_diff
MergeRequest
.where('id = ? AND COALESCE(latest_merge_request_diff_id, 0) < ?', self.merge_request_id, self.id)
.update_all(latest_merge_request_diff_id: self.id)
end
def ensure_commit_shas
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
......@@ -502,11 +505,6 @@ class MergeRequestDiff < ApplicationRecord
merge_request.closed? && merge_request.metrics.latest_closed_at < EXTERNAL_DIFF_CUTOFF.ago
end
# We can't rely on `merge_request.latest_merge_request_diff_id` because that
# may have been changed in `save_git_content` without being reflected in
# the association's instance. This query is always subject to races, but
# the worst case is that we *don't* make a diff external when we could. The
# background worker will make it external at a later date.
def old_version?
latest_id = MergeRequest
.where(id: merge_request_id)
......@@ -514,7 +512,7 @@ class MergeRequestDiff < ApplicationRecord
.pluck(:latest_merge_request_diff_id)
.first
self.id != latest_id
latest_id && self.id < latest_id
end
def load_diffs(options)
......
......@@ -257,10 +257,9 @@ class Milestone < ApplicationRecord
title.to_slug.normalize.to_s
end
def parent
def resource_parent
group || project
end
alias_method :resource_parent, :parent
def group_milestone?
group_id.present?
......
......@@ -483,10 +483,9 @@ class Note < ApplicationRecord
Upload.find_by(model: self, path: paths)
end
def parent
def resource_parent
project
end
alias_method :resource_parent, :parent
private
......
......@@ -26,6 +26,8 @@ class PushEvent < Event
delegate :commit_count, to: :push_event_payload
alias_method :commits_count, :commit_count
delegate :ref_count, to: :push_event_payload
# Returns events of pushes that either pushed to an existing ref or created a
# new one.
def self.created_or_pushed
......
......@@ -144,10 +144,9 @@ class Todo < ApplicationRecord
end
end
def parent
def resource_parent
project
end
alias_method :resource_parent, :parent
def unmergeable?
action == UNMERGEABLE
......
......@@ -3,7 +3,7 @@
class BoardPolicy < BasePolicy
include FindGroupProjects
delegate { @subject.parent }
delegate { @subject.resource_parent }
condition(:is_group_board) { @subject.group_board? }
condition(:is_project_board) { @subject.project_board? }
......@@ -19,7 +19,7 @@ class BoardPolicy < BasePolicy
condition(:reporter_of_group_projects) do
next unless @user
group_projects_for(user: @user, group: @subject.parent)
group_projects_for(user: @user, group: @subject.resource_parent)
.visible_to_user_and_access_level(@user, ::Gitlab::Access::REPORTER)
.exists?
end
......
# frozen_string_literal: true
class MilestonePolicy < BasePolicy
delegate { @subject.parent }
delegate { @subject.resource_parent }
end
......@@ -18,7 +18,7 @@ class ContainerRepositoryEntity < Grape::Entity
alias_method :repository, :object
def project
request.project
request.respond_to?(:project) ? request.project : object.project
end
def can_destroy?
......
# frozen_string_literal: true
class BulkPushEventPayloadService
def initialize(event, push_data)
@event = event
@push_data = push_data
end
def execute
@event.build_push_event_payload(
action: @push_data[:action],
commit_count: 0,
ref_count: @push_data[:ref_count],
ref_type: @push_data[:ref_type]
)
@event.push_event_payload.tap(&:save!)
end
end
......@@ -73,15 +73,27 @@ class EventCreateService
end
def push(project, current_user, push_data)
create_push_event(PushEventPayloadService, project, current_user, push_data)
end
def bulk_push(project, current_user, push_data)
create_push_event(BulkPushEventPayloadService, project, current_user, push_data)
end
private
def create_record_event(record, current_user, status)
create_event(record.resource_parent, current_user, status, target_id: record.id, target_type: record.class.name)
end
def create_push_event(service_class, project, current_user, push_data)
# We're using an explicit transaction here so that any errors that may occur
# when creating push payload data will result in the event creation being
# rolled back as well.
event = Event.transaction do
new_event = create_event(project, current_user, Event::PUSHED)
PushEventPayloadService
.new(new_event, push_data)
.execute
service_class.new(new_event, push_data).execute
new_event
end
......@@ -92,12 +104,6 @@ class EventCreateService
Users::ActivityService.new(current_user, 'push').execute
end
private
def create_record_event(record, current_user, status)
create_event(record.resource_parent, current_user, status, target_id: record.id, target_type: record.class.name)
end
def create_event(resource_parent, current_user, status, attributes = {})
attributes.reverse_merge!(
action: status,
......
......@@ -48,6 +48,8 @@ module Git
# Push events in the activity feed only show information for the
# last commit.
def create_events
return unless params.fetch(:create_push_event, true)
EventCreateService.new.push(project, current_user, event_push_data)
end
......
......@@ -16,8 +16,8 @@ module Git
def process_changes_by_action(ref_type, changes)
changes_by_action = group_changes_by_action(changes)
changes_by_action.each do |_, changes|
process_changes(ref_type, changes, execute_project_hooks: execute_project_hooks?(changes)) if changes.any?
changes_by_action.each do |action, changes|
process_changes(ref_type, action, changes, execute_project_hooks: execute_project_hooks?(changes)) if changes.any?
end
end
......@@ -38,9 +38,11 @@ module Git
(changes.size <= Gitlab::CurrentSettings.push_event_hooks_limit) || Feature.enabled?(:git_push_execute_all_project_hooks, project)
end
def process_changes(ref_type, changes, execute_project_hooks:)
def process_changes(ref_type, action, changes, execute_project_hooks:)
push_service_class = push_service_class_for(ref_type)
create_bulk_push_event = changes.size > Gitlab::CurrentSettings.push_event_activities_limit
changes.each do |change|
push_service_class.new(
project,
......@@ -48,9 +50,20 @@ module Git
change: change,
push_options: params[:push_options],
create_pipelines: change[:index] < PIPELINE_PROCESS_LIMIT || Feature.enabled?(:git_push_create_all_pipelines, project),
execute_project_hooks: execute_project_hooks
execute_project_hooks: execute_project_hooks,
create_push_event: !create_bulk_push_event
).execute
end
create_bulk_push_event(ref_type, action, changes) if create_bulk_push_event
end
def create_bulk_push_event(ref_type, action, changes)
EventCreateService.new.bulk_push(
project,
current_user,
Gitlab::DataBuilder::Push.build_bulk(action: action, ref_type: ref_type, changes: changes)
)
end
def push_service_class_for(ref_type)
......
......@@ -50,7 +50,7 @@ module Notes
return if update_params.empty?
return unless supported?(note)
self.class.noteable_update_service(note).new(note.parent, current_user, update_params).execute(note.noteable)
self.class.noteable_update_service(note).new(note.resource_parent, current_user, update_params).execute(note.noteable)
end
end
end
......
# frozen_string_literal: true
module Search
class SnippetService
attr_accessor :current_user, :params
def initialize(user, params)
@current_user, @params = user, params.dup
end
class SnippetService < Search::GlobalService
def execute
Gitlab::SnippetSearchResults.new(current_user, params[:search])
end
......
......@@ -25,5 +25,10 @@
= f.number_field :push_event_hooks_limit, class: 'form-control'
.form-text.text-muted
= _("Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value.")
.form-group
= f.label :push_event_activities_limit, class: 'label-bold'
= f.number_field :push_event_activities_limit, class: 'form-control'
.form-text.text-muted
= _('Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.')
= f.submit 'Save changes', class: "btn btn-success"
......@@ -6,11 +6,13 @@
.event-title.d-flex.flex-wrap
= inline_event_icon(event)
%span.event-type.d-inline-block.append-right-4.pushed #{event.action_name} #{event.ref_type}
%span.append-right-4
- commits_link = project_commits_path(project, event.ref_name)
- should_link = event.tag? ? project.repository.tag_exists?(event.ref_name) : project.repository.branch_exists?(event.ref_name)
= link_to_if should_link, event.ref_name, commits_link, class: 'ref-name'
- many_refs = event.ref_count.to_i > 1
%span.event-type.d-inline-block.append-right-4.pushed= many_refs ? "#{event.action_name} #{event.ref_count} #{event.ref_type.pluralize}" : "#{event.action_name} #{event.ref_type}"
- unless many_refs
%span.append-right-4
- commits_link = project_commits_path(project, event.ref_name)
- should_link = event.tag? ? project.repository.tag_exists?(event.ref_name) : project.repository.branch_exists?(event.ref_name)
= link_to_if should_link, event.ref_name, commits_link, class: 'ref-name'
= render "events/event_scope", event: event
......
- page_title _("Container Registry")
%section
.row.registry-placeholder.prepend-bottom-10
.col-12
#js-vue-registry-images{ data: { endpoint: group_container_registries_path(@group, format: :json),
"help_page_path" => help_page_path('user/packages/container_registry/index'),
"no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
"repository_url" => "",
is_group_page: true,
character_error: @character_error.to_s } }
- if group_container_registry_nav?
= nav_link(path: group_packages_nav_link_paths) do
= link_to group_container_registries_path(@group), title: _('Container Registry') do
.nav-icon-container
= sprite_icon('package')
%span.nav-item-name
= _('Packages')
%ul.sidebar-sub-level-items
= nav_link(controller: [:packages, :repositories], html_options: { class: "fly-out-top-item" } ) do
= link_to group_container_registries_path(@group), title: _('Container Registry') do
%strong.fly-out-top-item-name
= _('Packages')
%li.divider.fly-out-top-item
= nav_link(controller: 'groups/container_registries') do
= link_to group_container_registries_path(@group), title: _('Container Registry') do
%span= _('Container Registry')
......@@ -118,7 +118,7 @@
%strong.fly-out-top-item-name
= _('Kubernetes')
= render_if_exists 'groups/sidebar/packages' # EE-specific
= render_if_exists 'groups/sidebar/packages'
- if group_sidebar_link?(:group_members)
= nav_link(path: 'group_members#index') do
......
- parent = board.parent
- parent = board.resource_parent
- milestone_filter_opts = { format: :json }
- milestone_filter_opts = milestone_filter_opts.merge(only_group_milestones: true) if board.group_board?
- weights = Gitlab.ee? ? ([Issue::WEIGHT_ANY] + Issue.weight_options) : []
......
......@@ -3,6 +3,6 @@
Add list
.dropdown-menu.dropdown-extended-height.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
- if can?(current_user, :admin_label, board.parent)
- if can?(current_user, :admin_label, board.resource_parent)
= render partial: "shared/issuable/label_page_create", locals: { show_add_list: true, add_list: true, add_list_class: 'd-none' }
= dropdown_loading
......@@ -2,7 +2,7 @@
- board = local_assigns.fetch(:board, nil)
- is_not_boards_modal_or_productivity_analytics = type != :boards_modal && type != :productivity_analytics
- block_css_class = is_not_boards_modal_or_productivity_analytics ? 'row-content-block second-block' : ''
- user_can_admin_list = board && can?(current_user, :admin_list, board.parent)
- user_can_admin_list = board && can?(current_user, :admin_list, board.resource_parent)
.issues-filters{ class: ("w-100" if type == :boards_modal) }
.issues-details-filters.filtered-search-block.d-flex.flex-column.flex-md-row{ class: block_css_class, "v-pre" => type == :boards_modal }
......
---
title: Prevent the slash command parser from removing leading whitespace from content that is unrelated to slash commands
merge_request: 18589
author: Jared Deckard
type: fixed
---
title: Adds separate parsers for mentions of users, groups, projects in markdown content
merge_request: 18318
author:
type: added
---
title: Group level Container Registry browser
merge_request: 17615
author:
type: added
---
title: Support ES searches for project snippets
merge_request: 18459
author:
type: fixed
---
title: Aggregate push events when there are too many
merge_request: 18239
author:
type: changed
---
title: Add two new predefined stages to pipelines
merge_request: 18205
author:
type: added
---
title: Cleanup background migrations for any approval rules
merge_request: 18256
author:
type: changed
---
title: Remove N+1 for fetching commits signatures
merge_request: 18389
author:
type: performance
---
title: Reduce idle in transaction time when updating a merge request
merge_request: 18493
author:
type: performance
---
title: Update GitLab Shell to v10.2.0
merge_request: 18735
author:
type: other
......@@ -77,6 +77,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
post :pause
end
end
resources :container_registries, only: [:index], controller: 'registry/repositories'
end
scope(path: '*id',
......
# frozen_string_literal: true
class AddPushEventActivitiesLimitToApplicationSettings < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:application_settings, :push_event_activities_limit, :integer, default: 3)
end
def down
remove_column(:application_settings, :push_event_activities_limit)
end
end
# frozen_string_literal: true
class AddRefCountToPushEventPayloads < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :push_event_payloads, :ref_count, :integer
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class PopulateRemainingAnyApproverRulesForMergeRequests < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 10_000
MIGRATION = 'PopulateAnyApprovalRuleForMergeRequests'
disable_ddl_transaction!
class MergeRequest < ActiveRecord::Base
include EachBatch
self.table_name = 'merge_requests'
scope :with_approvals_before_merge, -> { where.not(approvals_before_merge: 0) }
end
def up
return unless Gitlab.ee?
add_concurrent_index :merge_requests, :id,
name: 'tmp_merge_requests_with_approvals_before_merge',
where: 'approvals_before_merge != 0'
Gitlab::BackgroundMigration.steal(MIGRATION)
PopulateRemainingAnyApproverRulesForMergeRequests::MergeRequest.with_approvals_before_merge.each_batch(of: BATCH_SIZE) do |batch|
range = batch.pluck('MIN(id)', 'MAX(id)').first
Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForMergeRequests.new.perform(*range)
end
remove_concurrent_index_by_name(:merge_requests, 'tmp_merge_requests_with_approvals_before_merge')
end
def down
# no-op
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class PopulateRemainingAnyApproverRulesForProjects < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 5_000
MIGRATION = 'PopulateAnyApprovalRuleForProjects'
disable_ddl_transaction!
class Project < ActiveRecord::Base
include EachBatch
self.table_name = 'projects'
scope :with_approvals_before_merge, -> { where.not(approvals_before_merge: 0) }
end
def up
return unless Gitlab.ee?
add_concurrent_index :projects, :id,
name: 'tmp_projects_with_approvals_before_merge',
where: 'approvals_before_merge != 0'
Gitlab::BackgroundMigration.steal(MIGRATION)
PopulateRemainingAnyApproverRulesForProjects::Project.with_approvals_before_merge.each_batch(of: BATCH_SIZE) do |batch|
range = batch.pluck('MIN(id)', 'MAX(id)').first
Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForProjects.new.perform(*range)
end
remove_concurrent_index_by_name(:projects, 'tmp_projects_with_approvals_before_merge')
end
def down
# no-op
end
end
......@@ -339,6 +339,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_072826) do
t.integer "throttle_incident_management_notification_period_in_seconds", default: 3600
t.integer "throttle_incident_management_notification_per_period", default: 3600
t.integer "push_event_hooks_limit", default: 3, null: false
t.integer "push_event_activities_limit", default: 3, null: false
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
......@@ -3158,6 +3159,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_072826) do
t.binary "commit_to"
t.text "ref"
t.string "commit_title", limit: 70
t.integer "ref_count"
t.index ["event_id"], name: "index_push_event_payloads_on_event_id", unique: true
end
......
......@@ -86,7 +86,8 @@ Below we describe how to configure two Gitaly servers one at
`gitaly1.internal` and the other at `gitaly2.internal`
with secret token `abc123secret`. We assume
your GitLab installation has three repository storages: `default`,
`storage1` and `storage2`.
`storage1` and `storage2`. You can use as little as just one server with one
repository storage if desired.
### 1. Installation
......@@ -129,7 +130,7 @@ Configure a token on the instance that runs the GitLab Rails application.
Next, on the Gitaly servers, you need to configure storage paths, enable
the network listener and configure the token.
NOTE: **Note:** if you want to reduce the risk of downtime when you enable
NOTE: **Note:** If you want to reduce the risk of downtime when you enable
authentication you can temporarily disable enforcement, see [the
documentation on configuring Gitaly
authentication](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md#authentication)
......@@ -177,20 +178,19 @@ Check the directory layout on your Gitaly server to be sure.
# Don't forget to copy `/etc/gitlab/gitlab-secrets.json` from web server to Gitaly server.
gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
# Authentication token to ensure only authorized servers can communicate with
# Gitaly server
gitaly['auth_token'] = 'abc123secret'
# Make Gitaly accept connections on all network interfaces. You must use
# firewalls to restrict access to this address/port.
# Comment out following line if you only want to support TLS connections
gitaly['listen_addr'] = "0.0.0.0:8075"
gitaly['auth_token'] = 'abc123secret'
# To use TLS for Gitaly you need to add
gitaly['tls_listen_addr'] = "0.0.0.0:9999"
gitaly['certificate_path'] = "path/to/cert.pem"
gitaly['key_path'] = "path/to/key.pem"
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:
For `gitaly1.internal`:
On `gitaly1.internal`:
```
gitaly['storage'] = [
......@@ -199,7 +199,7 @@ Check the directory layout on your Gitaly server to be sure.
]
```
For `gitaly2.internal`:
On `gitaly2.internal`:
```
gitaly['storage'] = [
......@@ -219,11 +219,6 @@ Check the directory layout on your Gitaly server to be sure.
```toml
listen_addr = '0.0.0.0:8075'
tls_listen_addr = '0.0.0.0:9999'
[tls]
certificate_path = /path/to/cert.pem
key_path = /path/to/key.pem
[auth]
token = 'abc123secret'
......@@ -231,7 +226,7 @@ Check the directory layout on your Gitaly server to be sure.
1. Append the following to `/home/git/gitaly/config.toml` for each respective server:
For `gitaly1.internal`:
On `gitaly1.internal`:
```toml
[[storage]]
......@@ -241,7 +236,7 @@ Check the directory layout on your Gitaly server to be sure.
name = 'storage1'
```
For `gitaly2.internal`:
On `gitaly2.internal`:
```toml
[[storage]]
......@@ -369,11 +364,12 @@ To disable Gitaly on a client node:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/22602) in GitLab 11.8.
Gitaly supports TLS encryption. To be able to communicate
with a Gitaly instance that listens for secure connections you will need to use `tls://` url
with a Gitaly instance that listens for secure connections you will need to use `tls://` URL
scheme in the `gitaly_address` of the corresponding storage entry in the GitLab configuration.
You will need to bring your own certificates as this isn't provided automatically.
The certificate to be used needs to be installed on all Gitaly nodes and on all
The certificate to be used needs to be installed on all Gitaly nodes, and the
certificate (or CA of certificate) on all
client nodes that communicate with it following the procedure described in
[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
......@@ -395,7 +391,7 @@ To configure Gitaly with TLS:
**For Omnibus GitLab**
1. On the client nodes, edit `/etc/gitlab/gitlab.rb`:
1. On the client node(s), edit `/etc/gitlab/gitlab.rb` as follows:
```ruby
git_data_dirs({
......@@ -407,20 +403,38 @@ To configure Gitaly with TLS:
gitlab_rails['gitaly_token'] = 'abc123secret'
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
1. On the Gitaly server nodes, edit `/etc/gitlab/gitlab.rb`:
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) on client node(s).
1. Create the `/etc/gitlab/ssl` directory and copy your key and certificate there:
```sh
sudo mkdir -p /etc/gitlab/ssl
sudo chmod 700 /etc/gitlab/ssl
sudo cp key.pem cert.pem /etc/gitlab/ssl/
```
1. On the Gitaly server node(s), edit `/etc/gitlab/gitlab.rb` and add:
<!--
updates to following example must also be made at
https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
-->
```ruby
gitaly['tls_listen_addr'] = "0.0.0.0:9999"
gitaly['certificate_path'] = "path/to/cert.pem"
gitaly['key_path'] = "path/to/key.pem"
gitaly['certificate_path'] = "/etc/gitlab/ssl/cert.pem"
gitaly['key_path'] = "/etc/gitlab/ssl/key.pem"
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) on Gitaly server node(s).
1. (Optional) After [verifying that all Gitaly traffic is being served over TLS](#observe-type-of-gitaly-connections),
you can improve security by disabling non-TLS connections by commenting out
or deleting `gitaly['listen_addr']` in `/etc/gitlab/gitlab.rb`, saving the file,
and [reconfiguring GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure)
on Gitaly server node(s).
**For installations from source**
1. On the client nodes, edit `/home/git/gitlab/config/gitlab.yml`:
1. On the client node(s), edit `/home/git/gitlab/config/gitlab.yml` as follows:
```yaml
gitlab:
......@@ -445,18 +459,33 @@ To configure Gitaly with TLS:
data will be stored in this folder. This will no longer be necessary after
[this issue](https://gitlab.com/gitlab-org/gitaly/issues/1282) is resolved.
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
1. On the Gitaly server nodes, edit `/home/git/gitaly/config.toml`:
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source) on client node(s).
1. Create the `/etc/gitlab/ssl` directory and copy your key and certificate there:
```sh
sudo mkdir -p /etc/gitlab/ssl
sudo chmod 700 /etc/gitlab/ssl
sudo cp key.pem cert.pem /etc/gitlab/ssl/
```
1. On the Gitaly server node(s), edit `/home/git/gitaly/config.toml` and add:
```toml
tls_listen_addr = '0.0.0.0:9999'
[tls]
certificate_path = '/path/to/cert.pem'
key_path = '/path/to/key.pem'
certificate_path = '/etc/gitlab/ssl/cert.pem'
key_path = '/etc/gitlab/ssl/key.pem'
```
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source) on Gitaly server node(s).
1. (Optional) After [verifying that all Gitaly traffic is being served over TLS](#observe-type-of-gitaly-connections),
you can improve security by disabling non-TLS connections by commenting out
or deleting `listen_addr` in `/home/git/gitaly/config.toml`, saving the file,
and [restarting GitLab](../restart_gitlab.md#installations-from-source)
on Gitaly server node(s).
### Observe type of Gitaly connections
To observe what type of connections are actually being used in a
production environment you can use the following Prometheus query:
......
......@@ -68,20 +68,26 @@ sidekiq['enable'] = false
gitlab_workhorse['enable'] = false
gitaly['enable'] = false
# virtual_storage_name must match the same storage name given to praefect in git_data_dirs
praefect['virtual_storage_name'] = 'praefect'
praefect['auth_token'] = 'super_secret_abc'
praefect['enable'] = true
praefect['storage_nodes'] = [
{
'storage' => 'praefect-git-1',
'address' => 'tcp://praefect-git-1.internal',
'token' => 'token1',
'primary' => true
},
{
'storage' => 'praefect-git-2',
'address' => 'tcp://praefect-git-2.internal'
'address' => 'tcp://praefect-git-2.internal',
'token' => 'token2'
},
{
'storage' => 'praefect-git-3',
'address' => 'tcp://praefect-git-3.internal'
'address' => 'tcp://praefect-git-3.internal',
'token' => 'token3'
}
]
```
......
......@@ -290,6 +290,7 @@ are listed in the descriptions of the relevant settings.
| `protected_ci_variables` | boolean | no | Environment variables are protected by default. |
| `pseudonymizer_enabled` | boolean | no | **(PREMIUM)** When enabled, GitLab will run a background job that will produce pseudonymized CSVs of the GitLab database that will be uploaded to your configured object storage directory.
| `push_event_hooks_limit` | integer | no | Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value. |
| `push_event_activities_limit` | integer | no | Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push events will be created. [Bulk push events will be created](../user/admin_area/settings/push_event_activities_limit.md) if it surpasses that value. |
| `recaptcha_enabled` | boolean | no | (**If enabled, requires:** `recaptcha_private_key` and `recaptcha_site_key`) Enable reCAPTCHA. |
| `recaptcha_private_key` | string | required by: `recaptcha_enabled` | Private key for reCAPTCHA. |
| `recaptcha_site_key` | string | required by: `recaptcha_enabled` | Site key for reCAPTCHA. |
......
......@@ -29,7 +29,12 @@ If you want to disable it for a specific project, you can do so in
## Maximum artifacts size **(CORE ONLY)**
The maximum size of the [job artifacts](../../../administration/job_artifacts.md)
can be set at the project level, group level, and at the instance level. The value is:
can be set at:
- The instance level.
- [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/issues/21688), the project and group level.
The value is:
- In *MB* and the default is 100MB per job.
- [Set to 1G](../../gitlab_com/index.md#gitlab-cicd) on GitLab.com.
......
......@@ -22,6 +22,7 @@ include:
- [Custom templates repository](instance_template_repository.md) **(PREMIUM)**
- [Protected paths](protected_paths.md) **(CORE ONLY)**
- [Help messages for the `/help` page and the login page](help_page.md)
- [Push event activities limit and bulk push events](push_event_activities_limit.md)
NOTE: **Note:**
You can change the [first day of the week](../../profile/preferences.md) for the entire GitLab instance
......
---
type: reference
---
# Push event activities limit and bulk push events
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/31007) in GitLab 12.4.
This allows you to set the number of changes (branches or tags) in a single push
to determine whether individual push events or bulk push event will be created.
Bulk push events will be created if it surpasses that value.
For example, if 4 branches are pushed and the limit is currently set to 3,
you'll see the following in the activity feed:
![Bulk push event](img/bulk_push_event_v12_4.png)
With this feature, when a single push includes a lot of changes (e.g. 1,000
branches), only 1 bulk push event will be created instead of creating 1,000 push
events. This helps in maintaining good system performance and preventing spam on
the activity feed.
This setting can be modified in **Admin Area > Settings > Network > Performance Optimization**.
This can also be configured via the [Application settings API](../../../api/settings.md#list-of-settings-that-can-be-accessed-via-api-calls)
as `push_event_activities_limit`. The default value is 3, but it can be greater
than or equal 0.
![Push event activities limit](img/push_event_activities_limit_v12_4.png)
......@@ -178,9 +178,9 @@ There are two different ways to add a new project to a group:
### Default project-creation level
> [Introduced][ee-2534] in [GitLab Premium][ee] 10.5.
> Brought to [GitLab Starter][ee] in 10.7.
> [Moved](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/25975) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.10.
> - [Introduced][ee-2534] in [GitLab Premium][ee] 10.5.
> - Brought to [GitLab Starter][ee] in 10.7.
> - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/25975) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.10.
By default, [Developers and Maintainers](../permissions.md#group-members-permissions) can create projects under a group.
......@@ -338,8 +338,7 @@ request to add a new user to a project through API will not be possible.
#### IP access restriction **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/1985) in
[GitLab Ultimate and Gold](https://about.gitlab.com/pricing/) 12.0.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/1985) in [GitLab Ultimate and Gold](https://about.gitlab.com/pricing/) 12.0.
To make sure only people from within your organization can access particular
resources, you have the option to restrict access to groups and their
......@@ -351,16 +350,20 @@ Add one or more whitelisted IP subnets using CIDR notation in comma separated fo
coming from a different IP address won't be able to access the restricted
content.
Restriction currently applies to UI, API access and Git actions via SSH.
Restriction currently applies to:
- UI.
- API access.
- [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/issues/32113), Git actions via SSH.
To avoid accidental lock-out, admins and group owners are are able to access
the group regardless of the IP restriction.
#### Allowed domain restriction **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7297) in
[GitLab Premium and Silver](https://about.gitlab.com/pricing/) 12.2.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/7297) in [GitLab Premium and Silver](https://about.gitlab.com/pricing/) 12.2.
You can restrict access to groups and their underlying projects by
You can restrict access to groups by
allowing only users with email addresses in particular domains to be added to the group.
Add email domains you want to whitelist and users with emails from different
......
......@@ -933,8 +933,8 @@ module API
end
class PushEventPayload < Grape::Entity
expose :commit_count, :action, :ref_type, :commit_from, :commit_to
expose :ref, :commit_title
expose :commit_count, :action, :ref_type, :commit_from, :commit_to, :ref,
:commit_title, :ref_count
end
class Event < Grape::Entity
......@@ -988,11 +988,11 @@ module API
def todo_target_url(todo)
target_type = todo.target_type.underscore
target_url = "#{todo.parent.class.to_s.underscore}_#{target_type}_url"
target_url = "#{todo.resource_parent.class.to_s.underscore}_#{target_type}_url"
Gitlab::Routing
.url_helpers
.public_send(target_url, todo.parent, todo.target, anchor: todo_target_anchor(todo)) # rubocop:disable GitlabSecurity/PublicSend
.public_send(target_url, todo.resource_parent, todo.target, anchor: todo_target_anchor(todo)) # rubocop:disable GitlabSecurity/PublicSend
end
def todo_target_anchor(todo)
......
......@@ -102,6 +102,7 @@ module API
optional :project_export_enabled, type: Boolean, desc: 'Enable project export'
optional :prometheus_metrics_enabled, type: Boolean, desc: 'Enable Prometheus metrics'
optional :push_event_hooks_limit, type: Integer, desc: "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value."
optional :push_event_activities_limit, type: Integer, desc: 'Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.'
optional :recaptcha_enabled, type: Boolean, desc: 'Helps prevent bots from creating accounts'
given recaptcha_enabled: ->(val) { val } do
requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
......
# frozen_string_literal: true
module Banzai
module ReferenceParser
class MentionedUserParser < BaseParser
self.reference_type = :user
def references_relation
User
end
# any user can be mentioned by username
def can_read_reference?(user, ref_attr, node)
true
end
end
end
end
# frozen_string_literal: true
module Banzai
module ReferenceParser
class MentionedUsersByGroupParser < BaseParser
GROUP_ATTR = 'data-group'
self.reference_type = :user
def self.data_attribute
@data_attribute ||= GROUP_ATTR
end
def references_relation
Group
end
def nodes_visible_to_user(user, nodes)
groups = lazy { grouped_objects_for_nodes(nodes, Group, GROUP_ATTR) }
nodes.select do |node|
node.has_attribute?(GROUP_ATTR) && can_read_group_reference?(node, user, groups)
end
end
def can_read_group_reference?(node, user, groups)
node_group = groups[node]
node_group && can?(user, :read_group, node_group)
end
end
end
end
# frozen_string_literal: true
module Banzai
module ReferenceParser
class MentionedUsersByProjectParser < ProjectParser
PROJECT_ATTR = 'data-project'
self.reference_type = :user
def self.data_attribute
@data_attribute ||= PROJECT_ATTR
end
def references_relation
Project
end
end
end
end
......@@ -78,8 +78,13 @@ module Gitlab
def build_config(config)
initial_config = Gitlab::Config::Loader::Yaml.new(config).load!
initial_config = Config::External::Processor.new(initial_config, @context).perform
initial_config = Config::Extendable.new(initial_config).to_hash
Config::Extendable.new(initial_config).to_hash
if Feature.enabled?(:ci_pre_post_pipeline_stages, @context.project, default_enabled: true)
initial_config = Config::EdgeStagesInjector.new(initial_config).to_hash
end
initial_config
end
def build_context(project:, sha:, user:)
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
class EdgeStagesInjector
PRE_PIPELINE = '.pre'
POST_PIPELINE = '.post'
EDGES = [PRE_PIPELINE, POST_PIPELINE].freeze
def self.wrap_stages(stages)
stages = stages.to_a - EDGES
stages.unshift PRE_PIPELINE
stages.push POST_PIPELINE
stages
end
def initialize(config)
@config = config.to_h.deep_dup
end
def to_hash
if config.key?(:stages)
process(:stages)
elsif config.key?(:types)
process(:types)
else
config
end
end
private
attr_reader :config
delegate :wrap_stages, to: :class
def process(keyword)
stages = extract_stages(keyword)
return config if stages.empty?
stages = wrap_stages(stages)
config[keyword] = stages
config
end
def extract_stages(keyword)
stages = config[keyword]
return [] unless stages.is_a?(Array)
stages
end
end
end
end
end
......@@ -15,7 +15,7 @@ module Gitlab
end
def self.default
%w[build test deploy]
Config::EdgeStagesInjector.wrap_stages %w[build test deploy]
end
end
end
......
......@@ -107,6 +107,14 @@ module Gitlab
}
end
def build_bulk(action:, ref_type:, changes:)
{
action: action,
ref_count: changes.count,
ref_type: ref_type
}
end
# This method provides a sample data generated with
# existing project and commits to test webhooks
def build_sample(project, user)
......
......@@ -10,6 +10,8 @@ module Gitlab
repo = commit.project.repository.raw_repository
@signature_data = Gitlab::Git::Commit.extract_signature_lazily(repo, commit.sha || commit.id)
lazy_signature
end
def signature_text
......@@ -28,18 +30,16 @@ module Gitlab
!!(signature_text && signed_text)
end
# rubocop: disable CodeReuse/ActiveRecord
def signature
return unless has_signature?
return @signature if @signature
cached_signature = GpgSignature.find_by(commit_sha: @commit.sha)
cached_signature = lazy_signature&.itself
return @signature = cached_signature if cached_signature.present?
@signature = create_cached_signature!
end
# rubocop: enable CodeReuse/ActiveRecord
def update_signature!(cached_signature)
using_keychain do |gpg_key|
......@@ -50,6 +50,14 @@ module Gitlab
private
def lazy_signature
BatchLoader.for(@commit.sha).batch do |shas, loader|
GpgSignature.by_commit_sha(shas).each do |signature|
loader.call(signature.commit_sha, signature)
end
end
end
def using_keychain
Gitlab::Gpg.using_tmp_keychain do
# first we need to get the fingerprint from the signature to query the gpg
......
......@@ -14,6 +14,29 @@ module Gitlab
def log_filename
File.join(Rails.root, 'log', 'sidekiq_exporter.log')
end
private
# Sidekiq Exporter does not work properly in sidekiq-cluster
# mode. It tries to start the service on the same port for
# each of the cluster workers, this results in failure
# due to duplicate binding.
#
# For now we ignore this error, as metrics are still "kind of"
# valid as they are rendered from shared directory.
#
# Issue: https://gitlab.com/gitlab-org/gitlab/issues/5714
def start_working
super
rescue Errno::EADDRINUSE => e
Sidekiq.logger.error(
class: self.class.to_s,
message: 'Cannot start sidekiq_exporter',
exception: e.message
)
false
end
end
end
end
......
......@@ -50,7 +50,7 @@ module Gitlab
content, commands = perform_substitutions(content, commands)
[content.strip, commands]
[content.rstrip, commands]
end
private
......@@ -109,7 +109,7 @@ module Gitlab
[ ]
(?<arg>[^\n]*)
)?
(?:\n|$)
(?:\s*\n|$)
)
}mix
end
......
......@@ -3,7 +3,8 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range directly_addressed_user epic).freeze
REFERABLES = %i(user issue label milestone
merge_request snippet commit commit_range directly_addressed_user epic).freeze
attr_accessor :project, :current_user, :author
def initialize(project, current_user = nil)
......@@ -54,9 +55,9 @@ module Gitlab
def self.references_pattern
return @pattern if @pattern
patterns = REFERABLES.map do |ref|
ref.to_s.classify.constantize.try(:reference_pattern)
end
patterns = REFERABLES.map do |type|
Banzai::ReferenceParser[type].reference_type.to_s.classify.constantize.try(:reference_pattern)
end.uniq
@pattern = Regexp.union(patterns.compact)
end
......
......@@ -4352,7 +4352,7 @@ msgstr ""
msgid "ContainerRegistry|Docker connection error"
msgstr ""
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token %{personalAccessTokensDocLinkEnd}instead of a password."
msgid "ContainerRegistry|If you are not already logged in, you need to authenticate to the Container Registry by using your GitLab username and password. If you have %{twofaDocLinkStart}Two-Factor Authentication%{twofaDocLinkEnd} enabled, use a %{personalAccessTokensDocLinkStart}Personal Access Token%{personalAccessTokensDocLinkEnd} instead of a password."
msgstr ""
msgid "ContainerRegistry|Last Updated"
......@@ -4384,6 +4384,9 @@ msgstr ""
msgid "ContainerRegistry|Tag ID"
msgstr ""
msgid "ContainerRegistry|There are no container images available in this group"
msgstr ""
msgid "ContainerRegistry|There are no container images stored for this project"
msgstr ""
......@@ -4393,6 +4396,9 @@ msgstr ""
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Push at least one Docker image in one of this group's projects in order to show up here. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
......@@ -11193,6 +11199,9 @@ msgstr ""
msgid "Number of LOCs per commit"
msgstr ""
msgid "Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value."
msgstr ""
msgid "Number of changes (branches or tags) in a single push to determine whether webhooks and services will be fired or not. Webhooks and services won't be submitted if it surpasses that value."
msgstr ""
......
......@@ -62,10 +62,10 @@ gitlab:
unicorn:
resources:
requests:
cpu: 600m
cpu: 400m
memory: 1.4G
limits:
cpu: 1.2G
cpu: 800m
memory: 2.8G
deployment:
readinessProbe:
......@@ -95,10 +95,10 @@ gitlab-runner:
minio:
resources:
requests:
cpu: 100m
cpu: 5m
memory: 128M
limits:
cpu: 200m
cpu: 10m
memory: 280M
nginx-ingress:
controller:
......@@ -107,10 +107,10 @@ nginx-ingress:
replicaCount: 2
resources:
requests:
cpu: 150m
cpu: 100m
memory: 250M
limits:
cpu: 300m
cpu: 200m
memory: 500M
minAvailable: 1
service:
......
# frozen_string_literal: true
require 'spec_helper'
describe Groups::Registry::RepositoriesController do
let_it_be(:user) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:group, reload: true) { create(:group) }
before do
stub_container_registry_config(enabled: true)
group.add_owner(user)
group.add_guest(guest)
sign_in(user)
end
context 'GET #index' do
context 'when container registry is enabled' do
it 'show index page' do
get :index, params: {
group_id: group
}
expect(response).to have_gitlab_http_status(:ok)
end
it 'has the correct response schema' do
get :index, params: {
group_id: group,
format: :json
}
expect(response).to match_response_schema('registry/repositories')
end
it 'returns a list of projects for json format' do
project = create(:project, group: group)
repo = create(:container_repository, project: project)
get :index, params: {
group_id: group,
format: :json
}
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_kind_of(Array)
expect(json_response.first).to include(
'id' => repo.id,
'name' => repo.name
)
end
it 'tracks the event' do
expect(Gitlab::Tracking).to receive(:event).with(anything, 'list_repositories', {})
get :index, params: {
group_id: group
}
end
end
context 'container registry is disabled' do
before do
stub_container_registry_config(enabled: false)
end
it 'renders not found' do
get :index, params: {
group_id: group
}
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'user do not have acces to container registry' do
before do
sign_out(user)
sign_in(guest)
end
it 'renders not found' do
get :index, params: {
group_id: group
}
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
......@@ -7,7 +7,7 @@ FactoryBot.define do
group { nil }
project_id { nil }
group_id { nil }
parent { nil }
resource_parent { nil }
end
after(:build, :stub) do |board, evaluator|
......@@ -19,9 +19,9 @@ FactoryBot.define do
board.project = evaluator.project
elsif evaluator.project_id
board.project_id = evaluator.project_id
elsif evaluator.parent
id = evaluator.parent.id
evaluator.parent.is_a?(Group) ? board.group_id = id : evaluator.project_id = id
elsif evaluator.resource_parent
id = evaluator.resource_parent.id
evaluator.resource_parent.is_a?(Group) ? board.group_id = id : evaluator.project_id = id
else
board.project = create(:project, :empty_repo)
end
......
......@@ -9,7 +9,7 @@ FactoryBot.define do
group { nil }
project_id { nil }
group_id { nil }
parent { nil }
resource_parent { nil }
end
trait :active do
......@@ -34,9 +34,9 @@ FactoryBot.define do
milestone.project = evaluator.project
elsif evaluator.project_id
milestone.project_id = evaluator.project_id
elsif evaluator.parent
id = evaluator.parent.id
evaluator.parent.is_a?(Group) ? evaluator.group_id = id : evaluator.project_id = id
elsif evaluator.resource_parent
id = evaluator.resource_parent.id
evaluator.resource_parent.is_a?(Group) ? evaluator.group_id = id : evaluator.project_id = id
else
milestone.project = create(:project)
end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Groups > sidebar' do
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
group.add_developer(user)
sign_in(user)
end
context 'Package menu' do
context 'when container registry is enabled' do
before do
stub_container_registry_config(enabled: true)
visit group_path(group)
end
it 'shows main menu' do
within '.nav-sidebar' do
expect(page).to have_link(_('Packages'))
end
end
it 'has container registry link' do
within '.nav-sidebar' do
expect(page).to have_link(_('Container Registry'))
end
end
end
context 'when container registry is disabled' do
before do
stub_container_registry_config(enabled: false)
visit group_path(group)
end
it 'does not have container registry link' do
within '.nav-sidebar' do
expect(page).not_to have_link(_('Container Registry'))
end
end
end
end
end
......@@ -12,7 +12,7 @@ describe "User deletes milestone", :js do
end
context "when milestone belongs to project" do
let!(:milestone) { create(:milestone, parent: project, title: "project milestone") }
let!(:milestone) { create(:milestone, resource_parent: project, title: "project milestone") }
it "deletes milestone" do
project.add_developer(user)
......@@ -30,8 +30,8 @@ describe "User deletes milestone", :js do
end
context "when milestone belongs to group" do
let!(:milestone_to_be_deleted) { create(:milestone, parent: group, title: "group milestone 1") }
let!(:milestone) { create(:milestone, parent: group, title: "group milestone 2") }
let!(:milestone_to_be_deleted) { create(:milestone, resource_parent: group, title: "group milestone 1") }
let!(:milestone) { create(:milestone, resource_parent: group, title: "group milestone 2") }
it "deletes milestone" do
group.add_developer(user)
......
......@@ -10,7 +10,7 @@ describe Boards::VisitsFinder do
let(:project) { create(:project) }
let(:project_board) { create(:board, project: project) }
subject(:finder) { described_class.new(project_board.parent, user) }
subject(:finder) { described_class.new(project_board.resource_parent, user) }
it 'returns nil when there is no user' do
finder.current_user = nil
......@@ -27,7 +27,7 @@ describe Boards::VisitsFinder do
it 'queries for last N visits' do
expect(BoardProjectRecentVisit).to receive(:latest).with(user, project, count: 5).once
described_class.new(project_board.parent, user).latest(5)
described_class.new(project_board.resource_parent, user).latest(5)
end
end
......@@ -35,7 +35,7 @@ describe Boards::VisitsFinder do
let(:group) { create(:group) }
let(:group_board) { create(:board, group: group) }
subject(:finder) { described_class.new(group_board.parent, user) }
subject(:finder) { described_class.new(group_board.resource_parent, user) }
it 'returns nil when there is no user' do
finder.current_user = nil
......@@ -52,7 +52,7 @@ describe Boards::VisitsFinder do
it 'queries for last N visits' do
expect(BoardGroupRecentVisit).to receive(:latest).with(user, group, count: 5).once
described_class.new(group_board.parent, user).latest(5)
described_class.new(group_board.resource_parent, user).latest(5)
end
end
end
......
......@@ -80,6 +80,81 @@ describe('Jobs Store Mutations', () => {
expect(stateCopy.traceSize).toEqual(511846);
expect(stateCopy.isTraceComplete).toEqual(true);
});
describe('with new job log', () => {
let stateWithNewLog;
beforeEach(() => {
gon.features = gon.features || {};
gon.features.jobLogJson = true;
stateWithNewLog = state();
});
afterEach(() => {
gon.features.jobLogJson = false;
});
describe('log.lines', () => {
describe('when append is true', () => {
it('sets the parsed log ', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
append: true,
size: 511846,
complete: true,
lines: [
{
offset: 1,
content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
},
],
});
expect(stateWithNewLog.trace).toEqual([
{
offset: 1,
content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
lineNumber: 0,
},
]);
});
});
describe('when it is defined', () => {
it('sets the parsed log ', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
append: false,
size: 511846,
complete: true,
lines: [
{ offset: 0, content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }] },
],
});
expect(stateWithNewLog.trace).toEqual([
{
offset: 0,
content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }],
lineNumber: 0,
},
]);
});
});
describe('when it is null', () => {
it('sets the default value', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateWithNewLog, {
append: true,
html,
size: 511846,
complete: false,
lines: null,
});
expect(stateWithNewLog.trace).toEqual([]);
});
});
});
});
});
describe('STOP_POLLING_TRACE', () => {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment