Commit 68a316a2 authored by Robert Speicher's avatar Robert Speicher

Merge remote-tracking branch 'origin/master' into rs-security-master-conflict

parents 9f3d696e 3e8bc389
......@@ -143,7 +143,7 @@ rspec-ee frontend_fixture:
extends:
- .frontend-fixtures-base
- .frontend:rules:default-frontend-jobs-ee
parallel: 2
parallel: 3
graphql-schema-dump:
variables:
......
......@@ -26,7 +26,6 @@ update-tests-metadata:
- .test-metadata:rules:update-tests-metadata
stage: post-test
dependencies:
- retrieve-tests-metadata
- setup-test-env
- rspec migration pg12
- rspec frontend_fixture
......
a8520a1568f0c0515eef6931c01b3fa8e55e7985
f69cea16bcc88ddf29fb6c4c67a5d788fbc00f9a
......@@ -335,6 +335,8 @@ gem 'method_source', '~> 1.0', require: false
gem 'webrick', '~> 1.6.1', require: false
gem 'prometheus-client-mmap', '~> 0.12.0', require: 'prometheus/client'
gem 'warning', '~> 1.2.0'
group :development do
gem 'lefthook', '~> 0.7.0', require: false
gem 'solargraph', '~> 0.42', require: false
......
......@@ -1339,6 +1339,7 @@ GEM
vmstat (2.3.0)
warden (1.2.8)
rack (>= 2.0.6)
warning (1.2.0)
webauthn (2.3.0)
android_key_attestation (~> 0.3.0)
awrence (~> 1.1)
......@@ -1648,6 +1649,7 @@ DEPENDENCIES
validates_hostname (~> 1.0.11)
version_sorter (~> 2.2.4)
vmstat (~> 2.3.0)
warning (~> 1.2.0)
webauthn (~> 2.3)
webmock (~> 3.9.1)
webrick (~> 1.6.1)
......
......@@ -18,7 +18,7 @@ const boardDefaults = {
id: false,
name: '',
labels: [],
milestone_id: undefined,
milestone: {},
iteration_id: undefined,
assignee: {},
weight: null,
......@@ -190,10 +190,9 @@ export default {
return {
weight: this.board.weight,
assigneeId: this.board.assignee?.id || null,
milestoneId:
this.board.milestone?.id || this.board.milestone?.id === 0
? convertToGraphQLId(TYPE_MILESTONE, this.board.milestone.id)
: null,
milestoneId: this.board.milestone?.id
? convertToGraphQLId(TYPE_MILESTONE, this.board.milestone.id)
: null,
iterationId: this.board.iteration_id
? convertToGraphQLId(TYPE_ITERATION, this.board.iteration_id)
: null,
......@@ -304,9 +303,14 @@ export default {
});
},
setAssignee(assigneeId) {
this.board.assignee = {
this.$set(this.board, 'assignee', {
id: assigneeId,
};
});
},
setMilestone(milestoneId) {
this.$set(this.board, 'milestone', {
id: milestoneId,
});
},
},
};
......@@ -376,6 +380,7 @@ export default {
@set-iteration="setIteration"
@set-board-labels="setBoardLabels"
@set-assignee="setAssignee"
@set-milestone="setMilestone"
/>
</form>
</gl-modal>
......
import { s__ } from '~/locale';
import { __, s__ } from '~/locale';
export const DEFAULT_DAYS_IN_PAST = 30;
export const DEFAULT_DAYS_TO_DISPLAY = 30;
......@@ -22,3 +22,11 @@ export const PAGINATION_SORT_DIRECTION_ASC = 'asc';
export const STAGE_TITLE_STAGING = 'staging';
export const STAGE_TITLE_TEST = 'test';
export const I18N_VSA_ERROR_STAGES = __(
'There was an error fetching value stream analytics stages.',
);
export const I18N_VSA_ERROR_STAGE_MEDIAN = __('There was an error fetching median data for stages');
export const I18N_VSA_ERROR_SELECTED_STAGE = __(
'There was an error fetching data for the selected stage',
);
......@@ -7,7 +7,11 @@ import {
} from '~/api/analytics_api';
import createFlash from '~/flash';
import { __ } from '~/locale';
import { DEFAULT_DAYS_TO_DISPLAY, DEFAULT_VALUE_STREAM } from '../constants';
import {
DEFAULT_DAYS_TO_DISPLAY,
DEFAULT_VALUE_STREAM,
I18N_VSA_ERROR_STAGE_MEDIAN,
} from '../constants';
import * as types from './mutation_types';
export const setSelectedValueStream = ({ commit, dispatch }, valueStream) => {
......@@ -120,9 +124,7 @@ export const fetchStageMedians = ({
.then((data) => commit(types.RECEIVE_STAGE_MEDIANS_SUCCESS, data))
.catch((error) => {
commit(types.RECEIVE_STAGE_MEDIANS_ERROR, error);
createFlash({
message: __('There was an error fetching median data for stages'),
});
createFlash({ message: I18N_VSA_ERROR_STAGE_MEDIAN });
});
};
......
import { isArray } from 'lodash';
/**
* Ids generated by GraphQL endpoints are usually in the format
* gid://gitlab/Environments/123. This method checks if the passed id follows that format
*
* @param {String|Number} id The id value
* @returns {Boolean}
*/
export const isGid = (id) => {
if (typeof id === 'string' && id.startsWith('gid://gitlab/')) {
return true;
}
return false;
};
/**
* Ids generated by GraphQL endpoints are usually in the format
* gid://gitlab/Environments/123. This method extracts Id number
......@@ -35,6 +50,10 @@ export const convertToGraphQLId = (type, id) => {
throw new TypeError(`id must be a number or string; got ${typeof id}`);
}
if (isGid(id)) {
return id;
}
return `gid://gitlab/${type}/${id}`;
};
......
......@@ -186,7 +186,7 @@ export default {
data-testid="commit-button"
class="qa-commit-button"
category="primary"
variant="success"
variant="confirm"
@click="commit"
>
{{ __('Commit') }}
......
......@@ -9,13 +9,14 @@ import {
GlSprintf,
GlButton,
GlFormInput,
GlFormCheckboxGroup,
} from '@gitlab/ui';
import { partition, isString } from 'lodash';
import Api from '~/api';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { s__, sprintf } from '~/locale';
import { INVITE_MEMBERS_IN_COMMENT, GROUP_FILTERS } from '../constants';
import { INVITE_MEMBERS_IN_COMMENT, GROUP_FILTERS, MEMBER_AREAS_OF_FOCUS } from '../constants';
import eventHub from '../event_hub';
import {
responseMessageFromError,
......@@ -36,6 +37,7 @@ export default {
GlSprintf,
GlButton,
GlFormInput,
GlFormCheckboxGroup,
MembersTokenSelect,
GroupSelect,
},
......@@ -74,6 +76,14 @@ export default {
type: String,
required: true,
},
areasOfFocusOptions: {
type: Array,
required: true,
},
noSelectionAreasOfFocus: {
type: Array,
required: true,
},
},
data() {
return {
......@@ -83,6 +93,7 @@ export default {
inviteeType: 'members',
newUsersToInvite: [],
selectedDate: undefined,
selectedAreasOfFocus: [],
groupToBeSharedWith: {},
source: 'unknown',
invalidFeedbackMessage: '',
......@@ -128,10 +139,21 @@ export default {
this.newUsersToInvite.length === 0 && Object.keys(this.groupToBeSharedWith).length === 0
);
},
areasOfFocusEnabled() {
return this.areasOfFocusOptions.length !== 0;
},
areasOfFocusForPost() {
if (this.selectedAreasOfFocus.length === 0 && this.areasOfFocusEnabled) {
return this.noSelectionAreasOfFocus;
}
return this.selectedAreasOfFocus;
},
},
mounted() {
eventHub.$on('openModal', (options) => {
this.openModal(options);
this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.view);
});
},
methods: {
......@@ -152,9 +174,12 @@ export default {
this.$root.$emit(BV_SHOW_MODAL, this.modalId);
},
trackEvent(experimentName, eventName) {
const tracking = new ExperimentTracking(experimentName);
tracking.event(eventName);
},
closeModal() {
this.resetFields();
this.$root.$emit(BV_HIDE_MODAL, this.modalId);
this.$refs.modal.hide();
},
sendInvite() {
if (this.isInviteGroup) {
......@@ -165,9 +190,10 @@ export default {
},
trackInvite() {
if (this.source === INVITE_MEMBERS_IN_COMMENT) {
const tracking = new ExperimentTracking(INVITE_MEMBERS_IN_COMMENT);
tracking.event('comment_invite_success');
this.trackEvent(INVITE_MEMBERS_IN_COMMENT, 'comment_invite_success');
}
this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.submit);
},
resetFields() {
this.isLoading = false;
......@@ -176,6 +202,7 @@ export default {
this.newUsersToInvite = [];
this.groupToBeSharedWith = {};
this.invalidFeedbackMessage = '';
this.selectedAreasOfFocus = [];
},
changeSelectedItem(item) {
this.selectedAccessLevel = item;
......@@ -223,6 +250,7 @@ export default {
email: usersToInviteByEmail,
access_level: this.selectedAccessLevel,
invite_source: this.source,
areas_of_focus: this.areasOfFocusForPost,
};
},
addByUserIdPostData(usersToAddById) {
......@@ -231,6 +259,7 @@ export default {
user_id: usersToAddById,
access_level: this.selectedAccessLevel,
invite_source: this.source,
areas_of_focus: this.areasOfFocusForPost,
};
},
shareWithGroupPostData(groupToBeSharedWith) {
......@@ -304,18 +333,22 @@ export default {
inviteButtonText: s__('InviteMembersModal|Invite'),
cancelButtonText: s__('InviteMembersModal|Cancel'),
headerCloseLabel: s__('InviteMembersModal|Close invite team members'),
areasOfFocusLabel: s__(
'InviteMembersModal|What would you like new member(s) to focus on? (optional)',
),
},
membersTokenSelectLabelId: 'invite-members-input',
};
</script>
<template>
<gl-modal
ref="modal"
:modal-id="modalId"
size="sm"
data-qa-selector="invite_members_modal_content"
:title="$options.labels[inviteeType].modalTitle"
:header-close-label="$options.labels.headerCloseLabel"
@close="resetFields"
@hidden="resetFields"
>
<div>
<p ref="introText">
......@@ -351,7 +384,7 @@ export default {
/>
</gl-form-group>
<label class="gl-font-weight-bold gl-mt-3">{{ $options.labels.accessLevel }}</label>
<label class="gl-mt-3">{{ $options.labels.accessLevel }}</label>
<div class="gl-mt-2 gl-w-half gl-xs-w-full">
<gl-dropdown
class="gl-shadow-none gl-w-full"
......@@ -381,7 +414,7 @@ export default {
</gl-sprintf>
</div>
<label class="gl-font-weight-bold gl-mt-5 gl-display-block" for="expires_at">{{
<label class="gl-mt-5 gl-display-block" for="expires_at">{{
$options.labels.accessExpireDate
}}</label>
<div class="gl-mt-2 gl-w-half gl-xs-w-full gl-display-inline-block">
......@@ -400,6 +433,16 @@ export default {
</template>
</gl-datepicker>
</div>
<div v-if="areasOfFocusEnabled">
<label class="gl-mt-5">
{{ $options.labels.areasOfFocusLabel }}
</label>
<gl-form-checkbox-group
v-model="selectedAreasOfFocus"
:options="areasOfFocusOptions"
data-testid="area-of-focus-checks"
/>
</div>
</div>
<template #modal-footer>
......
......@@ -3,6 +3,11 @@ import { __ } from '~/locale';
export const SEARCH_DELAY = 200;
export const INVITE_MEMBERS_IN_COMMENT = 'invite_members_in_comment';
export const MEMBER_AREAS_OF_FOCUS = {
name: 'member_areas_of_focus',
view: 'view',
submit: 'submit',
};
export const GROUP_FILTERS = {
ALL: 'all',
......
......@@ -23,6 +23,8 @@ export default function initInviteMembersModal() {
defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10),
groupSelectFilter: el.dataset.groupsFilter,
groupSelectParentId: parseInt(el.dataset.parentId, 10),
areasOfFocusOptions: JSON.parse(el.dataset.areasOfFocusOptions),
noSelectionAreasOfFocus: JSON.parse(el.dataset.noSelectionAreasOfFocus),
},
}),
});
......
......@@ -82,9 +82,9 @@ export default class MergeRequestTabs {
this.mergeRequestTabPanes && this.mergeRequestTabPanes.querySelectorAll
? this.mergeRequestTabPanes.querySelectorAll('.tab-pane')
: null;
const navbar = document.querySelector('.navbar-gitlab');
const peek = document.getElementById('js-peek');
const paddingTop = 16;
this.navbar = document.querySelector('.navbar-gitlab');
this.peek = document.getElementById('js-peek');
this.paddingTop = 16;
this.commitsTab = document.querySelector('.tab-content .commits.tab-pane');
......@@ -99,15 +99,6 @@ export default class MergeRequestTabs {
this.setCurrentAction = this.setCurrentAction.bind(this);
this.tabShown = this.tabShown.bind(this);
this.clickTab = this.clickTab.bind(this);
this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
if (peek) {
this.stickyTop += peek.offsetHeight;
}
if (this.mergeRequestTabs) {
this.stickyTop += this.mergeRequestTabs.offsetHeight;
}
if (stubLocation) {
location = stubLocation;
......@@ -520,4 +511,18 @@ export default class MergeRequestTabs {
}
}, 0);
}
get stickyTop() {
let stickyTop = this.navbar ? this.navbar.offsetHeight : 0;
if (this.peek) {
stickyTop += this.peek.offsetHeight;
}
if (this.mergeRequestTabs) {
stickyTop += this.mergeRequestTabs.offsetHeight;
}
return stickyTop;
}
}
......@@ -122,7 +122,7 @@ export default {
/>
</div>
<div class="gl-mt-4">
<gl-button category="primary" variant="success" @click="onSubmit">
<gl-button category="primary" variant="confirm" @click="onSubmit">
{{ s__('CompareRevisions|Compare') }}
</gl-button>
<gl-button data-testid="swapRevisionsButton" class="btn btn-default" @click="onSwapRevision">
......
#import "./milestone.fragment.graphql"
query groupMilestones($fullPath: ID!, $title: String, $state: MilestoneStateEnum) {
workspace: group(fullPath: $fullPath) {
__typename
id
attributes: milestones(
searchTitle: $title
state: $state
sort: EXPIRED_LAST_DUE_DATE_ASC
first: 20
includeAncestors: true
) {
nodes {
...MilestoneFragment
state
}
}
}
}
/* eslint-disable @gitlab/require-i18n-strings */
import { __ } from '~/locale';
import DropdownWidget from './dropdown_widget.vue';
export default {
component: DropdownWidget,
title: 'vue_shared/components/dropdown/dropdown_widget/dropdown_widget',
};
const Template = (args, { argTypes }) => ({
components: { DropdownWidget },
props: Object.keys(argTypes),
template: '<dropdown-widget v-bind="$props" v-on="$props" />',
});
export const Default = Template.bind({});
Default.args = {
options: [
{ id: 'gid://gitlab/Milestone/-1', title: __('Any Milestone') },
{ id: 'gid://gitlab/Milestone/0', title: __('No Milestone') },
{ id: 'gid://gitlab/Milestone/-2', title: __('Upcoming') },
{ id: 'gid://gitlab/Milestone/-3', title: __('Started') },
],
selectText: 'Select',
searchText: 'Search',
};
<script>
import {
GlLoadingIcon,
GlDropdown,
GlDropdownForm,
GlDropdownDivider,
GlDropdownItem,
GlSearchBoxByType,
} from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlLoadingIcon,
GlDropdown,
GlDropdownForm,
GlDropdownDivider,
GlDropdownItem,
GlSearchBoxByType,
},
props: {
selectText: {
type: String,
required: false,
default: __('Select'),
},
searchText: {
type: String,
required: false,
default: __('Search'),
},
presetOptions: {
type: Array,
required: false,
default: () => [],
},
options: {
type: Array,
required: false,
default: () => [],
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
selected: {
type: Object,
required: false,
default: () => {},
},
searchTerm: {
type: String,
required: false,
default: '',
},
},
computed: {
isSearchEmpty() {
return this.searchTerm === '' && !this.isLoading;
},
noOptionsFound() {
return !this.isSearchEmpty && this.options.length === 0;
},
},
methods: {
selectOption(option) {
this.$emit('set-option', option || null);
},
isSelected(option) {
return this.selected && this.selected.title === option.title;
},
showDropdown() {
this.$refs.dropdown.show();
},
setFocus() {
this.$refs.search.focusInput();
},
setSearchTerm(search) {
this.$emit('set-search', search);
},
},
i18n: {
noMatchingResults: __('No matching results'),
},
};
</script>
<template>
<gl-dropdown
ref="dropdown"
:text="selectText"
lazy
menu-class="gl-w-full!"
class="gl-w-full"
v-on="$listeners"
@shown="setFocus"
>
<template #header>
<gl-search-box-by-type
ref="search"
:value="searchTerm"
:placeholder="searchText"
class="js-dropdown-input-field"
@input="setSearchTerm"
/>
</template>
<gl-dropdown-form class="gl-relative gl-min-h-7">
<gl-loading-icon
v-if="isLoading"
size="md"
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
/>
<template v-else>
<template v-if="isSearchEmpty && presetOptions.length > 0">
<gl-dropdown-item
v-for="option in presetOptions"
:key="option.id"
:is-checked="isSelected(option)"
:is-check-centered="true"
:is-check-item="true"
@click="selectOption(option)"
>
{{ option.title }}
</gl-dropdown-item>
<gl-dropdown-divider />
</template>
<gl-dropdown-item
v-for="option in options"
:key="option.id"
:is-checked="isSelected(option)"
:is-check-centered="true"
:is-check-item="true"
data-testid="unselected-option"
@click="selectOption(option)"
>
{{ option.title }}
</gl-dropdown-item>
<gl-dropdown-item v-if="noOptionsFound" class="gl-pl-6!">
{{ $options.i18n.noMatchingResults }}
</gl-dropdown-item>
</template>
</gl-dropdown-form>
<template #footer>
<slot name="footer"></slot>
</template>
</gl-dropdown>
</template>
......@@ -3,7 +3,7 @@ import { reportTypeToSecurityReportTypeEnum } from 'ee_else_ce/vue_shared/securi
import createFlash from '~/flash';
import { s__ } from '~/locale';
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql';
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
import { extractSecurityReportArtifactsFromMergeRequest } from '~/vue_shared/security_reports/utils';
export default {
......
fragment JobArtifacts on Pipeline {
jobs(securityReportTypes: $reportTypes) {
nodes {
name
artifacts {
nodes {
downloadPath
fileType
}
}
}
}
}
#import "../fragments/job_artifacts.fragment.graphql"
query getCorpuses($projectPath: ID!, $iid: ID, $reportTypes: [SecurityReportTypeEnum!]) {
project(fullPath: $projectPath) {
pipeline(iid: $iid) {
id
jobs(securityReportTypes: $reportTypes) {
nodes {
name
artifacts {
nodes {
downloadPath
fileType
}
}
}
}
...JobArtifacts
}
}
}
......@@ -13,7 +13,7 @@ import {
REPORT_TYPE_SECRET_DETECTION,
reportTypeToSecurityReportTypeEnum,
} from './constants';
import securityReportMergeRequestDownloadPathsQuery from './queries/security_report_merge_request_download_paths.query.graphql';
import securityReportMergeRequestDownloadPathsQuery from './graphql/queries/security_report_merge_request_download_paths.query.graphql';
import store from './store';
import { MODULE_SAST, MODULE_SECRET_DETECTION } from './store/constants';
import { extractSecurityReportArtifactsFromMergeRequest } from './utils';
......
......@@ -854,12 +854,12 @@ table.code {
@include media-breakpoint-up(sm) {
position: -webkit-sticky;
position: sticky;
top: $header-height;
top: $header-height + $mr-tabs-height;
background-color: $white;
z-index: 200;
.with-performance-bar & {
top: $header-height + $performance-bar-height;
top: $header-height + $mr-tabs-height + $performance-bar-height;
}
&.is-stuck {
......
......@@ -1003,10 +1003,10 @@ $tabs-holder-z-index: 250;
.mr-compare {
.diff-file .file-title-flex-parent {
top: $header-height + 51px;
top: $header-height + $mr-tabs-height + 36px;
.with-performance-bar & {
top: $performance-bar-height + $header-height + 51px;
top: $performance-bar-height + $header-height + $mr-tabs-height + 36px;
}
}
}
......
......@@ -71,7 +71,7 @@ class Admin::SessionsController < ApplicationController
::Users::ValidateOtpService.new(user).execute(user_params[:otp_attempt])
valid_otp_attempt = otp_validation_result[:status] == :success
return valid_otp_attempt if Gitlab::Database.main.read_only?
return valid_otp_attempt if Gitlab::Database.read_only?
valid_otp_attempt || user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end
......
......@@ -136,7 +136,9 @@ class Admin::UsersController < Admin::ApplicationController
end
def unban
if update_user { |user| user.activate }
result = Users::UnbanService.new(current_user).execute(user)
if result[:status] == :success
redirect_back_or_admin_user(notice: _("Successfully unbanned"))
else
redirect_back_or_admin_user(alert: _("Error occurred. User was not unbanned"))
......
......@@ -27,7 +27,7 @@ module Boards
list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
issues = issues_from(list_service)
if Gitlab::Database.main.read_write? && !board.disabled_for?(current_user)
if Gitlab::Database.read_write? && !board.disabled_for?(current_user)
Issue.move_nulls_to_end(issues)
end
......
......@@ -47,7 +47,7 @@ module AuthenticatesWithTwoFactorForAdminMode
# Remove any lingering user data from login
session.delete(:otp_user_id)
user.save! unless Gitlab::Database.main.read_only?
user.save! unless Gitlab::Database.read_only?
# The admin user has successfully passed 2fa, enable admin mode ignoring password
enable_admin_mode
......
......@@ -148,7 +148,7 @@ module IssuableActions
# on GET requests.
# This is just a fail-safe in case notes_filter is sent via GET request in GitLab Geo.
# In some cases, we also force the filter to not be persisted with the `persist_filter` param
if Gitlab::Database.main.read_only? || params[:persist_filter] == 'false'
if Gitlab::Database.read_only? || params[:persist_filter] == 'false'
notes_filter_param || current_user&.notes_filter_for(issuable)
else
notes_filter = current_user&.set_notes_filter(notes_filter_param, issuable) || notes_filter_param
......
......@@ -17,7 +17,7 @@ module RecordUserLastActivity
def set_user_last_activity
return unless request.get?
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
if current_user && current_user.last_activity_on != Date.today
Users::ActivityService.new(current_user).execute
......
......@@ -41,7 +41,7 @@ module SortingPreference
sort_param = params[:sort]
sort_param ||= user_preference[field]
return sort_param if Gitlab::Database.main.read_only?
return sort_param if Gitlab::Database.read_only?
if user_preference[field] != sort_param
user_preference.update(field => sort_param)
......
......@@ -388,6 +388,7 @@ class ProjectsController < Projects::ApplicationController
analytics_access_level
operations_access_level
security_and_compliance_access_level
container_registry_access_level
]
end
......
......@@ -77,7 +77,7 @@ module Repositories
def update_fetch_statistics
return unless project
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
return unless repo_type.project?
OnboardingProgressService.async(project.namespace_id).execute(action: :git_pull)
......
......@@ -126,7 +126,7 @@ module Repositories
# Overridden in EE
def batch_operation_disallowed?
upload_request? && Gitlab::Database.main.read_only?
upload_request? && Gitlab::Database.read_only?
end
# Overridden in EE
......
......@@ -29,7 +29,7 @@ module Mutations
end
def ready?(**args)
raise_resource_not_available_error! ERROR_MESSAGE if Gitlab::Database.main.read_only?
raise_resource_not_available_error! ERROR_MESSAGE if Gitlab::Database.read_only?
missing_args = self.class.arguments.values
.reject { |arg| arg.accepts?(args.fetch(arg.keyword, :not_given)) }
......
......@@ -344,7 +344,7 @@ module ApplicationHelper
# Overridden in EE
def read_only_message
return unless Gitlab::Database.main.read_only?
return unless Gitlab::Database.read_only?
_('You are on a read-only GitLab instance.')
end
......
......@@ -215,7 +215,7 @@ module CommitsHelper
path = project_blob_path(project, tree_join(commit_sha, diff_new_path))
title = replaced ? _('View replaced file @ ') : _('View file @ ')
link_to(path, class: 'btn gl-button btn-default') do
link_to(path, class: 'btn gl-button btn-default gl-ml-3') do
raw(title) + content_tag(:span, truncate_sha(commit_sha), class: 'commit-sha')
end
end
......
......@@ -39,4 +39,43 @@ module InviteMembersHelper
{}
end
end
def common_invite_modal_dataset(source)
dataset = {
id: source.id,
name: source.name,
default_access_level: Gitlab::Access::GUEST
}
experiment(:member_areas_of_focus, user: current_user) do |e|
e.publish_to_database
e.control { dataset.merge!(areas_of_focus_options: [], no_selection_areas_of_focus: []) }
e.candidate { dataset.merge!(areas_of_focus_options: member_areas_of_focus_options.to_json, no_selection_areas_of_focus: ['no_selection']) }
end
dataset
end
private
def member_areas_of_focus_options
[
{
value: 'Contribute to the codebase', text: s_('InviteMembersModal|Contribute to the codebase')
},
{
value: 'Collaborate on open issues and merge requests', text: s_('InviteMembersModal|Collaborate on open issues and merge requests')
},
{
value: 'Configure CI/CD', text: s_('InviteMembersModal|Configure CI/CD')
},
{
value: 'Configure security features', text: s_('InviteMembersModal|Configure security features')
},
{
value: 'Other', text: s_('InviteMembersModal|Other')
}
]
end
end
......@@ -354,6 +354,29 @@ module ProjectsHelper
project.repository_languages.with_programming_language('HCL').exists? && project.terraform_states.empty?
end
def project_permissions_panel_data(project)
{
packagesAvailable: ::Gitlab.config.packages.enabled,
packagesHelpPath: help_page_path('user/packages/index'),
currentSettings: project_permissions_settings(project),
canDisableEmails: can_disable_emails?(project, current_user),
canChangeVisibilityLevel: can_change_visibility_level?(project, current_user),
allowedVisibilityOptions: project_allowed_visibility_levels(project),
visibilityHelpPath: help_page_path('public_access/public_access'),
registryAvailable: Gitlab.config.registry.enabled,
registryHelpPath: help_page_path('user/packages/container_registry/index'),
lfsAvailable: Gitlab.config.lfs.enabled,
lfsHelpPath: help_page_path('topics/git/lfs/index'),
lfsObjectsExist: project.lfs_objects.exists?,
lfsObjectsRemovalHelpPath: help_page_path('topics/git/lfs/index', anchor: 'removing-objects-from-lfs'),
pagesAvailable: Gitlab.config.pages.enabled,
pagesAccessControlEnabled: Gitlab.config.pages.access_control,
pagesAccessControlForced: ::Gitlab::Pages.access_control_is_forced?,
pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control'),
issuesHelpPath: help_page_path('user/project/issues/index')
}
end
private
def tab_ability_map
......@@ -510,37 +533,11 @@ module ProjectsHelper
metricsDashboardAccessLevel: feature.metrics_dashboard_access_level,
operationsAccessLevel: feature.operations_access_level,
showDefaultAwardEmojis: project.show_default_award_emojis?,
securityAndComplianceAccessLevel: project.security_and_compliance_access_level
}
end
def project_permissions_panel_data(project)
{
packagesAvailable: ::Gitlab.config.packages.enabled,
packagesHelpPath: help_page_path('user/packages/index'),
currentSettings: project_permissions_settings(project),
canDisableEmails: can_disable_emails?(project, current_user),
canChangeVisibilityLevel: can_change_visibility_level?(project, current_user),
allowedVisibilityOptions: project_allowed_visibility_levels(project),
visibilityHelpPath: help_page_path('public_access/public_access'),
registryAvailable: Gitlab.config.registry.enabled,
registryHelpPath: help_page_path('user/packages/container_registry/index'),
lfsAvailable: Gitlab.config.lfs.enabled,
lfsHelpPath: help_page_path('topics/git/lfs/index'),
lfsObjectsExist: project.lfs_objects.exists?,
lfsObjectsRemovalHelpPath: help_page_path('topics/git/lfs/index', anchor: 'removing-objects-from-lfs'),
pagesAvailable: Gitlab.config.pages.enabled,
pagesAccessControlEnabled: Gitlab.config.pages.access_control,
pagesAccessControlForced: ::Gitlab::Pages.access_control_is_forced?,
pagesHelpPath: help_page_path('user/project/pages/introduction', anchor: 'gitlab-pages-access-control'),
issuesHelpPath: help_page_path('user/project/issues/index')
securityAndComplianceAccessLevel: project.security_and_compliance_access_level,
containerRegistryAccessLevel: feature.container_registry_access_level
}
end
def project_permissions_panel_data_json(project)
project_permissions_panel_data(project).to_json.html_safe
end
def project_allowed_visibility_levels(project)
Gitlab::VisibilityLevel.values.select do |level|
project.visibility_level_allowed?(level) && !restricted_levels.include?(level)
......
......@@ -554,6 +554,7 @@ module Ci
.concat(persisted_variables)
.concat(dependency_proxy_variables)
.concat(job_jwt_variables)
.concat(kubernetes_variables)
.concat(scoped_variables)
.concat(job_variables)
.concat(persisted_environment_variables)
......@@ -1172,6 +1173,10 @@ module Ci
end
end
def kubernetes_variables
[] # Overridden in EE
end
def conditionally_allow_failure!(exit_code)
return unless exit_code
......
......@@ -34,7 +34,7 @@ module DeprecatedAssignee
end
def assignee_ids
if Gitlab::Database.main.read_only? && pending_assignees_population?
if Gitlab::Database.read_only? && pending_assignees_population?
return Array(deprecated_assignee_id)
end
......@@ -43,7 +43,7 @@ module DeprecatedAssignee
end
def assignees
if Gitlab::Database.main.read_only? && pending_assignees_population?
if Gitlab::Database.read_only? && pending_assignees_population?
return User.where(id: deprecated_assignee_id)
end
......@@ -56,7 +56,7 @@ module DeprecatedAssignee
# This will make the background migration process quicker (#26496) as it'll have less
# assignee_id rows to look through.
def nullify_deprecated_assignee
return unless persisted? && Gitlab::Database.main.read_only?
return unless persisted? && Gitlab::Database.read_only?
update_column(:assignee_id, nil)
end
......
......@@ -41,7 +41,7 @@ module TokenAuthenticatableStrategies
# Resets the token, but only saves when the database is in read & write mode
def reset_token!(instance)
write_new_token(instance)
instance.save! if Gitlab::Database.main.read_write?
instance.save! if Gitlab::Database.read_write?
end
def self.fabricate(model, field, options)
......
......@@ -2,6 +2,35 @@
module VulnerabilityFindingHelpers
extend ActiveSupport::Concern
end
def matches_signatures(other_signatures, other_uuid)
other_signature_types = other_signatures.index_by(&:algorithm_type)
# highest first
match_result = nil
signatures.sort_by(&:priority).reverse_each do |signature|
matching_other_signature = other_signature_types[signature.algorithm_type]
next if matching_other_signature.nil?
match_result = matching_other_signature == signature
break
end
VulnerabilityFindingHelpers.prepend_mod_with('VulnerabilityFindingHelpers')
if match_result.nil?
[uuid, *signature_uuids].include?(other_uuid)
else
match_result
end
end
def signature_uuids
signatures.map do |signature|
hex_sha = signature.signature_hex
::Security::VulnerabilityUUID.generate(
report_type: report_type,
location_fingerprint: hex_sha,
primary_identifier_fingerprint: primary_identifier&.fingerprint,
project_id: project_id
)
end
end
end
......@@ -2,6 +2,30 @@
module VulnerabilityFindingSignatureHelpers
extend ActiveSupport::Concern
end
# If the location object describes a physical location within a file
# (filename + line numbers), the 'location' algorithm_type should be used
# If the location object describes arbitrary data, then the 'hash'
# algorithm_type should be used.
ALGORITHM_TYPES = { hash: 1, location: 2, scope_offset: 3 }.with_indifferent_access.freeze
class_methods do
def priority(algorithm_type)
raise ArgumentError, "No priority for #{algorithm_type.inspect}" unless ALGORITHM_TYPES.key?(algorithm_type)
ALGORITHM_TYPES[algorithm_type]
end
VulnerabilityFindingSignatureHelpers.prepend_mod_with('VulnerabilityFindingSignatureHelpers')
def algorithm_types
ALGORITHM_TYPES
end
end
def priority
self.class.priority(algorithm_type)
end
def algorithm_types
self.class.algorithm_types
end
end
......@@ -5,7 +5,7 @@ class Packages::PackageFile < ApplicationRecord
delegate :project, :project_id, to: :package
delegate :conan_file_type, to: :conan_file_metadatum
delegate :file_type, :component, :architecture, :fields, to: :debian_file_metadatum, prefix: :debian
delegate :file_type, :dsc?, :component, :architecture, :fields, to: :debian_file_metadatum, prefix: :debian
delegate :channel, :metadata, to: :helm_file_metadatum, prefix: :helm
belongs_to :package
......@@ -33,6 +33,8 @@ class Packages::PackageFile < ApplicationRecord
scope :with_file_name_like, ->(file_name) { where(arel_table[:file_name].matches(file_name)) }
scope :with_files_stored_locally, -> { where(file_store: ::Packages::PackageFileUploader::Store::LOCAL) }
scope :with_format, ->(format) { where(::Packages::PackageFile.arel_table[:file_name].matches("%.#{format}")) }
scope :preload_package, -> { preload(:package) }
scope :preload_conan_file_metadata, -> { preload(:conan_file_metadatum) }
scope :preload_debian_file_metadata, -> { preload(:debian_file_metadatum) }
scope :preload_helm_file_metadata, -> { preload(:helm_file_metadatum) }
......
......@@ -2818,11 +2818,11 @@ class Project < ApplicationRecord
end
def cache_has_external_wiki
update_column(:has_external_wiki, integrations.external_wikis.any?) if Gitlab::Database.main.read_write?
update_column(:has_external_wiki, integrations.external_wikis.any?) if Gitlab::Database.read_write?
end
def cache_has_external_issue_tracker
update_column(:has_external_issue_tracker, integrations.external_issue_trackers.any?) if Gitlab::Database.main.read_write?
update_column(:has_external_issue_tracker, integrations.external_issue_trackers.any?) if Gitlab::Database.read_write?
end
def active_runners_with_tags
......
......@@ -38,7 +38,7 @@ class ProjectStatistics < ApplicationRecord
end
def refresh!(only: [])
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
COLUMNS_TO_REFRESH.each do |column, generator|
if only.empty? || only.include?(column)
......
......@@ -34,7 +34,7 @@ class SnippetStatistics < ApplicationRecord
end
def refresh!
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
update_commit_count
update_repository_size
......
......@@ -80,7 +80,7 @@ class User < ApplicationRecord
# to limit database writes to at most once every hour
# rubocop: disable CodeReuse/ServiceClass
def update_tracked_fields!(request)
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
update_tracked_fields(request)
......@@ -205,6 +205,7 @@ class User < ApplicationRecord
has_one :user_canonical_email
has_one :credit_card_validation, class_name: '::Users::CreditCardValidation'
has_one :atlassian_identity, class_name: 'Atlassian::Identity'
has_one :banned_user, class_name: '::Users::BannedUser'
has_many :reviews, foreign_key: :author_id, inverse_of: :author
......@@ -326,7 +327,6 @@ class User < ApplicationRecord
transition deactivated: :blocked
transition ldap_blocked: :blocked
transition blocked_pending_approval: :blocked
transition banned: :blocked
end
event :ldap_block do
......@@ -363,7 +363,7 @@ class User < ApplicationRecord
end
before_transition do
!Gitlab::Database.main.read_only?
!Gitlab::Database.read_only?
end
# rubocop: disable CodeReuse/ServiceClass
......@@ -380,6 +380,14 @@ class User < ApplicationRecord
NotificationService.new.user_deactivated(user.name, user.notification_email)
end
# rubocop: enable CodeReuse/ServiceClass
after_transition active: :banned do |user|
user.create_banned_user
end
after_transition banned: :active do |user|
user.banned_user&.destroy
end
end
# Scopes
......@@ -848,11 +856,11 @@ class User < ApplicationRecord
end
def remember_me!
super if ::Gitlab::Database.main.read_write?
super if ::Gitlab::Database.read_write?
end
def forget_me!
super if ::Gitlab::Database.main.read_write?
super if ::Gitlab::Database.read_write?
end
def disable_two_factor!
......@@ -1751,7 +1759,7 @@ class User < ApplicationRecord
#
# rubocop: disable CodeReuse/ServiceClass
def increment_failed_attempts!
return if ::Gitlab::Database.main.read_only?
return if ::Gitlab::Database.read_only?
increment_failed_attempts
......@@ -1995,7 +2003,7 @@ class User < ApplicationRecord
def consume_otp!
if self.consumed_timestep != current_otp_timestep
self.consumed_timestep = current_otp_timestep
return Gitlab::Database.main.read_only? ? true : save(validate: false)
return Gitlab::Database.read_only? ? true : save(validate: false)
end
false
......
# frozen_string_literal: true
module Users
class BannedUser < ApplicationRecord
self.primary_key = :user_id
belongs_to :user
validates :user, presence: true
validates :user_id, uniqueness: { message: _("banned user already exists") }
end
end
......@@ -111,7 +111,7 @@ class AuditEventService
end
def log_security_event_to_database
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
event = AuditEvent.new(base_payload.merge(details: @details))
save_or_track event
......@@ -120,7 +120,7 @@ class AuditEventService
end
def log_authentication_event_to_database
return unless Gitlab::Database.main.read_write? && authentication_event?
return unless Gitlab::Database.read_write? && authentication_event?
event = AuthenticationEvent.new(authentication_event_payload)
save_or_track event
......
......@@ -4,7 +4,7 @@ module Boards
module Visits
class CreateService < Boards::BaseService
def execute(board)
return unless current_user && Gitlab::Database.main.read_write?
return unless current_user && Gitlab::Database.read_write?
return unless board
model.visited!(current_user, board)
......
......@@ -18,7 +18,7 @@ module Keys
end
def update?
return false if ::Gitlab::Database.main.read_only?
return false if ::Gitlab::Database.read_only?
last_used = key.last_used_at
......
......@@ -166,7 +166,7 @@ module MergeRequests
strong_memoize(:service_error) do
if !merge_request
ServiceResponse.error(message: 'Invalid argument')
elsif Gitlab::Database.main.read_only?
elsif Gitlab::Database.read_only?
ServiceResponse.error(message: 'Unsupported operation')
end
end
......
......@@ -11,7 +11,7 @@ module Packages
::Gitlab::UsageDataCounters::PackageEventCounter.count(event_name)
end
if Feature.enabled?(:collect_package_events) && Gitlab::Database.main.read_write?
if Feature.enabled?(:collect_package_events) && Gitlab::Database.read_write?
::Packages::Event.create!(
event_type: event_name,
originator: current_user&.id,
......
......@@ -12,7 +12,7 @@ module Packages
DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze
# From https://salsa.debian.org/ftp-team/dak/-/blob/991aaa27a7f7aa773bb9c0cf2d516e383d9cffa0/setup/core-init.d/080_metadatakeys#L9
BINARIES_METADATA = %w(
METADATA_KEYS = %w(
Package
Source
Binary
......@@ -89,15 +89,18 @@ module Packages
@distribution.components.ordered_by_name.each do |component|
@distribution.architectures.ordered_by_name.each do |architecture|
generate_component_file(component, :packages, architecture, :deb)
generate_component_file(component, :di_packages, architecture, :udeb)
end
generate_component_file(component, :source, nil, :dsc)
end
end
def generate_component_file(component, component_file_type, architecture, package_file_type)
paragraphs = @distribution.package_files
.preload_package
.preload_debian_file_metadata
.with_debian_component_name(component.name)
.with_debian_architecture_name(architecture.name)
.with_debian_architecture_name(architecture&.name)
.with_debian_file_type(package_file_type)
.find_each
.map(&method(:package_stanza_from_fields))
......@@ -106,21 +109,49 @@ module Packages
def package_stanza_from_fields(package_file)
[
BINARIES_METADATA.map do |metadata_key|
rfc822_field(metadata_key, package_file.debian_fields[metadata_key])
METADATA_KEYS.map do |metadata_key|
metadata_name = metadata_key
metadata_value = package_file.debian_fields[metadata_key]
if package_file.debian_dsc?
metadata_name = 'Package' if metadata_key == 'Source'
checksum = case metadata_key
when 'Files' then package_file.file_md5
when 'Checksums-Sha256' then package_file.file_sha256
when 'Checksums-Sha1' then package_file.file_sha1
end
if checksum
metadata_value = "\n#{checksum} #{package_file.size} #{package_file.file_name}#{metadata_value}"
end
end
rfc822_field(metadata_name, metadata_value)
end,
rfc822_field('Section', package_file.debian_fields['Section'] || 'misc'),
rfc822_field('Priority', package_file.debian_fields['Priority'] || 'extra'),
rfc822_field('Filename', package_filename(package_file)),
rfc822_field('Size', package_file.size),
rfc822_field('MD5sum', package_file.file_md5),
rfc822_field('SHA256', package_file.file_sha256)
package_file_extra_fields(package_file)
].flatten.compact.join('')
end
def package_filename(package_file)
def package_file_extra_fields(package_file)
if package_file.debian_dsc?
[
rfc822_field('Directory', package_dirname(package_file))
]
else
[
rfc822_field('Filename', "#{package_dirname(package_file)}/#{package_file.file_name}"),
rfc822_field('Size', package_file.size),
rfc822_field('MD5sum', package_file.file_md5),
rfc822_field('SHA256', package_file.file_sha256)
]
end
end
def package_dirname(package_file)
letter = package_file.package.name.start_with?('lib') ? package_file.package.name[0..3] : package_file.package.name[0]
"#{pool_prefix(package_file)}/#{letter}/#{package_file.package.name}/#{package_file.package.version}/#{package_file.file_name}"
"#{pool_prefix(package_file)}/#{letter}/#{package_file.package.name}/#{package_file.package.version}"
end
def pool_prefix(package_file)
......@@ -206,7 +237,8 @@ module Packages
return unless condition
return if value.blank?
"#{name}: #{value.to_s.gsub("\n\n", "\n.\n").gsub("\n", "\n ")}\n"
value = " #{value}" unless value[0] == "\n"
"#{name}:#{value.to_s.gsub("\n\n", "\n.\n").gsub("\n", "\n ")}\n"
end
def valid_until_field
......
......@@ -18,7 +18,7 @@ module PersonalAccessTokens
private
def update?
return false if ::Gitlab::Database.main.read_only?
return false if ::Gitlab::Database.read_only?
last_used = @personal_access_token.last_used_at
......
......@@ -19,7 +19,7 @@ class Repositories::DestroyService < Repositories::BaseService
# never be triggered on a read-only instance.
#
# Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/223272
if Gitlab::Database.main.read_only?
if Gitlab::Database.read_only?
Repositories::ShellDestroyService.new(current_repository).execute
else
container.run_after_commit do
......
......@@ -23,7 +23,7 @@ module Users
private
def record_activity
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
today = Date.today
......
# frozen_string_literal: true
module Users
class BanService < BaseService
def initialize(current_user)
@current_user = current_user
end
class BanService < BannedUserBaseService
private
def execute(user)
if user.ban
log_event(user)
success
else
messages = user.errors.full_messages
error(messages.uniq.join('. '))
end
def update_user(user)
user.ban
end
private
def log_event(user)
Gitlab::AppLogger.info(message: "User banned", user: "#{user.username}", email: "#{user.email}", banned_by: "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}")
def action
:ban
end
end
end
# frozen_string_literal: true
module Users
class BannedUserBaseService < BaseService
def initialize(current_user)
@current_user = current_user
end
def execute(user)
return permission_error unless allowed?
if update_user(user)
log_event(user)
success
else
messages = user.errors.full_messages
error(messages.uniq.join('. '))
end
end
private
attr_reader :current_user
def allowed?
can?(current_user, :admin_all_resources)
end
def permission_error
error(_("You are not allowed to %{action} a user" % { action: action.to_s }), :forbidden)
end
def log_event(user)
Gitlab::AppLogger.info(message: "User #{action}", user: "#{user.username}", email: "#{user.email}", "#{action}_by": "#{current_user.username}", ip_address: "#{current_user.current_sign_in_ip}")
end
end
end
# frozen_string_literal: true
module Users
class UnbanService < BannedUserBaseService
private
def update_user(user)
user.activate
end
def action
:unban
end
end
end
- return unless can_manage_members?(group)
.js-invite-members-modal{ data: { id: group.id,
name: group.name,
is_project: 'false',
.js-invite-members-modal{ data: { is_project: 'false',
access_levels: GroupMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') }.merge(group_select_data(group)) }
help_link: help_page_url('user/permissions') }.merge(group_select_data(group)).merge(common_invite_modal_dataset(group)) }
- return unless can_import_members?
.js-invite-members-modal{ data: { id: project.id,
name: project.name,
is_project: 'true',
.js-invite-members-modal{ data: { is_project: 'true',
access_levels: ProjectMember.access_level_roles.to_json,
default_access_level: Gitlab::Access::GUEST,
help_link: help_page_url('user/permissions') } }
help_link: help_page_url('user/permissions') }.merge(common_invite_modal_dataset(project)) }
......@@ -5,7 +5,7 @@
.file-holder-bottom-radius.file-holder.file.gl-mb-3
.js-file-title.file-title.gl-display-flex.gl-align-items-center.clearfix{ data: { current_action: action } }
.editor-ref.block-truncated.has-tooltip{ title: ref }
= sprite_icon('fork', size: 12)
= sprite_icon('branch', size: 12)
= ref
- if current_action?(:edit) || current_action?(:update)
%span.float-left.gl-mr-3
......
......@@ -4,7 +4,7 @@
%li{ class: "branch-item js-branch-item js-branch-#{branch.name}", data: { name: branch.name } }
.branch-info
.branch-title
= sprite_icon('fork', size: 12, css_class: 'gl-flex-shrink-0')
= sprite_icon('branch', size: 12, css_class: 'gl-flex-shrink-0')
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name gl-ml-3 qa-branch-name' do
= branch.name
- if branch.name == @repository.root_ref
......
......@@ -16,7 +16,7 @@
- unless diff_file.submodule?
.file-actions.gl-display-none.gl-sm-display-flex
- if diff_file.blob&.readable_text?
%span.has-tooltip.gl-mr-3{ title: _("Toggle comments for this file") }
%span.has-tooltip{ title: _("Toggle comments for this file") }
= link_to '#', class: 'js-toggle-diff-comments btn gl-button btn-default btn-icon selected', disabled: @diff_notes_disabled do
= sprite_icon('comment')
\
......
......@@ -19,7 +19,7 @@
.settings-content
= form_for @project, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
%template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data_json(@project)
%template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project).to_json.html_safe
.js-project-permissions-form
- if show_visibility_confirm_modal?(@project)
= render "visibility_modal"
......
......@@ -33,7 +33,7 @@
%span.project-ref-path.has-tooltip{ title: _('Target branch') }
&nbsp;
= link_to project_ref_path(merge_request.project, merge_request.target_branch), class: 'ref-name' do
= sprite_icon('fork', size: 12, css_class: 'fork-sprite')
= sprite_icon('branch', size: 12, css_class: 'fork-sprite')
= merge_request.target_branch
- if merge_request.labels.any?
&nbsp;
......
......@@ -40,7 +40,7 @@
#diff-notes-app.tab-content
#new.commits.tab-pane.active
= render "projects/merge_requests/commits"
#diffs.diffs.tab-pane
#diffs.diffs.tab-pane{ class: "gl-m-0!" }
-# This tab is always loaded via AJAX
- if @pipelines.any?
#pipelines.pipelines.tab-pane
......
......@@ -1077,7 +1077,7 @@
:feature_category: :incident_management
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:resource_boundary: :cpu
:weight: 2
:idempotent:
:tags:
......@@ -1096,7 +1096,7 @@
:feature_category: :incident_management
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:resource_boundary: :cpu
:weight: 2
:idempotent: true
:tags: []
......@@ -2141,7 +2141,7 @@
:feature_category: :metrics
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:resource_boundary: :cpu
:weight: 1
:idempotent: true
:tags:
......
......@@ -26,7 +26,7 @@ module AuthorizedProjectUpdate
private
def lock_key(project)
"#{self.class.name.underscore}/#{project.root_namespace.id}"
"#{self.class.name.underscore}/projects/#{project.id}"
end
end
end
......@@ -124,7 +124,7 @@ module GitGarbageCollectMethods
def update_repository_statistics(resource)
resource.repository.expire_statistics_caches
return if Gitlab::Database.main.read_only? # GitGarbageCollectWorker may be run on a Geo secondary
return if Gitlab::Database.read_only? # GitGarbageCollectWorker may be run on a Geo secondary
update_db_repository_statistics(resource)
end
......
......@@ -4,6 +4,7 @@ class GitlabPerformanceBarStatsWorker
include ApplicationWorker
data_consistency :always
worker_resource_boundary :cpu
sidekiq_options retry: 3
......
......@@ -5,6 +5,7 @@ module IncidentManagement
include ApplicationWorker
data_consistency :always
worker_resource_boundary :cpu
sidekiq_options retry: 3
......
......@@ -5,6 +5,7 @@ module IncidentManagement
include ApplicationWorker
data_consistency :always
worker_resource_boundary :cpu
queue_namespace :incident_management
feature_category :incident_management
......
......@@ -11,7 +11,7 @@ class PagesDomainVerificationCronWorker # rubocop:disable Scalability/Idempotent
worker_resource_boundary :cpu
def perform
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
PagesDomain.needs_verification.with_logging_info.find_each do |domain|
with_context(project: domain.project) do
......
......@@ -12,7 +12,7 @@ class PagesDomainVerificationWorker # rubocop:disable Scalability/IdempotentWork
# rubocop: disable CodeReuse/ActiveRecord
def perform(domain_id)
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
domain = PagesDomain.find_by(id: domain_id)
......
......@@ -44,7 +44,7 @@ class ProjectCacheWorker
# statistics to become accurate if they were already updated once in the
# last 15 minutes.
def update_statistics(project, statistics = [])
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
return unless try_obtain_lease_for(project.id, statistics)
Projects::UpdateStatisticsService.new(project, nil, statistics: statistics).execute
......
......@@ -23,7 +23,7 @@ module Projects
end
def cleanup_orphan_lfs_file_references(resource)
return if Gitlab::Database.main.read_only? # GitGarbageCollectWorker may be run on a Geo secondary
return if Gitlab::Database.read_only? # GitGarbageCollectWorker may be run on a Geo secondary
::Gitlab::Cleanup::OrphanLfsFileReferences.new(resource, dry_run: false, logger: logger).run!
rescue StandardError => err
......
......@@ -12,7 +12,7 @@ class ScheduleMergeRequestCleanupRefsWorker
idempotent!
def perform
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
return unless Feature.enabled?(:merge_request_refs_cleanup, default_enabled: false)
MergeRequestCleanupRefsWorker.perform_with_capacity
......
---
name: agent_kubeconfig_ci_variable
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67089
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337164
milestone: '14.2'
type: development
group: group::configure
default_enabled: false
---
name: member_areas_of_focus
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65273
rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/406
milestone: '14.2'
type: experiment
group: group::expansion
default_enabled: false
# frozen_string_literal: true
def log_deprecations?
via_env_var = Gitlab::Utils.to_boolean(ENV['GITLAB_LOG_DEPRECATIONS'])
# enable by default during development unless explicitly turned off
via_env_var.nil? ? Rails.env.development? : via_env_var
end
if log_deprecations?
# Log deprecation warnings emitted through Kernel#warn, such as from gems or
# the Ruby VM.
Warning.process(/.+is deprecated$/) do |warning|
Gitlab::DeprecationJsonLogger.info(message: warning.strip, source: 'ruby')
# Returning :default means we continue emitting this to stderr as well.
:default
end
# Log deprecation warnings emitted from Rails (see ActiveSupport::Deprecation).
ActiveSupport::Notifications.subscribe('deprecation.rails') do |name, start, finish, id, payload|
Gitlab::DeprecationJsonLogger.info(message: payload[:message].strip, source: 'rails')
end
end
......@@ -4,6 +4,6 @@
# `Shard.connected?` could be cached and return true even though the table doesn't exist
return unless Shard.connected?
return unless ActiveRecord::Migrator.current_version >= 20190402150158
return if Gitlab::Database.main.read_only?
return if Gitlab::Database.read_only?
Shard.populate!
# frozen_string_literal: true
class CreateBannedUsers < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
create_table :banned_users, id: false do |t|
t.timestamps_with_timezone null: false
t.references :user, primary_key: true, default: nil, foreign_key: { on_delete: :cascade }, type: :bigint, index: false, null: false
end
end
end
def down
with_lock_retries do
drop_table :banned_users
end
end
end
f66d8f3bc32996fe7743cc98cfb96fedd86784d38c8debb5143b7adabdfebd18
\ No newline at end of file
......@@ -9959,6 +9959,12 @@ CREATE SEQUENCE badges_id_seq
ALTER SEQUENCE badges_id_seq OWNED BY badges.id;
CREATE TABLE banned_users (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
user_id bigint NOT NULL
);
CREATE TABLE batched_background_migration_jobs (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
......@@ -21092,6 +21098,9 @@ ALTER TABLE ONLY background_migration_jobs
ALTER TABLE ONLY badges
ADD CONSTRAINT badges_pkey PRIMARY KEY (id);
ALTER TABLE ONLY banned_users
ADD CONSTRAINT banned_users_pkey PRIMARY KEY (user_id);
ALTER TABLE ONLY batched_background_migration_jobs
ADD CONSTRAINT batched_background_migration_jobs_pkey PRIMARY KEY (id);
......@@ -28235,6 +28244,9 @@ ALTER TABLE ONLY merge_trains
ALTER TABLE ONLY ci_runner_namespaces
ADD CONSTRAINT fk_rails_f9d9ed3308 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY banned_users
ADD CONSTRAINT fk_rails_fa5bb598e5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY requirements_management_test_reports
ADD CONSTRAINT fk_rails_fb3308ad55 FOREIGN KEY (requirement_id) REFERENCES requirements(id) ON DELETE CASCADE;
---
redirect_to: 'index.md'
remove_date: '2021-07-22'
---
This document was moved to [another location](index.md).
<!-- This redirect file can be deleted after <2021-07-22>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
---
stage: Create
group: Ecosystem
stage: Ecosystem
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
......
---
stage: Create
group: Ecosystem
stage: Ecosystem
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
......
---
stage: Create
group: Ecosystem
stage: Ecosystem
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
......
---
stage: Create
group: Ecosystem
stage: Ecosystem
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
......
---
redirect_to: 'dora/metrics.md'
remove_date: '2021-07-25'
---
This document was moved to [another location](dora/metrics.md).
<!-- This redirect file can be deleted after <2021-07-25>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
---
stage: Create
group: Ecosystem
stage: Ecosystem
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
......
---
stage: Create
group: Ecosystem
stage: Ecosystem
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
......
---
stage: Create
group: Ecosystem
stage: Ecosystem
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
......
---
stage: Create
group: Ecosystem
stage: Ecosystem
group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
......
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.
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.
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.
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.
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.
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.
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.
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.
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