Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
611b009e92
commit
e6d048d769
|
|
@ -96,7 +96,7 @@ review-build-cng:
|
|||
variables:
|
||||
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
|
||||
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
|
||||
GITLAB_HELM_CHART_REF: "febc4ad69acb7bba0eeb4a62daa577d0b7c3ee71" # 6.9.1: https://gitlab.com/gitlab-org/charts/gitlab/-/commit/febc4ad69acb7bba0eeb4a62daa577d0b7c3ee71
|
||||
GITLAB_HELM_CHART_REF: "b0d2cc33afaa29b6e28e3b2b3591239fad8377a0" # 6.10.7: https://gitlab.com/gitlab-org/charts/gitlab/-/commit/b0d2cc33afaa29b6e28e3b2b3591239fad8377a0
|
||||
environment:
|
||||
name: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # No separator for SCHEDULE_TYPE so it's compatible as before and looks nice without it
|
||||
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
|
||||
|
|
|
|||
4
Gemfile
4
Gemfile
|
|
@ -452,10 +452,10 @@ group :test do
|
|||
gem 'rspec-benchmark', '~> 0.6.0'
|
||||
gem 'rspec-parameterized', '~> 1.0', require: false
|
||||
|
||||
gem 'capybara', '~> 3.39'
|
||||
gem 'capybara', '~> 3.39', '>= 3.39.1'
|
||||
gem 'capybara-screenshot', '~> 1.0.26'
|
||||
# 4.9.1 drops Ruby 2.7 support. We can upgrade further after we drop Ruby 2.7 support.
|
||||
gem 'selenium-webdriver', '= 4.9.0'
|
||||
gem 'selenium-webdriver', '= 4.9.1'
|
||||
|
||||
gem 'graphlyte', '~> 1.0.0'
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
{"name":"bullet","version":"7.0.2","platform":"ruby","checksum":"4b7986b366f694bb05d5c1b4ea8ba949a99224d4511bf02f0c3944112f719c81"},
|
||||
{"name":"bundler-audit","version":"0.7.0.1","platform":"ruby","checksum":"12d853cb0b92fa8868abbb539414d7a33da9e48b792e2ff28271d36c8ace8912"},
|
||||
{"name":"byebug","version":"11.1.3","platform":"ruby","checksum":"2485944d2bb21283c593d562f9ae1019bf80002143cc3a255aaffd4e9cf4a35b"},
|
||||
{"name":"capybara","version":"3.39.0","platform":"ruby","checksum":"a30994beb4b4f318e39e3dc81e73203bd1edf1f9ef237d82b708eb1c21b56419"},
|
||||
{"name":"capybara","version":"3.39.1","platform":"ruby","checksum":"25831b3860d54b88013e6e41b77412b2e6a80bcc59aa9a1d48b0f8de65210fe2"},
|
||||
{"name":"capybara-screenshot","version":"1.0.26","platform":"ruby","checksum":"816b9370a07752097c82a05f568aaf5d3b7f45c3db5d3aab2014071e1b3c0c77"},
|
||||
{"name":"carrierwave","version":"1.3.3","platform":"ruby","checksum":"0f0244de2ece54c80745b755993bd26cf47d4650823e5f89c115dbc9d73a13f1"},
|
||||
{"name":"cbor","version":"0.5.9.6","platform":"ruby","checksum":"434a147658dd1df24ec9e7b3297c1fd4f8a691c97d0e688b3049df8e728b2114"},
|
||||
|
|
@ -563,7 +563,7 @@
|
|||
{"name":"sawyer","version":"0.9.2","platform":"ruby","checksum":"fa3a72d62a4525517b18857ddb78926aab3424de0129be6772a8e2ba240e7aca"},
|
||||
{"name":"sd_notify","version":"0.1.1","platform":"ruby","checksum":"cbc7ac6caa7cedd26b30a72b5eeb6f36050dc0752df263452ea24fb5a4ad3131"},
|
||||
{"name":"seed-fu","version":"2.3.7","platform":"ruby","checksum":"f19673443e9af799b730e3d4eca6a89b39e5a36825015dffd00d02ea3365cf74"},
|
||||
{"name":"selenium-webdriver","version":"4.9.0","platform":"ruby","checksum":"0f5fc4118ab231e5ef1895b1e14a4366eb9d73d60a8e42b0d84f69cdfdd8b6cf"},
|
||||
{"name":"selenium-webdriver","version":"4.9.1","platform":"ruby","checksum":"055b8c3a528c7150d7e1f6b8551725f7643a8c00f36028a052f6ec8e50819184"},
|
||||
{"name":"semver_dialects","version":"1.2.1","platform":"ruby","checksum":"60a1f67659f79c51a667e8858ec9b089c1e4ce4f6d2a0f0b4ac101916946eb23"},
|
||||
{"name":"sentry-rails","version":"5.8.0","platform":"ruby","checksum":"c11b2d909de2c2bfda793c45f64180fd784d54c46886338b683ee3f8efa7731b"},
|
||||
{"name":"sentry-raven","version":"3.1.2","platform":"ruby","checksum":"103d3b122958810d34898ce2e705bcf549ddb9d855a70ce9a3970ee2484f364a"},
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ GEM
|
|||
bundler (>= 1.2.0, < 3)
|
||||
thor (>= 0.18, < 2)
|
||||
byebug (11.1.3)
|
||||
capybara (3.39.0)
|
||||
capybara (3.39.1)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
|
|
@ -1392,7 +1392,7 @@ GEM
|
|||
seed-fu (2.3.7)
|
||||
activerecord (>= 3.1)
|
||||
activesupport (>= 3.1)
|
||||
selenium-webdriver (4.9.0)
|
||||
selenium-webdriver (4.9.1)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
|
|
@ -1684,7 +1684,7 @@ DEPENDENCIES
|
|||
bullet (~> 7.0.2)
|
||||
bundler-audit (~> 0.7.0.1)
|
||||
bundler-checksum (~> 0.1.0)!
|
||||
capybara (~> 3.39)
|
||||
capybara (~> 3.39, >= 3.39.1)
|
||||
capybara-screenshot (~> 1.0.26)
|
||||
carrierwave (~> 1.3)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
|
|
@ -1906,7 +1906,7 @@ DEPENDENCIES
|
|||
sassc-rails (~> 2.1.0)
|
||||
sd_notify (~> 0.1.0)
|
||||
seed-fu (~> 2.3.7)
|
||||
selenium-webdriver (= 4.9.0)
|
||||
selenium-webdriver (= 4.9.1)
|
||||
semver_dialects (~> 1.2.1)
|
||||
sentry-rails (~> 5.8.0)
|
||||
sentry-raven (~> 3.1)
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ export default {
|
|||
this.adjustView();
|
||||
},
|
||||
viewDiffsFileByFile(newViewFileByFile) {
|
||||
if (!newViewFileByFile && this.diffsIncomplete && this.glFeatures.singleFileFileByFile) {
|
||||
if (!newViewFileByFile && this.diffsIncomplete) {
|
||||
this.refetchDiffData({ refetchMeta: false });
|
||||
}
|
||||
},
|
||||
|
|
@ -467,26 +467,19 @@ export default {
|
|||
subscribeToEvents() {
|
||||
notesEventHub.$once('fetchDiffData', this.fetchData);
|
||||
notesEventHub.$on('refetchDiffData', this.refetchDiffData);
|
||||
if (this.glFeatures.singleFileFileByFile) {
|
||||
diffsEventHub.$on('diffFilesModified', this.setDiscussions);
|
||||
notesEventHub.$on('fetchedNotesData', this.rereadNoteHash);
|
||||
}
|
||||
notesEventHub.$on('fetchedNotesData', this.rereadNoteHash);
|
||||
diffsEventHub.$on('diffFilesModified', this.setDiscussions);
|
||||
diffsEventHub.$on(EVT_MR_PREPARED, this.fetchData);
|
||||
},
|
||||
unsubscribeFromEvents() {
|
||||
diffsEventHub.$off(EVT_MR_PREPARED, this.fetchData);
|
||||
if (this.glFeatures.singleFileFileByFile) {
|
||||
notesEventHub.$off('fetchedNotesData', this.rereadNoteHash);
|
||||
diffsEventHub.$off('diffFilesModified', this.setDiscussions);
|
||||
}
|
||||
diffsEventHub.$off('diffFilesModified', this.setDiscussions);
|
||||
notesEventHub.$off('fetchedNotesData', this.rereadNoteHash);
|
||||
notesEventHub.$off('refetchDiffData', this.refetchDiffData);
|
||||
notesEventHub.$off('fetchDiffData', this.fetchData);
|
||||
},
|
||||
navigateToDiffFileNumber(number) {
|
||||
this.navigateToDiffFileIndex({
|
||||
index: number - 1,
|
||||
singleFile: this.glFeatures.singleFileFileByFile,
|
||||
});
|
||||
this.navigateToDiffFileIndex(number - 1);
|
||||
},
|
||||
refetchDiffData({ refetchMeta = true } = {}) {
|
||||
this.fetchData({ toggleTree: false, fetchMeta: refetchMeta });
|
||||
|
|
@ -506,7 +499,7 @@ export default {
|
|||
if (data) {
|
||||
realSize = data.real_size;
|
||||
|
||||
if (this.viewDiffsFileByFile && this.glFeatures.singleFileFileByFile) {
|
||||
if (this.viewDiffsFileByFile) {
|
||||
this.fetchFileByFile();
|
||||
}
|
||||
}
|
||||
|
|
@ -527,7 +520,7 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
if (!this.viewDiffsFileByFile || !this.glFeatures.singleFileFileByFile) {
|
||||
if (!this.viewDiffsFileByFile) {
|
||||
this.fetchDiffFilesBatch()
|
||||
.then(() => {
|
||||
if (toggleTree) this.setTreeDisplay();
|
||||
|
|
@ -618,10 +611,7 @@ export default {
|
|||
jumpToFile(step) {
|
||||
const targetIndex = this.currentDiffIndex + step;
|
||||
if (targetIndex >= 0 && targetIndex < this.flatBlobsList.length) {
|
||||
this.goToFile({
|
||||
path: this.flatBlobsList[targetIndex].path,
|
||||
singleFile: this.glFeatures.singleFileFileByFile,
|
||||
});
|
||||
this.goToFile({ path: this.flatBlobsList[targetIndex].path });
|
||||
}
|
||||
},
|
||||
setTreeDisplay() {
|
||||
|
|
|
|||
|
|
@ -209,12 +209,6 @@ export default {
|
|||
|
||||
if (this.hasDiff) {
|
||||
this.postRender();
|
||||
} else if (
|
||||
this.viewDiffsFileByFile &&
|
||||
!this.isCollapsed &&
|
||||
!this.glFeatures.singleFileFileByFile
|
||||
) {
|
||||
this.requestDiff();
|
||||
}
|
||||
|
||||
this.manageViewedEffects();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import micromatch from 'micromatch';
|
|||
import { debounce } from 'lodash';
|
||||
import { getModifierKey } from '~/constants';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { RecycleScroller } from 'vendor/vue-virtual-scroller';
|
||||
import DiffFileRow from './diff_file_row.vue';
|
||||
|
||||
|
|
@ -20,7 +19,6 @@ export default {
|
|||
DiffFileRow,
|
||||
RecycleScroller,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
hideFileStats: {
|
||||
type: Boolean,
|
||||
|
|
@ -177,7 +175,7 @@ export default {
|
|||
:class="{ 'tree-list-parent': item.level > 0 }"
|
||||
class="gl-relative"
|
||||
@toggleTreeOpen="toggleTreeOpen"
|
||||
@clickFile="(path) => goToFile({ singleFile: glFeatures.singleFileFileByFile, path })"
|
||||
@clickFile="(path) => goToFile({ path })"
|
||||
/>
|
||||
</template>
|
||||
<template #after>
|
||||
|
|
|
|||
|
|
@ -608,8 +608,8 @@ export const setCurrentFileHash = ({ commit }, hash) => {
|
|||
commit(types.SET_CURRENT_DIFF_FILE, hash);
|
||||
};
|
||||
|
||||
export const goToFile = ({ state, commit, dispatch, getters }, { path, singleFile }) => {
|
||||
if (!state.viewDiffsFileByFile || !singleFile) {
|
||||
export const goToFile = ({ state, commit, dispatch, getters }, { path }) => {
|
||||
if (!state.viewDiffsFileByFile) {
|
||||
dispatch('scrollToFile', { path });
|
||||
} else {
|
||||
if (!state.treeEntries[path]) return;
|
||||
|
|
@ -943,16 +943,13 @@ export const setCurrentDiffFileIdFromNote = ({ commit, getters, rootGetters }, n
|
|||
}
|
||||
};
|
||||
|
||||
export const navigateToDiffFileIndex = (
|
||||
{ state, getters, commit, dispatch },
|
||||
{ index, singleFile },
|
||||
) => {
|
||||
export const navigateToDiffFileIndex = ({ state, getters, commit, dispatch }, index) => {
|
||||
const { fileHash } = getters.flatBlobsList[index];
|
||||
document.location.hash = fileHash;
|
||||
|
||||
commit(types.SET_CURRENT_DIFF_FILE, fileHash);
|
||||
|
||||
if (state.viewDiffsFileByFile && singleFile) {
|
||||
if (state.viewDiffsFileByFile) {
|
||||
dispatch('fetchFileByFile');
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const TYPE_EPIC = 'epic';
|
|||
export const TYPE_INCIDENT = 'incident';
|
||||
export const TYPE_ISSUE = 'issue';
|
||||
export const TYPE_MERGE_REQUEST = 'merge_request';
|
||||
export const TYPE_MILESTONE = 'milestone';
|
||||
export const TYPE_TEST_CASE = 'test_case';
|
||||
|
||||
export const WORKSPACE_GROUP = 'group';
|
||||
|
|
|
|||
|
|
@ -9,12 +9,18 @@ import Sidebar from '~/right_sidebar';
|
|||
import MountMilestoneSidebar from '~/sidebar/mount_milestone_sidebar';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import ZenMode from '~/zen_mode';
|
||||
import TaskList from '~/task_list';
|
||||
import { TYPE_MILESTONE } from '~/issues/constants';
|
||||
import { createAlert } from '~/alert';
|
||||
import { __ } from '~/locale';
|
||||
import DeleteMilestoneModal from './components/delete_milestone_modal.vue';
|
||||
import PromoteMilestoneModal from './components/promote_milestone_modal.vue';
|
||||
import eventHub from './event_hub';
|
||||
|
||||
// See app/views/shared/milestones/_description.html.haml
|
||||
export const MILESTONE_DESCRIPTION_ELEMENT = '.milestone-detail .description';
|
||||
export const MILESTONE_DESCRIPTION_TASK_LIST_CONTAINER_ELEMENT = `${MILESTONE_DESCRIPTION_ELEMENT}.js-task-list-container`;
|
||||
export const MILESTONE_DETAIL_ELEMENT = '.milestone-detail';
|
||||
|
||||
export function initForm(initGFM = true) {
|
||||
new ZenMode(); // eslint-disable-line no-new
|
||||
|
|
@ -40,6 +46,26 @@ export function initShow() {
|
|||
new MountMilestoneSidebar(); // eslint-disable-line no-new
|
||||
|
||||
renderGFM(document.querySelector(MILESTONE_DESCRIPTION_ELEMENT));
|
||||
|
||||
const el = document.querySelector(MILESTONE_DESCRIPTION_TASK_LIST_CONTAINER_ELEMENT);
|
||||
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TaskList({
|
||||
dataType: TYPE_MILESTONE,
|
||||
fieldName: 'description',
|
||||
selector: MILESTONE_DETAIL_ELEMENT,
|
||||
lockVersion: el.dataset.lockVersion,
|
||||
onError: () => {
|
||||
createAlert({
|
||||
message: __(
|
||||
'Someone edited this milestone at the same time you did. Please refresh the page to see changes.',
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function initPromoteMilestoneModal() {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,12 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
|
||||
import { GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
|
||||
import { formatTimezone } from '~/lib/utils/datetime_utility';
|
||||
|
||||
export default {
|
||||
name: 'TimezoneDropdown',
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlSearchBoxByType,
|
||||
},
|
||||
directives: {
|
||||
autofocusonshow,
|
||||
GlCollapsibleListbox,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
|
|
@ -52,11 +46,10 @@ export default {
|
|||
identifier: timezone.identifier,
|
||||
}));
|
||||
},
|
||||
filteredResults() {
|
||||
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
|
||||
return this.timezones.filter((timezone) =>
|
||||
timezone.formattedTimezone.toLowerCase().includes(lowerCasedSearchTerm),
|
||||
);
|
||||
filteredListboxItems() {
|
||||
return this.timezones
|
||||
.filter((timezone) => timezone.formattedTimezone.toLowerCase().includes(this.searchTerm))
|
||||
.map(({ formattedTimezone }) => ({ value: formattedTimezone, text: formattedTimezone }));
|
||||
},
|
||||
selectedTimezoneLabel() {
|
||||
return this.tzValue || __('Select timezone');
|
||||
|
|
@ -68,14 +61,14 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
selectTimezone(selectedTimezone) {
|
||||
this.tzValue = selectedTimezone.formattedTimezone;
|
||||
selectTimezone(formattedTimezone) {
|
||||
const selectedTimezone = this.timezones.find(
|
||||
(timezone) => timezone.formattedTimezone === formattedTimezone,
|
||||
);
|
||||
this.tzValue = formattedTimezone;
|
||||
this.$emit('input', selectedTimezone);
|
||||
this.searchTerm = '';
|
||||
},
|
||||
isSelected(timezone) {
|
||||
return this.tzValue === timezone.formattedTimezone;
|
||||
},
|
||||
initialTimezone(timezones, value) {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
|
|
@ -89,6 +82,9 @@ export default {
|
|||
|
||||
return undefined;
|
||||
},
|
||||
setSearchTerm(value) {
|
||||
this.searchTerm = value?.toLowerCase();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -101,31 +97,16 @@ export default {
|
|||
:value="timezoneIdentifier || value"
|
||||
type="hidden"
|
||||
/>
|
||||
<gl-dropdown
|
||||
:text="selectedTimezoneLabel"
|
||||
:class="additionalClass"
|
||||
<gl-collapsible-listbox
|
||||
:items="filteredListboxItems"
|
||||
:toggle-text="selectedTimezoneLabel"
|
||||
:toggle-class="additionalClass"
|
||||
:no-results-text="$options.translations.noResultsText"
|
||||
:selected="tzValue"
|
||||
block
|
||||
lazy
|
||||
menu-class="gl-w-full!"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<gl-search-box-by-type v-model.trim="searchTerm" v-autofocusonshow autofocus />
|
||||
<gl-dropdown-item
|
||||
v-for="timezone in filteredResults"
|
||||
:key="timezone.formattedTimezone"
|
||||
:is-checked="isSelected(timezone)"
|
||||
is-check-item
|
||||
@click="selectTimezone(timezone)"
|
||||
>
|
||||
{{ timezone.formattedTimezone }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-item
|
||||
v-if="!filteredResults.length"
|
||||
class="gl-pointer-events-none"
|
||||
data-testid="noMatchingResults"
|
||||
>
|
||||
{{ $options.translations.noResultsText }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
searchable
|
||||
@search="setSearchTerm"
|
||||
@select="selectTimezone"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,19 @@ class Groups::MilestonesController < Groups::ApplicationController
|
|||
def update
|
||||
Milestones::UpdateService.new(@milestone.parent, current_user, milestone_params).execute(@milestone)
|
||||
|
||||
redirect_to milestone_path(@milestone)
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
redirect_to milestone_path(@milestone)
|
||||
end
|
||||
|
||||
format.json do
|
||||
if @milestone.valid?
|
||||
head :no_content
|
||||
else
|
||||
render json: { errors: @milestone.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:refactor_security_extension, @project)
|
||||
push_frontend_feature_flag(:deprecate_vulnerabilities_feedback, @project)
|
||||
push_frontend_feature_flag(:moved_mr_sidebar, project)
|
||||
push_frontend_feature_flag(:single_file_file_by_file, project)
|
||||
push_frontend_feature_flag(:mr_experience_survey, project)
|
||||
push_frontend_feature_flag(:realtime_mr_status_change, project)
|
||||
push_frontend_feature_flag(:realtime_approvals, project)
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
@milestone = Milestones::UpdateService.new(project, current_user, milestone_params).execute(milestone)
|
||||
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.html do
|
||||
if @milestone.valid?
|
||||
redirect_to project_milestone_path(@project, @milestone)
|
||||
|
|
@ -84,6 +83,16 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
format.js
|
||||
|
||||
format.json do
|
||||
if @milestone.valid?
|
||||
head :no_content
|
||||
else
|
||||
render json: { errors: @milestone.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
respond_to do |format|
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Finder for retrieving organizations scoped to a group
|
||||
# Finder for retrieving crm_organizations scoped to a group
|
||||
#
|
||||
# Arguments:
|
||||
# current_user - user performing the action. Must have the correct permission level for the group.
|
||||
|
|
@ -29,22 +29,22 @@ module Crm
|
|||
def execute
|
||||
return CustomerRelations::Organization.none unless root_group
|
||||
|
||||
organizations = root_group.organizations
|
||||
organizations = by_ids(organizations)
|
||||
organizations = by_search(organizations)
|
||||
organizations = by_state(organizations)
|
||||
sort_organizations(organizations)
|
||||
crm_organizations = root_group.crm_organizations
|
||||
crm_organizations = by_ids(crm_organizations)
|
||||
crm_organizations = by_search(crm_organizations)
|
||||
crm_organizations = by_state(crm_organizations)
|
||||
sort_crm_organizations(crm_organizations)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sort_organizations(organizations)
|
||||
return organizations.sort_by_name unless @params.key?(:sort)
|
||||
return organizations if @params[:sort].nil?
|
||||
def sort_crm_organizations(crm_organizations)
|
||||
return crm_organizations.sort_by_name unless @params.key?(:sort)
|
||||
return crm_organizations if @params[:sort].nil?
|
||||
|
||||
field = @params[:sort][:field]
|
||||
direction = @params[:sort][:direction]
|
||||
organizations.sort_by_field(field, direction)
|
||||
crm_organizations.sort_by_field(field, direction)
|
||||
end
|
||||
|
||||
def root_group
|
||||
|
|
@ -57,22 +57,22 @@ module Crm
|
|||
end
|
||||
end
|
||||
|
||||
def by_search(organizations)
|
||||
return organizations unless search?
|
||||
def by_search(crm_organizations)
|
||||
return crm_organizations unless search?
|
||||
|
||||
organizations.search(params[:search])
|
||||
crm_organizations.search(params[:search])
|
||||
end
|
||||
|
||||
def by_state(organizations)
|
||||
return organizations unless state?
|
||||
def by_state(crm_organizations)
|
||||
return crm_organizations unless state?
|
||||
|
||||
organizations.search_by_state(params[:state])
|
||||
crm_organizations.search_by_state(params[:state])
|
||||
end
|
||||
|
||||
def by_ids(organizations)
|
||||
return organizations unless ids?
|
||||
def by_ids(crm_organizations)
|
||||
return crm_organizations unless ids?
|
||||
|
||||
organizations.id_in(params[:ids])
|
||||
crm_organizations.id_in(params[:ids])
|
||||
end
|
||||
|
||||
def search?
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SafeFormatHelper
|
||||
# Returns a HTML-safe string where +format+ and +args+ are escaped via
|
||||
# `html_escape` if they are not marked as HTML-safe.
|
||||
#
|
||||
# Argument +format+ must not be marked as HTML-safe via `.html_safe`.
|
||||
# Returns a HTML-safe string where
|
||||
# * +format+ is escaped via `html_escape_once`
|
||||
# * +args+ are escaped via `html_escape` if they are not marked as HTML-safe
|
||||
#
|
||||
# Example:
|
||||
# safe_format('Some %{open}bold%{close} text.', open: '<strong>'.html_safe, close: '</strong>'.html_safe)
|
||||
# # => 'Some <strong>bold</strong>'
|
||||
# safe_format('See %{user_input}', user_input: '<b>bold</b>')
|
||||
# # => 'See <b>bold</b>
|
||||
# safe_format('In < hour & more')
|
||||
# # => 'In < hour & more'
|
||||
#
|
||||
def safe_format(format, **args)
|
||||
raise ArgumentError, 'Argument `format` must not be marked as html_safe!' if format.html_safe?
|
||||
|
||||
# Use `Kernel.format` to avoid conflicts with ViewComponent's `format`.
|
||||
Kernel.format(
|
||||
html_escape(format),
|
||||
html_escape_once(format),
|
||||
args.transform_values { |value| html_escape(value) }
|
||||
).html_safe
|
||||
end
|
||||
|
|
|
|||
|
|
@ -176,6 +176,11 @@ module Emails
|
|||
.gsub(/%\{\s*SYSTEM_FOOTER\s*\}/, text_footer_message.to_s)
|
||||
.gsub(/%\{\s*UNSUBSCRIBE_URL\s*\}/, unsubscribe_sent_notification_url(@sent_notification))
|
||||
.gsub(/%\{\s*ADDITIONAL_TEXT\s*\}/, service_desk_email_additional_text.to_s)
|
||||
.gsub(/%\{\s*ISSUE_URL\s*\}/, full_issue_url)
|
||||
end
|
||||
|
||||
def full_issue_url
|
||||
issue_url(@issue)
|
||||
end
|
||||
|
||||
def issue_id
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class Group < Namespace
|
|||
has_many :badges, class_name: 'GroupBadge'
|
||||
|
||||
# AR defaults to nullify when trying to delete via has_many associations unless we set dependent: :delete_all
|
||||
has_many :organizations, class_name: 'CustomerRelations::Organization', inverse_of: :group, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :crm_organizations, class_name: 'CustomerRelations::Organization', inverse_of: :group, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :contacts, class_name: 'CustomerRelations::Contact', inverse_of: :group, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :cluster_groups, class_name: 'Clusters::Group'
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ module Groups
|
|||
return false if group.root_ancestor == @new_parent_group.root_ancestor
|
||||
|
||||
return true if group.contacts.exists? && !current_user.can?(:admin_crm_contact, @new_parent_group.root_ancestor)
|
||||
return true if group.organizations.exists? && !current_user.can?(:admin_crm_organization, @new_parent_group.root_ancestor)
|
||||
return true if group.crm_organizations.exists? && !current_user.can?(:admin_crm_organization, @new_parent_group.root_ancestor)
|
||||
|
||||
false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
- add_page_specific_style 'page_bundles/ci_status'
|
||||
|
||||
- add_page_startup_api_call @endpoint_metadata_url
|
||||
- if mr_action == 'diffs' && (!@file_by_file_default || !single_file_file_by_file?)
|
||||
- if mr_action == 'diffs' && !@file_by_file_default
|
||||
- add_page_startup_api_call @endpoint_diff_batch_url
|
||||
|
||||
.merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version, diffs_batch_cache_key: @diffs_batch_cache_key } }
|
||||
|
|
|
|||
|
|
@ -9,5 +9,7 @@
|
|||
|
||||
- if milestone.try(:description).present?
|
||||
%div{ data: { qa_selector: "milestone_description_content" } }
|
||||
.description.md.gl-px-0.gl-pt-4
|
||||
.description.md.gl-px-0.gl-pt-4{ class: ('js-task-list-container' if can?(current_user, :admin_milestone, milestone)), data: { lock_version: @milestone.lock_version } }
|
||||
= markdown_field(milestone, :description)
|
||||
-# This textarea is necessary for `task_list.js` to work.
|
||||
%textarea.hidden.js-task-list-field{ data: { value: milestone.description, update_url: milestone_path(milestone, format: :json)} }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: bitbucket_server_user_mapping_by_username
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36885
|
||||
rollout_issue_url:
|
||||
rollout_issue_url: # No rollout: This is an ops-flag
|
||||
milestone: '13.4'
|
||||
type: development
|
||||
type: ops
|
||||
group: group::import
|
||||
default_enabled: false
|
||||
default_enabled: false # Flag should be kept disabled by default
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: code_suggestions_tokens_api
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120892
|
||||
rollout_issue_url:
|
||||
milestone: '16.1'
|
||||
type: ops
|
||||
group: group::ai assisted
|
||||
default_enabled: true
|
||||
|
|
@ -6,6 +6,8 @@
|
|||
stage: Enablement
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/387898
|
||||
body: |
|
||||
This deprecation is now superseded by another [deprecation notice](#running-a-single-database-is-deprecated).
|
||||
|
||||
Previously, [GitLab's database](https://docs.gitlab.com/omnibus/settings/database.html)
|
||||
configuration had a single `main:` section. This is being deprecated. The new
|
||||
configuration has both a `main:` and a `ci:` section.
|
||||
|
|
@ -14,6 +16,4 @@
|
|||
to [add the `ci:` section](https://docs.gitlab.com/ee/install/installation.html#configure-gitlab-db-settings).
|
||||
Omnibus, the Helm chart, and Operator will handle this configuration
|
||||
automatically from GitLab 16.0 onwards.
|
||||
|
||||
This change is a preparation to deprecate two connections in favor of two databases in 16.1.
|
||||
documentation_url: https://docs.gitlab.com/ee/install/installation.html#configure-gitlab-db-settings
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
- title: "Running a single database is deprecated"
|
||||
removal_milestone: "17.0"
|
||||
announcement_milestone: "16.1"
|
||||
breaking_change: true
|
||||
reporter: lohrc
|
||||
stage: data_stores
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/411239
|
||||
body: |
|
||||
The option to run self-managed installations of GitLab on a single database is now deprecated. From GitLab 17.0, we will require a [separate database for CI features](https://gitlab.com/groups/gitlab-org/-/epics/7509). With this change, self-managed versions of GitLab will behave similarly to GitLab.com. This change applies to installation methods with Omnibus GitLab, GitLab Helm chart, GitLab Operator, GitLab Docker images, and installation from source. Before upgrading to GitLab 17.0, please ensure [migration](https://docs.gitlab.com/ee/administration/postgresql/multiple_databases.html) to two databases.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
migration_job_name: RemoveInvalidDeployAccessLevelGroups
|
||||
description: This deletes protected_environment_deploy_access_levels rows that have invalid group_id.
|
||||
feature_category: continuous_delivery
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121222
|
||||
milestone: 16.1
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ScheduleToRemoveInvalidDeployAccessLevelGroups < Gitlab::Database::Migration[2.1]
|
||||
MIGRATION = "RemoveInvalidDeployAccessLevelGroups"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:protected_environment_deploy_access_levels,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :protected_environment_deploy_access_levels, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
c2340753bf27ef119dd76a49ada76f07ef6f22577ae11651e81bba6bd7502f08
|
||||
|
|
@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
The Geo team performs manual testing and validation on common deployment configurations to ensure
|
||||
that Geo works when upgrading between minor GitLab versions and major PostgreSQL database versions.
|
||||
|
||||
This section contains a journal of recent validation tests and links to the relevant issues.
|
||||
This section contains a journal of validation tests and links to the relevant issues.
|
||||
|
||||
## GitLab upgrades
|
||||
|
||||
|
|
@ -184,24 +184,6 @@ The following are PostgreSQL upgrade validation tests we performed.
|
|||
|
||||
The following are additional validation tests we performed.
|
||||
|
||||
### May 2021
|
||||
|
||||
[Test failover with object storage replication enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/330362):
|
||||
|
||||
- Description: At the time of testing, Geo's object storage replication functionality was in beta. We tested that object storage replication works as intended and that the data was present on the new primary after a failover.
|
||||
- Outcome: The test was successful. Data in object storage was replicated and present after a failover.
|
||||
- Follow up issues:
|
||||
- [Geo: Failing to replicate initial Monitoring project](https://gitlab.com/gitlab-org/gitlab/-/issues/330485)
|
||||
|
||||
### January 2022
|
||||
|
||||
[Validate Object storage replication using Azure based object storage](https://gitlab.com/gitlab-org/gitlab/-/issues/348804#note_821294631):
|
||||
|
||||
- Description: Tested the average time it takes for a single image to replicate from the primary object storage location to the secondary when using Azure based object storage replication and [GitLab based object storage replication](object_storage.md#enabling-gitlab-managed-object-storage-replication). This was tested by uploading a 1 MB image to a project on the primary site every second for 60 seconds. The time was then measured until a image was available on the secondary site. This was achieved using a [Ruby Script](https://gitlab.com/gitlab-org/quality/geo-replication-tester).
|
||||
- Outcome: When using Azure based replication the average time for an image to replicate from the primary object storage to the secondary was recorded as 40 seconds, the longest replication time was 70 seconds and the quickest was 11 seconds. When using GitLab based replication the average time for replication to complete was 5 seconds, the longest replication time was 10 seconds and the quickest was 3 seconds.
|
||||
- Follow up issue:
|
||||
- [Validate Cross Region Object storage replication using Azure based object storage](https://gitlab.com/gitlab-org/gitlab/-/issues/358154)
|
||||
|
||||
### April 2022
|
||||
|
||||
[Validate Object storage replication using AWS based object storage](https://gitlab.com/gitlab-org/gitlab/-/issues/351463):
|
||||
|
|
@ -214,6 +196,24 @@ The following are additional validation tests we performed.
|
|||
- Description: Tested the average time it takes for a single image to replicate from the primary object storage location to the secondary when using GCP based object storage replication and [GitLab based object storage replication](object_storage.md#enabling-gitlab-managed-object-storage-replication). This was tested by uploading a 1 MB image to a project on the primary site every second for 60 seconds. The time was then measured until a image was available on the secondary site. This was achieved using a [Ruby Script](https://gitlab.com/gitlab-org/quality/geo-replication-tester).
|
||||
- Outcome: GCP handles replication differently than other Cloud Providers. In GCP, the process is to a create single bucket that is either multi, dual, or single region based. This means that the bucket automatically stores replicas in a region based on the option chosen. Even when using multi region, this only replicates in a single continent, the options being America, Europe, or Asia. At current there doesn't seem to be any way to replicate objects between continents using GCP based replication. For Geo managed replication the average time when replicating in the same region was 6 seconds, and when replicating cross region this rose to just 9 seconds.
|
||||
|
||||
### January 2022
|
||||
|
||||
[Validate Object storage replication using Azure based object storage](https://gitlab.com/gitlab-org/gitlab/-/issues/348804#note_821294631):
|
||||
|
||||
- Description: Tested the average time it takes for a single image to replicate from the primary object storage location to the secondary when using Azure based object storage replication and [GitLab based object storage replication](object_storage.md#enabling-gitlab-managed-object-storage-replication). This was tested by uploading a 1 MB image to a project on the primary site every second for 60 seconds. The time was then measured until a image was available on the secondary site. This was achieved using a [Ruby Script](https://gitlab.com/gitlab-org/quality/geo-replication-tester).
|
||||
- Outcome: When using Azure based replication the average time for an image to replicate from the primary object storage to the secondary was recorded as 40 seconds, the longest replication time was 70 seconds and the quickest was 11 seconds. When using GitLab based replication the average time for replication to complete was 5 seconds, the longest replication time was 10 seconds and the quickest was 3 seconds.
|
||||
- Follow up issue:
|
||||
- [Validate Cross Region Object storage replication using Azure based object storage](https://gitlab.com/gitlab-org/gitlab/-/issues/358154)
|
||||
|
||||
### May 2021
|
||||
|
||||
[Test failover with object storage replication enabled](https://gitlab.com/gitlab-org/gitlab/-/issues/330362):
|
||||
|
||||
- Description: At the time of testing, Geo's object storage replication functionality was in beta. We tested that object storage replication works as intended and that the data was present on the new primary after a failover.
|
||||
- Outcome: The test was successful. Data in object storage was replicated and present after a failover.
|
||||
- Follow up issues:
|
||||
- [Geo: Failing to replicate initial Monitoring project](https://gitlab.com/gitlab-org/gitlab/-/issues/330485)
|
||||
|
||||
## Other tests
|
||||
|
||||
### August 2020
|
||||
|
|
|
|||
|
|
@ -589,7 +589,7 @@ instead:
|
|||
- In Ruby/HAML:
|
||||
|
||||
```ruby
|
||||
html_escape_once(_('In < 1 hour')).html_safe
|
||||
safe_format(_('In < 1 hour'))
|
||||
|
||||
# => 'In < 1 hour'
|
||||
```
|
||||
|
|
|
|||
|
|
@ -479,6 +479,20 @@ that is available now. We recommend this alternative solution because it provide
|
|||
|
||||
<div class="deprecation breaking-change" data-milestone="17.0">
|
||||
|
||||
### Running a single database is deprecated
|
||||
|
||||
<div class="deprecation-notes">
|
||||
- Announced in: GitLab <span class="milestone">16.1</span>
|
||||
- This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/411239).
|
||||
</div>
|
||||
|
||||
The option to run self-managed installations of GitLab on a single database is now deprecated. From GitLab 17.0, we will require a [separate database for CI features](https://gitlab.com/groups/gitlab-org/-/epics/7509). With this change, self-managed versions of GitLab will behave similarly to GitLab.com. This change applies to installation methods with Omnibus GitLab, GitLab Helm chart, GitLab Operator, GitLab Docker images, and installation from source. Before upgrading to GitLab 17.0, please ensure [migration](https://docs.gitlab.com/ee/administration/postgresql/multiple_databases.html) to two databases.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change" data-milestone="17.0">
|
||||
|
||||
### Self-managed certificate-based integration with Kubernetes
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
|
@ -513,6 +527,8 @@ For updates and details about this deprecation, follow [this epic](https://gitla
|
|||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/387898).
|
||||
</div>
|
||||
|
||||
This deprecation is now superseded by another [deprecation notice](#running-a-single-database-is-deprecated).
|
||||
|
||||
Previously, [GitLab's database](https://docs.gitlab.com/omnibus/settings/database.html)
|
||||
configuration had a single `main:` section. This is being deprecated. The new
|
||||
configuration has both a `main:` and a `ci:` section.
|
||||
|
|
@ -522,8 +538,6 @@ to [add the `ci:` section](https://docs.gitlab.com/ee/install/installation.html#
|
|||
Omnibus, the Helm chart, and Operator will handle this configuration
|
||||
automatically from GitLab 16.0 onwards.
|
||||
|
||||
This change is a preparation to deprecate two connections in favor of two databases in 16.1.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change" data-milestone="17.0">
|
||||
|
|
|
|||
|
|
@ -101,7 +101,8 @@ visible in the email template. For more information, see
|
|||
|
||||
#### Thank you email
|
||||
|
||||
> `%{ISSUE_DESCRIPTION}` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223751) in GitLab 16.0.
|
||||
> - `%{ISSUE_DESCRIPTION}` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223751) in GitLab 16.0.
|
||||
> - `%{ISSUE_URL}` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/408793) in GitLab 16.1.
|
||||
|
||||
When a user submits an issue through Service Desk, GitLab sends a **thank you email**.
|
||||
|
||||
|
|
@ -110,20 +111,23 @@ directory in your repository, create a file named `thank_you.md`.
|
|||
|
||||
You can use these placeholders to be automatically replaced in each email:
|
||||
|
||||
- `%{ISSUE_ID}`: issue IID
|
||||
- `%{ISSUE_PATH}`: project path appended with the issue IID
|
||||
- `%{ISSUE_DESCRIPTION}`: issue description based on the original email
|
||||
- `%{UNSUBSCRIBE_URL}`: unsubscribe URL
|
||||
- `%{SYSTEM_HEADER}`: [system header message](../admin_area/appearance.md#system-header-and-footer-messages)
|
||||
- `%{SYSTEM_FOOTER}`: [system footer message](../admin_area/appearance.md#system-header-and-footer-messages)
|
||||
- `%{ADDITIONAL_TEXT}`: [custom additional text](../admin_area/settings/email.md#custom-additional-text)
|
||||
- `%{ISSUE_ID}`: Issue IID.
|
||||
- `%{ISSUE_PATH}`: Project path appended with the issue IID.
|
||||
- `%{ISSUE_URL}`: URL to the issue. External participants can only view the issue if the project is public
|
||||
and issue is not confidential (Service Desk issues are confidential by default).
|
||||
- `%{ISSUE_DESCRIPTION}`: Issue description based on the original email.
|
||||
- `%{UNSUBSCRIBE_URL}`: Unsubscribe URL.
|
||||
- `%{SYSTEM_HEADER}`: [System header message](../admin_area/appearance.md#system-header-and-footer-messages).
|
||||
- `%{SYSTEM_FOOTER}`: [System footer message](../admin_area/appearance.md#system-header-and-footer-messages).
|
||||
- `%{ADDITIONAL_TEXT}`: [Custom additional text](../admin_area/settings/email.md#custom-additional-text).
|
||||
|
||||
Because Service Desk issues are created as [confidential](issues/confidential_issues.md) (only project members can see them),
|
||||
the response email does not contain the issue link.
|
||||
|
||||
#### New note email
|
||||
|
||||
> `%{ISSUE_DESCRIPTION}` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223751) in GitLab 16.0.
|
||||
> - `%{ISSUE_DESCRIPTION}` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223751) in GitLab 16.0.
|
||||
> - `%{ISSUE_URL}` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/408793) in GitLab 16.1.
|
||||
|
||||
When a user-submitted issue receives a new comment, GitLab sends a **new note email**.
|
||||
|
||||
|
|
@ -132,17 +136,19 @@ directory in your repository, create a file named `new_note.md`.
|
|||
|
||||
You can use these placeholders to be automatically replaced in each email:
|
||||
|
||||
- `%{ISSUE_ID}`: issue IID
|
||||
- `%{ISSUE_PATH}`: project path appended with the issue IID
|
||||
- `%{ISSUE_DESCRIPTION}`: issue description at the time email is generated.
|
||||
- `%{ISSUE_ID}`: Issue IID.
|
||||
- `%{ISSUE_PATH}`: Project path appended with the issue IID.
|
||||
- `%{ISSUE_URL}`: URL to the issue. External participants can only view the issue if the project is public
|
||||
and issue is not confidential (Service Desk issues are confidential by default).
|
||||
- `%{ISSUE_DESCRIPTION}`: Issue description at the time email is generated.
|
||||
If a user has edited the description, it might contain sensitive information that is not intended
|
||||
to be delivered to external participants. Use this placeholder only if you never modify
|
||||
descriptions or your team is aware of the template design.
|
||||
- `%{NOTE_TEXT}`: note text
|
||||
- `%{UNSUBSCRIBE_URL}`: unsubscribe URL
|
||||
- `%{SYSTEM_HEADER}`: [system header message](../admin_area/appearance.md#system-header-and-footer-messages)
|
||||
- `%{SYSTEM_FOOTER}`: [system footer message](../admin_area/appearance.md#system-header-and-footer-messages)
|
||||
- `%{ADDITIONAL_TEXT}`: [custom additional text](../admin_area/settings/email.md#custom-additional-text)
|
||||
- `%{NOTE_TEXT}`: Note text.
|
||||
- `%{UNSUBSCRIBE_URL}`: Unsubscribe URL.
|
||||
- `%{SYSTEM_HEADER}`: [System header message](../admin_area/appearance.md#system-header-and-footer-messages).
|
||||
- `%{SYSTEM_FOOTER}`: [System footer message](../admin_area/appearance.md#system-header-and-footer-messages).
|
||||
- `%{ADDITIONAL_TEXT}`: [Custom additional text](../admin_area/settings/email.md#custom-additional-text).
|
||||
|
||||
### Use a custom template for Service Desk issues
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ module Gitlab
|
|||
# End date of the range
|
||||
#
|
||||
# **period**
|
||||
# Specifies the period in wich the dates should be generated. Options:
|
||||
# Specifies the period in which the dates should be generated. Options:
|
||||
#
|
||||
# - :day, generate date-value pair for each day in the given period
|
||||
# - :week, generate date-value pair for each week (beginning of the week date)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# This class removes invalid `protected_environment_deploy_access_levels.group_id` records.
|
||||
class RemoveInvalidDeployAccessLevelGroups < BatchedMigrationJob
|
||||
operation_name :remove_invalid_deploy_access_level_groups
|
||||
feature_category :database
|
||||
|
||||
scope_to ->(relation) do
|
||||
relation.joins('INNER JOIN namespaces ON namespaces.id = protected_environment_deploy_access_levels.group_id')
|
||||
.where.not(protected_environment_deploy_access_levels: { group_id: nil })
|
||||
.where("namespaces.type = 'User'")
|
||||
end
|
||||
|
||||
def perform
|
||||
each_sub_batch(&:delete_all)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -442,10 +442,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def uid(rep_object)
|
||||
# We want this explicit to only be username on the FF
|
||||
# Otherwise, match email.
|
||||
# There should be no default fall-through on username. Fall-through to import user
|
||||
if Feature.enabled?(:bitbucket_server_user_mapping_by_username)
|
||||
# We want this to only match either username or email depending on the flag state.
|
||||
# There should be no fall-through.
|
||||
if Feature.enabled?(:bitbucket_server_user_mapping_by_username, type: :ops)
|
||||
find_user_id(by: :username, value: rep_object.author_username)
|
||||
else
|
||||
find_user_id(by: :email, value: rep_object.author_email)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ module Gitlab
|
|||
def uid(object)
|
||||
# We want this to only match either username or email depending on the flag state.
|
||||
# There should be no fall-through.
|
||||
if Feature.enabled?(:bitbucket_server_user_mapping_by_username)
|
||||
if Feature.enabled?(:bitbucket_server_user_mapping_by_username, type: :ops)
|
||||
find_user_id(by: :username, value: object.is_a?(Hash) ? object[:author_username] : object.author_username)
|
||||
else
|
||||
find_user_id(by: :email, value: object.is_a?(Hash) ? object[:author_email] : object.author_email)
|
||||
|
|
|
|||
|
|
@ -75,8 +75,7 @@ module Gitlab
|
|||
# We do not want to risk cycles of feature code calling redis calling feature code.
|
||||
# Also, we only want to benchmark redis-cache, hence repository-cache and rate-limiting are excluded.
|
||||
!is_a?(Gitlab::Redis::FeatureFlag::FeatureFlagStore) &&
|
||||
!is_a?(Gitlab::Redis::RepositoryCache::RepositoryCacheStore) &&
|
||||
!is_a?(Gitlab::Redis::RateLimiting::RateLimitingStore)
|
||||
!is_a?(Gitlab::Redis::RepositoryCache::RepositoryCacheStore)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,18 +3,11 @@
|
|||
module Gitlab
|
||||
module Redis
|
||||
class RateLimiting < ::Gitlab::Redis::Wrapper
|
||||
# We create a subclass only for the purpose of differentiating between different stores in cache metrics
|
||||
RateLimitingStore = Class.new(ActiveSupport::Cache::RedisCacheStore)
|
||||
|
||||
class << self
|
||||
# The data we store on RateLimiting used to be stored on Cache.
|
||||
def config_fallback
|
||||
Cache
|
||||
end
|
||||
|
||||
def cache_store
|
||||
@cache_store ||= RateLimitingStore.new(redis: pool, namespace: Cache::CACHE_NAMESPACE)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42725,6 +42725,9 @@ msgstr ""
|
|||
msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
|
||||
msgstr ""
|
||||
|
||||
msgid "Someone edited this milestone at the same time you did. Please refresh the page to see changes."
|
||||
msgstr ""
|
||||
|
||||
msgid "Someone edited this test case at the same time you did. The description has been updated and you will need to make your changes again."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@
|
|||
"devDependencies": {
|
||||
"@gitlab/eslint-plugin": "19.0.0",
|
||||
"@gitlab/stylelint-config": "4.1.0",
|
||||
"@graphql-eslint/eslint-plugin": "3.18.0",
|
||||
"@graphql-eslint/eslint-plugin": "3.19.0",
|
||||
"@testing-library/dom": "^7.16.2",
|
||||
"@types/jest": "^28.1.3",
|
||||
"@vue/compat": "^3.2.47",
|
||||
|
|
@ -245,7 +245,7 @@
|
|||
"cheerio": "^1.0.0-rc.9",
|
||||
"commander": "^2.20.3",
|
||||
"custom-jquery-matchers": "^2.1.0",
|
||||
"eslint": "8.40.0",
|
||||
"eslint": "8.41.0",
|
||||
"eslint-import-resolver-jest": "3.0.2",
|
||||
"eslint-import-resolver-webpack": "0.13.2",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Groups::MilestonesController do
|
||||
RSpec.describe Groups::MilestonesController, feature_category: :team_planning do
|
||||
let(:group) { create(:group, :public) }
|
||||
let!(:project) { create(:project, :public, group: group) }
|
||||
let!(:project2) { create(:project, group: group) }
|
||||
|
|
@ -275,6 +275,57 @@ RSpec.describe Groups::MilestonesController do
|
|||
expect(response).not_to redirect_to(group_milestone_path(group, milestone.iid))
|
||||
expect(response).to render_template(:edit)
|
||||
end
|
||||
|
||||
context 'with format :json' do
|
||||
subject do
|
||||
patch :update,
|
||||
params: {
|
||||
id: milestone.iid,
|
||||
milestone: milestone_params,
|
||||
group_id: group.to_param,
|
||||
format: :json
|
||||
}
|
||||
end
|
||||
|
||||
it "responds :no_content (204) without content body and updates milestone sucessfully" do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
expect(response.body).to be_blank
|
||||
|
||||
milestone.reload
|
||||
|
||||
expect(milestone).to have_attributes(title: milestone_params[:title])
|
||||
end
|
||||
|
||||
it 'responds unprocessable_entity (422) with error data' do
|
||||
# Note: This assignment ensures and triggers a validation error when updating the milestone.
|
||||
# Same approach used in spec/models/milestone_spec.rb .
|
||||
milestone_params[:title] = '<img src=x onerror=prompt(1)>'
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
|
||||
expect(json_response).to include("errors" => be_an(Array))
|
||||
end
|
||||
|
||||
it "handles ActiveRecord::StaleObjectError" do
|
||||
milestone_params[:title] = "title changed"
|
||||
# Purposely reduce the `lock_version` to trigger an ActiveRecord::StaleObjectError
|
||||
milestone_params[:lock_version] = milestone.lock_version - 1
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response).to include "errors" => [
|
||||
format(
|
||||
_("Someone edited this %{model_name} at the same time you did. Please refresh your browser and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength
|
||||
model_name: _('milestone')
|
||||
)
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#destroy" do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::MilestonesController do
|
||||
RSpec.describe Projects::MilestonesController, feature_category: :team_planning do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
|
|
@ -161,20 +161,92 @@ RSpec.describe Projects::MilestonesController do
|
|||
{ title: "title changed" }
|
||||
end
|
||||
|
||||
subject do
|
||||
patch :update,
|
||||
params: {
|
||||
id: milestone.iid,
|
||||
milestone: milestone_params,
|
||||
namespace_id: project.namespace.id,
|
||||
project_id: project.id
|
||||
}
|
||||
end
|
||||
|
||||
# TODO: We should also add more tests for update
|
||||
it "redirects project milestone show path" do
|
||||
subject
|
||||
|
||||
expect(response).to redirect_to project_milestone_path(project, milestone.iid)
|
||||
end
|
||||
|
||||
it "updates project milestone be_successfully" do
|
||||
subject
|
||||
|
||||
milestone.reload
|
||||
|
||||
expect(milestone.title).to eq milestone_params[:title]
|
||||
end
|
||||
|
||||
it "handles ActiveRecord::StaleObjectError" do
|
||||
# Purposely reduce the lock_version to trigger an ActiveRecord::StaleObjectError
|
||||
milestone_params[:lock_version] = milestone.lock_version - 1
|
||||
|
||||
put :update, params: {
|
||||
id: milestone.iid,
|
||||
milestone: milestone_params,
|
||||
namespace_id: project.namespace.id,
|
||||
project_id: project.id
|
||||
}
|
||||
subject
|
||||
|
||||
expect(response).not_to redirect_to(project_milestone_path(project, milestone.iid))
|
||||
expect(response).to render_template(:edit)
|
||||
end
|
||||
|
||||
context 'with format :json' do
|
||||
subject do
|
||||
patch :update,
|
||||
params: {
|
||||
id: milestone.iid,
|
||||
milestone: milestone_params,
|
||||
namespace_id: project.namespace.id,
|
||||
project_id: project.id,
|
||||
format: :json
|
||||
}
|
||||
end
|
||||
|
||||
it "responds :no_content (204) without content body and updates milestone sucessfully" do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:no_content)
|
||||
expect(response.body).to be_blank
|
||||
|
||||
milestone.reload
|
||||
|
||||
expect(milestone).to have_attributes(title: milestone_params[:title])
|
||||
end
|
||||
|
||||
it 'responds unprocessable_entity (422) with error data' do
|
||||
# Note: This assignment ensures and triggers a validation error when updating the milestone.
|
||||
# Same approach used in spec/models/milestone_spec.rb .
|
||||
milestone_params[:title] = '<img src=x onerror=prompt(1)>'
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
|
||||
expect(json_response).to include("errors" => be_an(Array))
|
||||
end
|
||||
|
||||
it "handles ActiveRecord::StaleObjectError" do
|
||||
milestone_params[:title] = "title changed"
|
||||
# Purposely reduce the `lock_version` to trigger an ActiveRecord::StaleObjectError
|
||||
milestone_params[:lock_version] = milestone.lock_version - 1
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:conflict)
|
||||
expect(json_response).to include "errors" => [
|
||||
format(
|
||||
_("Someone edited this %{model_name} at the same time you did. Please refresh your browser and make sure your changes will not unintentionally remove theirs."), # rubocop:disable Layout/LineLength
|
||||
model_name: _('milestone')
|
||||
)
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#destroy" do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Group milestone', :js, feature_category: :team_planning do
|
||||
let_it_be(:group) { create(:group, owner: user) }
|
||||
let_it_be(:user) { create(:group_member, :maintainer, user: create(:user), group: group).user }
|
||||
|
||||
let(:milestone) { create(:milestone, group: group) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'milestone with interactive markdown task list items in description' do
|
||||
let(:milestone_path) { group_milestone_path(group, milestone) }
|
||||
end
|
||||
end
|
||||
|
|
@ -529,13 +529,13 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
|
|||
end
|
||||
|
||||
it 'allows the user to select a time zone from a dropdown list of options' do
|
||||
expect(page.find('.user-time-preferences .dropdown')).not_to have_css('.show')
|
||||
expect(page).not_to have_selector('.user-time-preferences [data-testid="base-dropdown-menu"]')
|
||||
|
||||
page.find('.user-time-preferences .dropdown').click
|
||||
page.find('.user-time-preferences .gl-new-dropdown-toggle').click
|
||||
|
||||
expect(page.find('.user-time-preferences .dropdown')).to have_css('.show')
|
||||
expect(page.find('.user-time-preferences [data-testid="base-dropdown-menu"]')).to be_visible
|
||||
|
||||
page.find("button", text: "Arizona").click
|
||||
page.find("li", text: "Arizona").click
|
||||
|
||||
expect(page).to have_field(:user_timezone, with: 'America/Phoenix', type: :hidden)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Project milestone', :js, feature_category: :team_planning do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, namespace: user.namespace) }
|
||||
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'milestone with interactive markdown task list items in description' do
|
||||
let(:milestone_path) { project_milestone_path(project, milestone) }
|
||||
end
|
||||
end
|
||||
|
|
@ -413,8 +413,8 @@ RSpec.describe 'Pipeline Schedules', :js, feature_category: :projects do
|
|||
end
|
||||
|
||||
def select_timezone
|
||||
find('[data-testid="schedule-timezone"] .dropdown-toggle').click
|
||||
find("button", text: "Arizona").click
|
||||
find('[data-testid="schedule-timezone"] .gl-new-dropdown-toggle').click
|
||||
find("li", text: "Arizona").click
|
||||
end
|
||||
|
||||
def select_target_branch
|
||||
|
|
|
|||
|
|
@ -711,27 +711,19 @@ describe('diffs/components/app', () => {
|
|||
});
|
||||
|
||||
it.each`
|
||||
currentDiffFileId | targetFile | newFileByFile
|
||||
${'123'} | ${2} | ${false}
|
||||
${'312'} | ${1} | ${true}
|
||||
currentDiffFileId | targetFile
|
||||
${'123'} | ${2}
|
||||
${'312'} | ${1}
|
||||
`(
|
||||
'calls navigateToDiffFileIndex with $index when $link is clicked',
|
||||
async ({ currentDiffFileId, targetFile, newFileByFile }) => {
|
||||
createComponent(
|
||||
{ fileByFileUserPreference: true },
|
||||
({ state }) => {
|
||||
state.diffs.treeEntries = {
|
||||
123: { type: 'blob', fileHash: '123', filePaths: { old: '1234', new: '123' } },
|
||||
312: { type: 'blob', fileHash: '312', filePaths: { old: '3124', new: '312' } },
|
||||
};
|
||||
state.diffs.currentDiffFileId = currentDiffFileId;
|
||||
},
|
||||
{
|
||||
glFeatures: {
|
||||
singleFileFileByFile: newFileByFile,
|
||||
},
|
||||
},
|
||||
);
|
||||
async ({ currentDiffFileId, targetFile }) => {
|
||||
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
|
||||
state.diffs.treeEntries = {
|
||||
123: { type: 'blob', fileHash: '123', filePaths: { old: '1234', new: '123' } },
|
||||
312: { type: 'blob', fileHash: '312', filePaths: { old: '3124', new: '312' } },
|
||||
};
|
||||
state.diffs.currentDiffFileId = currentDiffFileId;
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
|
||||
|
|
@ -741,10 +733,7 @@ describe('diffs/components/app', () => {
|
|||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.navigateToDiffFileIndex).toHaveBeenCalledWith({
|
||||
index: targetFile - 1,
|
||||
singleFile: newFileByFile,
|
||||
});
|
||||
expect(wrapper.vm.navigateToDiffFileIndex).toHaveBeenCalledWith(targetFile - 1);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1117,67 +1117,50 @@ describe('DiffsStoreActions', () => {
|
|||
});
|
||||
|
||||
describe('when the app is in fileByFile mode', () => {
|
||||
describe('when the singleFileFileByFile feature flag is enabled', () => {
|
||||
it('commits SET_CURRENT_DIFF_FILE', () => {
|
||||
diffActions.goToFile(
|
||||
{ state, commit, dispatch, getters },
|
||||
{ path: file.path, singleFile: true },
|
||||
);
|
||||
it('commits SET_CURRENT_DIFF_FILE', () => {
|
||||
diffActions.goToFile({ state, commit, dispatch, getters }, file);
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, fileHash);
|
||||
expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, fileHash);
|
||||
});
|
||||
|
||||
it('does nothing more if the path has already been loaded', () => {
|
||||
getters.isTreePathLoaded = () => true;
|
||||
|
||||
diffActions.goToFile({ state, dispatch, getters, commit }, file);
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, fileHash);
|
||||
expect(dispatch).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
describe('when the tree entry has not been loaded', () => {
|
||||
it('updates location hash', () => {
|
||||
diffActions.goToFile({ state, commit, getters, dispatch }, file);
|
||||
|
||||
expect(document.location.hash).toBe('#test');
|
||||
});
|
||||
|
||||
it('does nothing more if the path has already been loaded', () => {
|
||||
getters.isTreePathLoaded = () => true;
|
||||
it('loads the file and then scrolls to it', async () => {
|
||||
diffActions.goToFile({ state, commit, getters, dispatch }, file);
|
||||
|
||||
diffActions.goToFile(
|
||||
{ state, dispatch, getters, commit },
|
||||
{ path: file.path, singleFile: true },
|
||||
);
|
||||
// Wait for the fetchFileByFile dispatch to return, to trigger scrollToFile
|
||||
await waitForPromises();
|
||||
|
||||
expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, fileHash);
|
||||
expect(dispatch).toHaveBeenCalledTimes(0);
|
||||
expect(dispatch).toHaveBeenCalledWith('fetchFileByFile');
|
||||
expect(dispatch).toHaveBeenCalledWith('scrollToFile', file);
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
describe('when the tree entry has not been loaded', () => {
|
||||
it('updates location hash', () => {
|
||||
diffActions.goToFile(
|
||||
{ state, commit, getters, dispatch },
|
||||
{ path: file.path, singleFile: true },
|
||||
);
|
||||
it('shows an alert when there was an error fetching the file', async () => {
|
||||
dispatch = jest.fn().mockRejectedValue();
|
||||
|
||||
expect(document.location.hash).toBe('#test');
|
||||
});
|
||||
diffActions.goToFile({ state, commit, getters, dispatch }, file);
|
||||
|
||||
it('loads the file and then scrolls to it', async () => {
|
||||
diffActions.goToFile(
|
||||
{ state, commit, getters, dispatch },
|
||||
{ path: file.path, singleFile: true },
|
||||
);
|
||||
// Wait for the fetchFileByFile dispatch to return, to trigger the catch
|
||||
await waitForPromises();
|
||||
|
||||
// Wait for the fetchFileByFile dispatch to return, to trigger scrollToFile
|
||||
await waitForPromises();
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith('fetchFileByFile');
|
||||
expect(dispatch).toHaveBeenCalledWith('scrollToFile', file);
|
||||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('shows an alert when there was an error fetching the file', async () => {
|
||||
dispatch = jest.fn().mockRejectedValue();
|
||||
|
||||
diffActions.goToFile(
|
||||
{ state, commit, getters, dispatch },
|
||||
{ path: file.path, singleFile: true },
|
||||
);
|
||||
|
||||
// Wait for the fetchFileByFile dispatch to return, to trigger the catch
|
||||
await waitForPromises();
|
||||
|
||||
expect(createAlert).toHaveBeenCalledTimes(1);
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: expect.stringMatching(LOAD_SINGLE_DIFF_FAILED),
|
||||
});
|
||||
expect(createAlert).toHaveBeenCalledTimes(1);
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: expect.stringMatching(LOAD_SINGLE_DIFF_FAILED),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1798,17 +1781,17 @@ describe('DiffsStoreActions', () => {
|
|||
it('commits SET_CURRENT_DIFF_FILE', () => {
|
||||
return testAction(
|
||||
diffActions.navigateToDiffFileIndex,
|
||||
{ index: 0, singleFile: false },
|
||||
0,
|
||||
{ flatBlobsList: [{ fileHash: '123' }] },
|
||||
[{ type: types.SET_CURRENT_DIFF_FILE, payload: '123' }],
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it('dispatches the fetchFileByFile action when the state value viewDiffsFileByFile is true and the single-file file-by-file feature flag is enabled', () => {
|
||||
it('dispatches the fetchFileByFile action when the state value viewDiffsFileByFile is true', () => {
|
||||
return testAction(
|
||||
diffActions.navigateToDiffFileIndex,
|
||||
{ index: 0, singleFile: true },
|
||||
0,
|
||||
{ viewDiffsFileByFile: true, flatBlobsList: [{ fileHash: '123' }] },
|
||||
[{ type: types.SET_CURRENT_DIFF_FILE, payload: '123' }],
|
||||
[{ type: 'fetchFileByFile' }],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlDropdownItem, GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
|
||||
import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
|
||||
import { nextTick } from 'vue';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue';
|
||||
|
|
@ -9,7 +9,8 @@ describe('Deploy freeze timezone dropdown', () => {
|
|||
let wrapper;
|
||||
let store;
|
||||
|
||||
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
|
||||
const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
|
||||
const findSearchBox = () => wrapper.findByTestId('listbox-search-input');
|
||||
|
||||
const createComponent = async (searchTerm, selectedTimezone) => {
|
||||
wrapper = shallowMountExtended(TimezoneDropdown, {
|
||||
|
|
@ -19,15 +20,18 @@ describe('Deploy freeze timezone dropdown', () => {
|
|||
timezoneData: timezoneDataFixture,
|
||||
name: 'user[timezone]',
|
||||
},
|
||||
stubs: {
|
||||
GlCollapsibleListbox,
|
||||
},
|
||||
});
|
||||
|
||||
findSearchBox().vm.$emit('input', searchTerm);
|
||||
await nextTick();
|
||||
};
|
||||
|
||||
const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
|
||||
const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index);
|
||||
const findEmptyResultsItem = () => wrapper.findByTestId('noMatchingResults');
|
||||
const findAllDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
|
||||
const findDropdownItemByIndex = (index) => findAllDropdownItems().at(index);
|
||||
const findEmptyResultsItem = () => wrapper.findByTestId('listbox-no-results-text');
|
||||
const findHiddenInput = () => wrapper.find('input');
|
||||
|
||||
describe('No time zones found', () => {
|
||||
|
|
@ -36,7 +40,8 @@ describe('Deploy freeze timezone dropdown', () => {
|
|||
});
|
||||
|
||||
it('renders empty results message', () => {
|
||||
expect(findDropdownItemByIndex(0).text()).toBe('No matching results');
|
||||
expect(findEmptyResultsItem().exists()).toBe(true);
|
||||
expect(findEmptyResultsItem().text()).toBe('No matching results');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -69,11 +74,13 @@ describe('Deploy freeze timezone dropdown', () => {
|
|||
const selectedTz = findTzByName('Alaska');
|
||||
|
||||
it('should emit input if a time zone is clicked', () => {
|
||||
findDropdownItemByIndex(0).vm.$emit('click');
|
||||
const payload = formatTimezone(selectedTz);
|
||||
|
||||
findDropdown().vm.$emit('select', payload);
|
||||
expect(wrapper.emitted('input')).toEqual([
|
||||
[
|
||||
{
|
||||
formattedTimezone: formatTimezone(selectedTz),
|
||||
formattedTimezone: payload,
|
||||
identifier: selectedTz.identifier,
|
||||
},
|
||||
],
|
||||
|
|
@ -88,7 +95,7 @@ describe('Deploy freeze timezone dropdown', () => {
|
|||
});
|
||||
|
||||
it('renders empty selections', () => {
|
||||
expect(wrapper.findComponent(GlDropdown).props().text).toBe('Select timezone');
|
||||
expect(findDropdown().props('toggleText')).toBe('Select timezone');
|
||||
});
|
||||
|
||||
it('preserves initial value in the associated input', () => {
|
||||
|
|
@ -102,14 +109,14 @@ describe('Deploy freeze timezone dropdown', () => {
|
|||
});
|
||||
|
||||
it('renders selected time zone as dropdown label', () => {
|
||||
expect(wrapper.findComponent(GlDropdown).props().text).toBe('[UTC+2] Berlin');
|
||||
expect(findDropdown().props('toggleText')).toBe('[UTC+2] Berlin');
|
||||
});
|
||||
|
||||
it('adds a checkmark to the selected option', async () => {
|
||||
const selectedTZOption = findAllDropdownItems().at(0);
|
||||
selectedTZOption.vm.$emit('click');
|
||||
findDropdown().vm.$emit('select', formatTimezone(findTzByName('Abu Dhabi')));
|
||||
await nextTick();
|
||||
expect(selectedTZOption.attributes('ischecked')).toBe('true');
|
||||
|
||||
expect(findDropdownItemByIndex(0).props('isSelected')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,15 +27,8 @@ RSpec.describe SafeFormatHelper, feature_category: :shared do
|
|||
result: '<b>strong</b> <a href="">link</a>'
|
||||
|
||||
context 'when format is marked as html_safe' do
|
||||
let(:format) { '<b>strong</b>'.html_safe }
|
||||
let(:args) { {} }
|
||||
|
||||
it 'raises an error' do
|
||||
message = 'Argument `format` must not be marked as html_safe!'
|
||||
|
||||
expect { helper.safe_format(format, **args) }
|
||||
.to raise_error ArgumentError, message
|
||||
end
|
||||
it_behaves_like 'safe formatting', '<b>strong</b>'.html_safe, args: {},
|
||||
result: '<b>strong</b>'
|
||||
end
|
||||
|
||||
context 'with a view component' do
|
||||
|
|
@ -54,5 +47,19 @@ RSpec.describe SafeFormatHelper, feature_category: :shared do
|
|||
.to eq('<b><br></b>')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with format containing escaped entities' do
|
||||
it_behaves_like 'safe formatting', 'In < hour',
|
||||
args: {},
|
||||
result: 'In < hour'
|
||||
|
||||
it_behaves_like 'safe formatting', '"air"',
|
||||
args: {},
|
||||
result: '"air"'
|
||||
|
||||
it_behaves_like 'safe formatting', 'Mix & match > all',
|
||||
args: {},
|
||||
result: 'Mix & match > all'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::RemoveInvalidDeployAccessLevelGroups,
|
||||
:migration, schema: 20230519011151, feature_category: :continuous_delivery do
|
||||
let!(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
|
||||
let!(:project) { table(:projects).create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
|
||||
let!(:group) { table(:namespaces).create!(name: 'group', path: 'group', type: 'Group') }
|
||||
let!(:user) { table(:users).create!(email: 'deployer@example.com', username: 'deployer', projects_limit: 0) }
|
||||
let!(:protected_environment) { table(:protected_environments).create!(project_id: project.id, name: 'production') }
|
||||
|
||||
let(:migration) do
|
||||
described_class.new(
|
||||
start_id: 1, end_id: 1000,
|
||||
batch_table: :protected_environment_deploy_access_levels, batch_column: :id,
|
||||
sub_batch_size: 10, pause_ms: 0,
|
||||
connection: ApplicationRecord.connection
|
||||
)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
let!(:deploy_access_level_access_level) do
|
||||
table(:protected_environment_deploy_access_levels)
|
||||
.create!(protected_environment_id: protected_environment.id, access_level: 40)
|
||||
end
|
||||
|
||||
let!(:deploy_access_level_user) do
|
||||
table(:protected_environment_deploy_access_levels)
|
||||
.create!(protected_environment_id: protected_environment.id, user_id: user.id)
|
||||
end
|
||||
|
||||
let!(:deploy_access_level_group) do
|
||||
table(:protected_environment_deploy_access_levels)
|
||||
.create!(protected_environment_id: protected_environment.id, group_id: group.id)
|
||||
end
|
||||
|
||||
let!(:deploy_access_level_namespace) do
|
||||
table(:protected_environment_deploy_access_levels)
|
||||
.create!(protected_environment_id: protected_environment.id, group_id: namespace.id)
|
||||
end
|
||||
|
||||
it 'backfill tiers for all environments in range' do
|
||||
expect(deploy_access_level_access_level).to be_present
|
||||
expect(deploy_access_level_user).to be_present
|
||||
expect(deploy_access_level_group).to be_present
|
||||
expect(deploy_access_level_namespace).to be_present
|
||||
|
||||
migration.perform
|
||||
|
||||
expect { deploy_access_level_access_level.reload }.not_to raise_error
|
||||
expect { deploy_access_level_user.reload }.not_to raise_error
|
||||
expect { deploy_access_level_group.reload }.not_to raise_error
|
||||
expect { deploy_access_level_namespace.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -77,7 +77,6 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
|
|||
context 'when reading from non redis-cache stores' do
|
||||
it_behaves_like 'reading using non redis cache stores', Gitlab::Redis::RepositoryCache
|
||||
it_behaves_like 'reading using non redis cache stores', Gitlab::Redis::FeatureFlag
|
||||
it_behaves_like 'reading using non redis cache stores', Gitlab::Redis::RateLimiting
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
|
|
@ -133,7 +132,6 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
|
|||
context 'when deleting from non redis-cache stores' do
|
||||
it_behaves_like 'deleting using non redis cache stores', Gitlab::Redis::RepositoryCache
|
||||
it_behaves_like 'deleting using non redis cache stores', Gitlab::Redis::FeatureFlag
|
||||
it_behaves_like 'deleting using non redis cache stores', Gitlab::Redis::RateLimiting
|
||||
end
|
||||
|
||||
context 'when deleting large amount of keys' do
|
||||
|
|
|
|||
|
|
@ -4,10 +4,4 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Redis::RateLimiting do
|
||||
include_examples "redis_new_instance_shared_examples", 'rate_limiting', Gitlab::Redis::Cache
|
||||
|
||||
describe '.cache_store' do
|
||||
it 'uses the CACHE_NAMESPACE namespace' do
|
||||
expect(described_class.cache_store.options[:namespace]).to eq(Gitlab::Redis::Cache::CACHE_NAMESPACE)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -211,6 +211,28 @@ RSpec.describe Emails::ServiceDesk, feature_category: :service_desk do
|
|||
|
||||
it_behaves_like 'a service desk notification email with template content', 'thank_you'
|
||||
end
|
||||
|
||||
context 'when issue url placeholder is used' do
|
||||
let(:full_issue_url) { issue_url(issue) }
|
||||
let(:template_content) { 'thank you, your new issue has been created. %{ISSUE_URL}' }
|
||||
let(:expected_template_html) do
|
||||
"<p dir=\"auto\">thank you, your new issue has been created. " \
|
||||
"<a href=\"#{full_issue_url}\">#{full_issue_url}</a></p>"
|
||||
end
|
||||
|
||||
it_behaves_like 'a service desk notification email with template content', 'thank_you'
|
||||
|
||||
context 'when it is used in markdown format' do
|
||||
let(:template_content) { 'thank you, your new issue has been created. [%{ISSUE_PATH}](%{ISSUE_URL})' }
|
||||
let(:issue_path) { "#{project.full_path}##{issue.iid}" }
|
||||
let(:expected_template_html) do
|
||||
"<p dir=\"auto\">thank you, your new issue has been created. " \
|
||||
"<a href=\"#{full_issue_url}\">#{issue_path}</a></p>"
|
||||
end
|
||||
|
||||
it_behaves_like 'a service desk notification email with template content', 'thank_you'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleToRemoveInvalidDeployAccessLevelGroups, feature_category: :continuous_delivery do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :protected_environment_deploy_access_levels,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -102,13 +102,13 @@ RSpec.describe CustomerRelations::Organization, type: :model do
|
|||
)
|
||||
end
|
||||
|
||||
subject(:found_organizations) { group.organizations.search(search_term) }
|
||||
subject(:found_crm_organizations) { group.crm_organizations.search(search_term) }
|
||||
|
||||
context 'when search term is empty' do
|
||||
let(:search_term) { "" }
|
||||
|
||||
it 'returns all group organizations' do
|
||||
expect(found_organizations).to contain_exactly(crm_organization_a, crm_organization_b)
|
||||
it 'returns all group crm_organizations' do
|
||||
expect(found_crm_organizations).to contain_exactly(crm_organization_a, crm_organization_b)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -137,13 +137,13 @@ RSpec.describe CustomerRelations::Organization, type: :model do
|
|||
let_it_be(:crm_organization_a) { create(:crm_organization, group: group, state: "inactive") }
|
||||
let_it_be(:crm_organization_b) { create(:crm_organization, group: group, state: "active") }
|
||||
|
||||
context 'when searching for organizations state' do
|
||||
it 'returns only inactive organizations' do
|
||||
expect(group.organizations.search_by_state(:inactive)).to contain_exactly(crm_organization_a)
|
||||
context 'when searching for crm_organizations state' do
|
||||
it 'returns only inactive crm_organizations' do
|
||||
expect(group.crm_organizations.search_by_state(:inactive)).to contain_exactly(crm_organization_a)
|
||||
end
|
||||
|
||||
it 'returns only active organizations' do
|
||||
expect(group.organizations.search_by_state(:active)).to contain_exactly(crm_organization_b)
|
||||
it 'returns only active crm_organizations' do
|
||||
expect(group.crm_organizations.search_by_state(:active)).to contain_exactly(crm_organization_b)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -154,15 +154,15 @@ RSpec.describe CustomerRelations::Organization, type: :model do
|
|||
create_list(:crm_organization, 2, group: group, state: 'inactive')
|
||||
end
|
||||
|
||||
it 'returns correct organization counts' do
|
||||
counts = group.organizations.counts_by_state
|
||||
it 'returns correct crm_organization counts' do
|
||||
counts = group.crm_organizations.counts_by_state
|
||||
|
||||
expect(counts['active']).to be(3)
|
||||
expect(counts['inactive']).to be(2)
|
||||
end
|
||||
|
||||
it 'returns 0 with no results' do
|
||||
counts = group.organizations.where(id: non_existing_record_id).counts_by_state
|
||||
counts = group.crm_organizations.where(id: non_existing_record_id).counts_by_state
|
||||
|
||||
expect(counts['active']).to be(0)
|
||||
expect(counts['inactive']).to be(0)
|
||||
|
|
@ -176,13 +176,13 @@ RSpec.describe CustomerRelations::Organization, type: :model do
|
|||
|
||||
describe '.sort_by_name' do
|
||||
it 'sorts them by name in ascendent order' do
|
||||
expect(group.organizations.sort_by_name).to eq([crm_organization_b, crm_organization_c, crm_organization_a])
|
||||
expect(group.crm_organizations.sort_by_name).to eq([crm_organization_b, crm_organization_c, crm_organization_a])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.sort_by_field' do
|
||||
it 'sorts them by description in descending order' do
|
||||
expect(group.organizations.sort_by_field('description', :desc))
|
||||
expect(group.crm_organizations.sort_by_field('description', :desc))
|
||||
.to eq([crm_organization_c, crm_organization_a, crm_organization_b])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ RSpec.describe Group, feature_category: :subgroups do
|
|||
end
|
||||
|
||||
it { is_expected.to have_many(:contacts).class_name('CustomerRelations::Contact') }
|
||||
it { is_expected.to have_many(:organizations).class_name('CustomerRelations::Organization') }
|
||||
it { is_expected.to have_many(:crm_organizations).class_name('CustomerRelations::Organization') }
|
||||
it { is_expected.to have_many(:protected_branches).inverse_of(:group).with_foreign_key(:namespace_id) }
|
||||
it { is_expected.to have_one(:crm_settings) }
|
||||
it { is_expected.to have_one(:group_feature) }
|
||||
|
|
@ -3176,13 +3176,13 @@ RSpec.describe Group, feature_category: :subgroups do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.organizations' do
|
||||
it 'returns organizations belonging to the group' do
|
||||
describe '.crm_organizations' do
|
||||
it 'returns crm_organizations belonging to the group' do
|
||||
crm_organization1 = create(:crm_organization, group: group)
|
||||
create(:crm_organization)
|
||||
crm_organization3 = create(:crm_organization, group: group)
|
||||
|
||||
expect(group.organizations).to contain_exactly(crm_organization1, crm_organization3)
|
||||
expect(group.crm_organizations).to contain_exactly(crm_organization1, crm_organization3)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -907,7 +907,7 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg
|
|||
let(:subsub_project) { create(:project, group: subsubgroup) }
|
||||
|
||||
let!(:contacts) { create_list(:contact, 4, group: root_group) }
|
||||
let!(:organizations) { create_list(:crm_organization, 2, group: root_group) }
|
||||
let!(:crm_organizations) { create_list(:crm_organization, 2, group: root_group) }
|
||||
|
||||
before do
|
||||
create(:issue_customer_relations_contact, contact: contacts[0], issue: create(:issue, project: root_project))
|
||||
|
|
@ -966,7 +966,7 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg
|
|||
it 'moves all crm objects' do
|
||||
expect { transfer_service.execute(new_parent_group) }
|
||||
.to change { root_group.contacts.count }.by(-4)
|
||||
.and change { root_group.organizations.count }.by(-2)
|
||||
.and change { root_group.crm_organizations.count }.by(-2)
|
||||
end
|
||||
|
||||
it 'retains issue contacts' do
|
||||
|
|
@ -991,7 +991,7 @@ RSpec.describe Groups::TransferService, :sidekiq_inline, feature_category: :subg
|
|||
it 'moves all crm objects' do
|
||||
expect { transfer_service.execute(subgroup_in_new_parent_group) }
|
||||
.to change { root_group.contacts.count }.by(-4)
|
||||
.and change { root_group.organizations.count }.by(-2)
|
||||
.and change { root_group.crm_organizations.count }.by(-2)
|
||||
end
|
||||
|
||||
it 'retains issue contacts' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'milestone with interactive markdown task list items in description' do
|
||||
let(:markdown) do
|
||||
<<-MARKDOWN.strip_heredoc
|
||||
This is a task list:
|
||||
|
||||
- [ ] Incomplete task list item 1
|
||||
- [x] Complete task list item 1
|
||||
- [ ] Incomplete task list item 2
|
||||
- [x] Complete task list item 2
|
||||
- [ ] Incomplete task list item 3
|
||||
- [ ] Incomplete task list item 4
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
before do
|
||||
milestone.update!(description: markdown)
|
||||
end
|
||||
|
||||
it 'renders task list in description' do
|
||||
visit milestone_path
|
||||
|
||||
wait_for_requests
|
||||
|
||||
within('ul.task-list') do
|
||||
expect(page).to have_selector('li.task-list-item', count: 6)
|
||||
expect(page).to have_selector('li.task-list-item input.task-list-item-checkbox[checked]', count: 2)
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows interaction with task list item checkboxes' do
|
||||
visit milestone_path
|
||||
|
||||
wait_for_requests
|
||||
|
||||
within('ul.task-list') do
|
||||
within('li.task-list-item', text: 'Incomplete task list item 1') do
|
||||
find('input.task-list-item-checkbox').click
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
expect(page).to have_selector('li.task-list-item', count: 6)
|
||||
page.all('li.task-list-item input.task-list-item-checkbox') { |element| expect(element).to be_checked }
|
||||
|
||||
# After page reload, the task list items should still be checked
|
||||
visit milestone_path
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_selector('ul input[type="checkbox"][checked]', count: 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
42
yarn.lock
42
yarn.lock
|
|
@ -1059,10 +1059,10 @@
|
|||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@8.40.0":
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec"
|
||||
integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==
|
||||
"@eslint/js@8.41.0":
|
||||
version "8.41.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.41.0.tgz#080321c3b68253522f7646b55b577dd99d2950b3"
|
||||
integrity sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==
|
||||
|
||||
"@gitlab/at.js@1.5.7":
|
||||
version "1.5.7"
|
||||
|
|
@ -1139,10 +1139,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20230511143809.tgz#c13dfb4d1edab2e020d4a102d4ec18048917490f"
|
||||
integrity sha512-caP5WSaTuIhPrPGUWyvPT4np6swkKQHM1Pa9HiBnGhiOhhQ1+3X/+J9EoZXUhnhwiBzS7sp32Uyttam4am/sTA==
|
||||
|
||||
"@graphql-eslint/eslint-plugin@3.18.0":
|
||||
version "3.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-eslint/eslint-plugin/-/eslint-plugin-3.18.0.tgz#071b5580d1d47ac0f25fd4296fea4105ddd8e401"
|
||||
integrity sha512-riEEfRycc0+pWxcEWqHi8woRxzg1xZqAfh9DRACJUR7bTN8dmc1N04i7+pvW4sevClUFYC2wuL1Vtr+DwzXLUg==
|
||||
"@graphql-eslint/eslint-plugin@3.19.0":
|
||||
version "3.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@graphql-eslint/eslint-plugin/-/eslint-plugin-3.19.0.tgz#08cf96f7b093622449064bc258526a73f92944eb"
|
||||
integrity sha512-p1jK3IUTi+wecMAzeWpDWQE3ZskayKvE6sFnELaVqmYERJhsocKp1yoVWgWfLuSDgtcMEKG7YHz8OQCmy/9Siw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.18.6"
|
||||
"@graphql-tools/code-file-loader" "^7.3.6"
|
||||
|
|
@ -5819,15 +5819,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1:
|
|||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
|
||||
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
|
||||
|
||||
eslint@8.40.0:
|
||||
version "8.40.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4"
|
||||
integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==
|
||||
eslint@8.41.0:
|
||||
version "8.41.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.41.0.tgz#3062ca73363b4714b16dbc1e60f035e6134b6f1c"
|
||||
integrity sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.2.0"
|
||||
"@eslint-community/regexpp" "^4.4.0"
|
||||
"@eslint/eslintrc" "^2.0.3"
|
||||
"@eslint/js" "8.40.0"
|
||||
"@eslint/js" "8.41.0"
|
||||
"@humanwhocodes/config-array" "^0.11.8"
|
||||
"@humanwhocodes/module-importer" "^1.0.1"
|
||||
"@nodelib/fs.walk" "^1.2.8"
|
||||
|
|
@ -5847,13 +5847,12 @@ eslint@8.40.0:
|
|||
find-up "^5.0.0"
|
||||
glob-parent "^6.0.2"
|
||||
globals "^13.19.0"
|
||||
grapheme-splitter "^1.0.4"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.2.0"
|
||||
import-fresh "^3.0.0"
|
||||
imurmurhash "^0.1.4"
|
||||
is-glob "^4.0.0"
|
||||
is-path-inside "^3.0.3"
|
||||
js-sdsl "^4.1.4"
|
||||
js-yaml "^4.1.0"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.4.1"
|
||||
|
|
@ -6620,10 +6619,10 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
|
|||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
grapheme-splitter@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
|
||||
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
|
||||
graphemer@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
|
||||
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
|
||||
|
||||
graphql-config@^4.4.0:
|
||||
version "4.5.0"
|
||||
|
|
@ -8053,11 +8052,6 @@ js-cookie@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
|
||||
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
|
||||
|
||||
js-sdsl@^4.1.4:
|
||||
version "4.1.4"
|
||||
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.4.tgz#78793c90f80e8430b7d8dc94515b6c77d98a26a6"
|
||||
integrity sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
|
|
|
|||
Loading…
Reference in New Issue