Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-06-27 09:08:41 +00:00
parent 5849e597a0
commit 7a84ffdf31
17 changed files with 148 additions and 59 deletions

View File

@ -183,7 +183,6 @@ Lint/RedundantCopDisableDirective:
- 'lib/gitlab/background_migration/backfill_project_statistics_storage_size_without_uploads_size.rb'
- 'lib/gitlab/background_migration/batching_strategies/loose_index_scan_batching_strategy.rb'
- 'lib/gitlab/background_migration/fix_incoherent_packages_size_on_project_statistics.rb'
- 'lib/gitlab/background_migration/migrate_personal_namespace_project_maintainer_to_owner.rb'
- 'lib/gitlab/background_migration/re_expire_o_auth_tokens.rb'
- 'lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings.rb'
- 'lib/gitlab/background_migration/update_jira_tracker_data_deployment_type_based_on_url.rb'
@ -264,8 +263,6 @@ Lint/RedundantCopDisableDirective:
- 'spec/lib/gitlab/background_migration/backfill_vulnerability_reads_cluster_agent_spec.rb'
- 'spec/lib/gitlab/background_migration/batching_strategies/backfill_project_statistics_with_container_registry_size_batching_strategy_spec.rb'
- 'spec/lib/gitlab/background_migration/batching_strategies/remove_backfilled_job_artifacts_expire_at_batching_strategy_spec.rb'
- 'spec/lib/gitlab/background_migration/reset_duplicate_ci_runners_token_encrypted_values_on_projects_spec.rb'
- 'spec/lib/gitlab/background_migration/reset_duplicate_ci_runners_token_values_on_projects_spec.rb'
- 'spec/lib/gitlab/ci/reports/security/scanner_spec.rb'
- 'spec/lib/gitlab/database/load_balancing/transaction_leaking_spec.rb'
- 'spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb'
@ -284,7 +281,6 @@ Lint/RedundantCopDisableDirective:
- 'spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/size_limiter/server_spec.rb'
- 'spec/metrics_server/metrics_server_spec.rb'
- 'spec/migrations/20220725150127_update_jira_tracker_data_deployment_type_based_on_url_spec.rb'
- 'spec/migrations/add_namespaces_emails_enabled_column_data_spec.rb'
- 'spec/migrations/add_projects_emails_enabled_column_data_spec.rb'
- 'spec/models/ci/build_trace_chunk_spec.rb'
@ -300,7 +296,6 @@ Lint/RedundantCopDisableDirective:
- 'spec/presenters/packages/pypi/simple_package_versions_presenter_spec.rb'
- 'spec/requests/api/alert_management_alerts_spec.rb'
- 'spec/requests/api/graphql/ci/config_spec.rb'
- 'spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb'
- 'spec/services/alert_management/metric_images/upload_service_spec.rb'
- 'spec/services/boards/lists/list_service_spec.rb'
- 'spec/services/projects/update_statistics_service_spec.rb'

View File

@ -1363,7 +1363,6 @@ RSpec/MissingFeatureCategory:
- 'ee/spec/serializers/licenses_list_serializer_spec.rb'
- 'ee/spec/serializers/linked_feature_flag_issue_entity_spec.rb'
- 'ee/spec/serializers/member_user_entity_spec.rb'
- 'ee/spec/serializers/merge_request_poll_widget_entity_spec.rb'
- 'ee/spec/serializers/merge_request_sidebar_basic_entity_spec.rb'
- 'ee/spec/serializers/metrics_report_metric_entity_spec.rb'
- 'ee/spec/serializers/metrics_reports_comparer_entity_spec.rb'

View File

@ -66,7 +66,7 @@ gem 'omniauth-gitlab', '~> 4.0.0', path: 'vendor/gems/omniauth-gitlab' # See ven
gem 'omniauth-google-oauth2', '~> 1.1'
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 2.1.0'
gem 'omniauth-shibboleth-redux', '~> 2.0'
gem 'omniauth-shibboleth-redux', '~> 2.0', require: 'omniauth-shibboleth'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.4.0', path: 'vendor/gems/omniauth_crowd' # See vendor/gems/omniauth_crowd/README.md
gem 'omniauth_openid_connect', '~> 0.6.1'

View File

@ -161,6 +161,11 @@ export default {
</gl-sprintf>
</div>
<gl-disclosure-dropdown :toggle-text="selectedStage" :items="dropdownItems" class="gl-mt-3" />
<gl-disclosure-dropdown
:toggle-text="selectedStage"
:items="dropdownItems"
block
class="gl-mt-3"
/>
</div>
</template>

View File

@ -5,7 +5,7 @@ export default {
components: {
ListboxInput,
},
inject: ['label', 'name', 'emails', 'emptyValueText', 'value', 'disabled'],
inject: ['label', 'name', 'emails', 'emptyValueText', 'value', 'disabled', 'placement'],
data() {
return {
selected: this.value,
@ -41,6 +41,8 @@ export default {
:name="name"
:items="options"
:disabled="disabled"
:placement="placement"
fluid-width
@select="onSelect"
/>
</template>

View File

@ -10,7 +10,7 @@ const initNotificationEmailListboxInputs = () => {
const els = [...document.querySelectorAll('.js-notification-email-listbox-input')];
els.forEach((el, index) => {
const { label, name, emptyValueText, value = '' } = el.dataset;
const { label, name, emptyValueText, value = '', placement } = el.dataset;
return new Vue({
el,
@ -22,6 +22,7 @@ const initNotificationEmailListboxInputs = () => {
emptyValueText,
value,
disabled: parseBoolean(el.dataset.disabled),
placement,
},
render(h) {
return h(NotificationEmailListboxInput);

View File

@ -1,5 +1,6 @@
import Vue from 'vue';
import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
export const initListboxInputs = () => {
const els = [...document.querySelectorAll('.js-listbox-input')];
@ -30,6 +31,8 @@ export const initListboxInputs = () => {
name,
defaultToggleText,
selected: this.selected,
block: parseBoolean(el.dataset.block),
fluidWidth: parseBoolean(el.dataset.fluidWidth),
items,
},
attrs: {

View File

@ -47,6 +47,21 @@ export default {
required: false,
default: false,
},
fluidWidth: {
type: GlCollapsibleListbox.props.fluidWidth.type,
required: false,
default: GlCollapsibleListbox.props.fluidWidth.default,
},
placement: {
type: GlCollapsibleListbox.props.placement.type,
required: false,
default: GlCollapsibleListbox.props.placement.default,
},
block: {
type: GlCollapsibleListbox.props.block.type,
required: false,
default: GlCollapsibleListbox.props.block.default,
},
},
data() {
return {
@ -123,6 +138,9 @@ export default {
:searchable="isSearchable"
:no-results-text="$options.i18n.noResultsText"
:disabled="disabled"
:fluid-width="fluidWidth"
:placement="placement"
:block="block"
@search="search"
@select="$emit($options.model.event, $event)"
/>

View File

@ -14,4 +14,4 @@
.table-section.section-30
= form_for setting, url: profile_group_notifications_path(group), method: :put, html: { class: 'update-notifications gl-display-flex' } do |f|
.js-notification-email-listbox-input{ data: { name: 'notification_setting[notification_email]', emails: @user.public_verified_emails.to_json, empty_value_text: _('Global notification email') , value: setting.notification_email } }
.js-notification-email-listbox-input{ data: { name: 'notification_setting[notification_email]', emails: @user.public_verified_emails.to_json, empty_value_text: _('Global notification email') , value: setting.notification_email, placement: 'right' } }

View File

@ -79,7 +79,7 @@
= f.select :layout, layout_choices, {}, class: 'gl-form-select custom-select'
.form-text.text-muted
= s_('Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout.').html_safe % { percentage: '100%' }
.js-listbox-input{ data: { label: s_('Preferences|Homepage'), description: s_('Preferences|Choose what content you want to see by default on your homepage.'), name: 'user[dashboard]', items: dashboard_choices.to_json, value: current_user.dashboard } }
.js-listbox-input{ data: { label: s_('Preferences|Homepage'), description: s_('Preferences|Choose what content you want to see by default on your homepage.'), name: 'user[dashboard]', items: dashboard_choices.to_json, value: current_user.dashboard, block: true.to_s, fluid_width: true.to_s } }
= render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
@ -130,7 +130,7 @@
= succeed '.' do
= link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank', rel: 'noopener noreferrer'
.col-lg-8
.js-listbox-input{ data: { label: _('Language'), description: s_('Preferences|This feature is experimental and translations are not yet complete.'), name: 'user[preferred_language]', items: language_choices.to_json, value: current_user.preferred_language } }
.js-listbox-input{ data: { label: _('Language'), description: s_('Preferences|This feature is experimental and translations are not yet complete.'), name: 'user[preferred_language]', items: language_choices.to_json, value: current_user.preferred_language, block: true.to_s, fluid_width: true.to_s } }
%p.gl-mt-n5
= link_to help_page_url('development/i18n/translation'), class: 'text-nowrap', target: '_blank', rel: 'noopener noreferrer' do
= _("Help translate GitLab into your language")

View File

@ -47,7 +47,7 @@
self-managed: true
gitlab-com: true
available_in: [Premium, Ultimate]
documentation_link: 'https://docs.gitlab.com/ee/ci/pipelines/settings.html#coverage-check-approval-rule'
documentation_link: 'https://docs.gitlab.com/ee/ci/testing/code_coverage.html#coverage-check-approval-rule'
image_url: https://about.gitlab.com/images/14_1/coverage-mr-approval-rule.png
published_at: 2021-07-22
release: 14.1

View File

@ -19,6 +19,8 @@ A workspace is a virtual sandbox environment for your code in GitLab. You can us
Each workspace includes its own set of dependencies, libraries, and tools, which you can customize to meet the specific needs of each project. Workspaces use the AMD64 architecture.
For a demo of this feature, see [GitLab Workspaces Demo](https://tech-marketing.gitlab.io/static-demos/workspaces/ws_public.html).
## Set up a workspace
### Prerequisites

View File

@ -69,17 +69,23 @@ module QA
QA::Runtime::Logger.warn("Video deletion error: #{e}")
end
def record?(example)
example.metadata[:file_path].include?("/browser_ui/")
end
private
def configure_rspec
RSpec.configure do |config|
config.prepend_before do |example|
QA::Service::DockerRun::Video.start_recording(example)
QA::Service::DockerRun::Video.start_recording(example) if QA::Service::DockerRun::Video.record?(example)
end
config.append_after do |example|
QA::Service::DockerRun::Video.stop_recording
QA::Service::DockerRun::Video.delete_video unless example.exception
if QA::Service::DockerRun::Video.record?(example)
QA::Service::DockerRun::Video.stop_recording
QA::Service::DockerRun::Video.delete_video unless example.exception
end
end
end
end

View File

@ -12,14 +12,21 @@ describe('diff_discussion_header component', () => {
let store;
let wrapper;
const createComponent = ({ propsData = {} } = {}) => {
wrapper = shallowMount(diffDiscussionHeader, {
store,
propsData: {
discussion: discussionMock,
...propsData,
},
});
};
beforeEach(() => {
window.mrTabs = {};
store = createStore();
wrapper = shallowMount(diffDiscussionHeader, {
store,
propsData: { discussion: discussionMock },
});
createComponent({ propsData: { discussion: discussionMock } });
});
describe('Avatar', () => {
@ -33,13 +40,9 @@ describe('diff_discussion_header component', () => {
});
it('renders avatar of the first note author', () => {
const props = findAvatar().props();
expect(props).toMatchObject({
src: firstNoteAuthor.avatar_url,
alt: firstNoteAuthor.name,
size: 32,
});
expect(findAvatar().props('src')).toBe(firstNoteAuthor.avatar_url);
expect(findAvatar().props('alt')).toBe(firstNoteAuthor.name);
expect(findAvatar().props('size')).toBe(32);
});
});
@ -53,14 +56,16 @@ describe('diff_discussion_header component', () => {
projectPath: 'something',
};
wrapper.setProps({
discussion: {
...discussionMock,
for_commit: true,
commit_id: commitId,
diff_discussion: true,
diff_file: {
...mockDiffFile,
createComponent({
propsData: {
discussion: {
...discussionMock,
for_commit: true,
commit_id: commitId,
diff_discussion: true,
diff_file: {
...mockDiffFile,
},
},
},
});
@ -71,9 +76,15 @@ describe('diff_discussion_header component', () => {
describe('for diff threads without a commit id', () => {
it('should show started a thread on the diff text', async () => {
Object.assign(wrapper.vm.discussion, {
for_commit: false,
commit_id: null,
createComponent({
propsData: {
discussion: {
...discussionMock,
diff_discussion: true,
for_commit: false,
commit_id: null,
},
},
});
await nextTick();
@ -81,10 +92,16 @@ describe('diff_discussion_header component', () => {
});
it('should show thread on older version text', async () => {
Object.assign(wrapper.vm.discussion, {
for_commit: false,
commit_id: null,
active: false,
createComponent({
propsData: {
discussion: {
...discussionMock,
diff_discussion: true,
for_commit: false,
commit_id: null,
active: false,
},
},
});
await nextTick();
@ -102,7 +119,16 @@ describe('diff_discussion_header component', () => {
describe('for diff thread with a commit id', () => {
it('should display started thread on commit header', async () => {
wrapper.vm.discussion.for_commit = false;
createComponent({
propsData: {
discussion: {
...discussionMock,
diff_discussion: true,
for_commit: false,
commit_id: commitId,
},
},
});
await nextTick();
expect(wrapper.text()).toContain(`started a thread on commit ${truncatedCommitId}`);
@ -111,8 +137,17 @@ describe('diff_discussion_header component', () => {
});
it('should display outdated change on commit header', async () => {
wrapper.vm.discussion.for_commit = false;
wrapper.vm.discussion.active = false;
createComponent({
propsData: {
discussion: {
...discussionMock,
diff_discussion: true,
for_commit: false,
commit_id: commitId,
active: false,
},
},
});
await nextTick();
expect(wrapper.text()).toContain(

View File

@ -13,6 +13,7 @@ describe('NotificationEmailListboxInput', () => {
const emptyValueText = 'emptyValueText';
const value = 'value';
const disabled = false;
const placement = 'right';
// Finders
const findListboxInput = () => wrapper.findComponent(ListboxInput);
@ -26,6 +27,7 @@ describe('NotificationEmailListboxInput', () => {
emptyValueText,
value,
disabled,
placement,
},
attachTo,
});

View File

@ -11,6 +11,9 @@ let wrapper;
const tagName = 'test-tag';
const path = '/path/to/tag';
const isProtected = false;
const modalHideSpy = jest.fn();
const modalShowSpy = jest.fn();
const formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation();
const createComponent = (data = {}) => {
wrapper = extendedWrapper(
@ -27,6 +30,10 @@ const createComponent = (data = {}) => {
GlModal: stubComponent(GlModal, {
template:
'<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
methods: {
hide: modalHideSpy,
show: modalShowSpy,
},
}),
GlButton,
GlFormInput,
@ -61,32 +68,26 @@ describe('Delete tag modal', () => {
});
it('submits the form when the delete button is clicked', () => {
const submitFormSpy = jest.spyOn(wrapper.vm.$refs.form, 'submit');
findDeleteButton().trigger('click');
expect(findForm().attributes('action')).toBe(path);
expect(submitFormSpy).toHaveBeenCalled();
expect(formSubmitSpy).toHaveBeenCalledTimes(1);
});
it('calls show on the modal when a `openModal` event is received through the event hub', () => {
const showSpy = jest.spyOn(wrapper.vm.$refs.modal, 'show');
eventHub.$emit('openModal', {
isProtected,
tagName,
path,
});
expect(showSpy).toHaveBeenCalled();
expect(modalShowSpy).toHaveBeenCalled();
});
it('calls hide on the modal when cancel button is clicked', () => {
const closeModalSpy = jest.spyOn(wrapper.vm.$refs.modal, 'hide');
findCancelButton().trigger('click');
expect(closeModalSpy).toHaveBeenCalled();
expect(modalHideSpy).toHaveBeenCalled();
});
});

View File

@ -7,7 +7,7 @@ describe('ListboxInput', () => {
// Props
const label = 'label';
const decription = 'decription';
const description = 'description';
const name = 'name';
const defaultToggleText = 'defaultToggleText';
const items = [
@ -34,7 +34,7 @@ describe('ListboxInput', () => {
wrapper = shallowMount(ListboxInput, {
propsData: {
label,
decription,
description,
name,
defaultToggleText,
items,
@ -72,8 +72,8 @@ describe('ListboxInput', () => {
expect(findGlFormGroup().attributes('label')).toBe(label);
});
it('passes the decription to the form group', () => {
expect(findGlFormGroup().attributes('decription')).toBe(decription);
it('passes the description to the form group', () => {
expect(findGlFormGroup().attributes('description')).toBe(description);
});
it('sets the input name', () => {
@ -89,6 +89,26 @@ describe('ListboxInput', () => {
});
});
describe('props', () => {
it.each([true, false])("passes %s to the listbox's fluidWidth prop", (fluidWidth) => {
createComponent({ fluidWidth });
expect(findGlListbox().props('fluidWidth')).toBe(fluidWidth);
});
it.each(['right', 'left'])("passes %s to the listbox's placement prop", (placement) => {
createComponent({ placement });
expect(findGlListbox().props('placement')).toBe(placement);
});
it.each([true, false])("passes %s to the listbox's block prop", (block) => {
createComponent({ block });
expect(findGlListbox().props('block')).toBe(block);
});
});
describe('toggle text', () => {
it('uses the default toggle text while no value is selected', () => {
createComponent();