Commit 27704ed5 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 841df318 0aecfcaa
<script>
/*
* The commented part of this component needs to be re-enabled in the refactor process,
* See here for more info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64939
*/
import {
GlBadge,
GlButton,
GlModal,
GlModalDirective,
GlTooltipDirective,
GlEmptyState,
GlTab,
GlTabs,
GlSprintf,
} from '@gitlab/ui';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { objectToQuery } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
// import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue';
// import DependencyRow from '~/packages/details/components/dependency_row.vue';
// import InstallationCommands from '~/packages/details/components/installation_commands.vue';
// import PackageFiles from '~/packages/details/components/package_files.vue';
// import PackageHistory from '~/packages/details/components/package_history.vue';
// import PackageListRow from '~/packages/shared/components/package_list_row.vue';
import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
import {
PackageType,
TrackingActions,
SHOW_DELETE_SUCCESS_ALERT,
} from '~/packages/shared/constants';
import { packageTypeToTrackCategory } from '~/packages/shared/utils';
import Tracking from '~/tracking';
export default {
name: 'PackagesApp',
components: {
GlBadge,
GlButton,
GlEmptyState,
GlModal,
GlTab,
GlTabs,
GlSprintf,
PackageTitle: () => import('~/packages/details/components/package_title.vue'),
TerraformTitle: () =>
import('~/packages_and_registries/infrastructure_registry/components/details_title.vue'),
PackagesListLoader,
// PackageListRow,
// DependencyRow,
// PackageHistory,
// AdditionalMetadata,
// InstallationCommands,
// PackageFiles,
},
directives: {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
},
mixins: [Tracking.mixin()],
inject: [
'titleComponent',
'projectName',
'canDelete',
'svgPath',
'npmPath',
'npmHelpPath',
'projectListUrl',
'groupListUrl',
],
trackingActions: { ...TrackingActions },
data() {
return {
fileToDelete: null,
packageEntity: {},
};
},
computed: {
packageFiles() {
return this.packageEntity.packageFiles;
},
isLoading() {
return false;
},
isValidPackage() {
return Boolean(this.packageEntity.name);
},
tracking() {
return {
category: packageTypeToTrackCategory(this.packageEntity.package_type),
};
},
hasVersions() {
return this.packageEntity.versions?.length > 0;
},
packageDependencies() {
return this.packageEntity.dependency_links || [];
},
showDependencies() {
return this.packageEntity.package_type === PackageType.NUGET;
},
showFiles() {
return this.packageEntity?.package_type !== PackageType.COMPOSER;
},
},
methods: {
formatSize(size) {
return numberToHumanSize(size);
},
getPackageVersions() {
if (!this.packageEntity.versions) {
// this.fetchPackageVersions();
}
},
async confirmPackageDeletion() {
this.track(TrackingActions.DELETE_PACKAGE);
await this.deletePackage();
const returnTo =
!this.groupListUrl || document.referrer.includes(this.projectName)
? this.projectListUrl
: this.groupListUrl; // to avoid security issue url are supplied from backend
const modalQuery = objectToQuery({ [SHOW_DELETE_SUCCESS_ALERT]: true });
window.location.replace(`${returnTo}?${modalQuery}`);
},
handleFileDelete(file) {
this.track(TrackingActions.REQUEST_DELETE_PACKAGE_FILE);
this.fileToDelete = { ...file };
this.$refs.deleteFileModal.show();
},
confirmFileDelete() {
this.track(TrackingActions.DELETE_PACKAGE_FILE);
// this.deletePackageFile(this.fileToDelete.id);
this.fileToDelete = null;
},
},
i18n: {
deleteModalTitle: s__(`PackageRegistry|Delete Package Version`),
deleteModalContent: s__(
`PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?`,
),
deleteFileModalTitle: s__(`PackageRegistry|Delete Package File`),
deleteFileModalContent: s__(
`PackageRegistry|You are about to delete %{filename}. This is a destructive action that may render your package unusable. Are you sure?`,
),
},
modal: {
packageDeletePrimaryAction: {
text: __('Delete'),
attributes: [
{ variant: 'danger' },
{ category: 'primary' },
{ 'data-qa-selector': 'delete_modal_button' },
],
},
fileDeletePrimaryAction: {
text: __('Delete'),
attributes: [{ variant: 'danger' }, { category: 'primary' }],
},
cancelAction: {
text: __('Cancel'),
},
},
};
</script>
<template>
<gl-empty-state
v-if="!isValidPackage"
:title="s__('PackageRegistry|Unable to load package')"
:description="s__('PackageRegistry|There was a problem fetching the details for this package.')"
:svg-path="svgPath"
/>
<div v-else class="packages-app">
<component :is="titleComponent">
<template #delete-button>
<gl-button
v-if="canDelete"
v-gl-modal="'delete-modal'"
class="js-delete-button"
variant="danger"
category="primary"
data-qa-selector="delete_button"
>
{{ __('Delete') }}
</gl-button>
</template>
</component>
<gl-tabs>
<gl-tab :title="__('Detail')">
<div data-qa-selector="package_information_content">
<!-- <package-history :package-entity="packageEntity" :project-name="projectName" />
<installation-commands
:package-entity="packageEntity"
:npm-path="npmPath"
:npm-help-path="npmHelpPath"
/>
<additional-metadata :package-entity="packageEntity" /> -->
</div>
<!-- <package-files
v-if="showFiles"
:package-files="packageFiles"
:can-delete="canDelete"
@download-file="track($options.trackingActions.PULL_PACKAGE)"
@delete-file="handleFileDelete"
/> -->
</gl-tab>
<gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
<template #title>
<span>{{ __('Dependencies') }}</span>
<gl-badge size="sm" data-testid="dependencies-badge">{{
packageDependencies.length
}}</gl-badge>
</template>
<template v-if="packageDependencies.length > 0">
<dependency-row
v-for="(dep, index) in packageDependencies"
:key="index"
:dependency="dep"
/>
</template>
<p v-else class="gl-mt-3" data-testid="no-dependencies-message">
{{ s__('PackageRegistry|This NuGet package has no dependencies.') }}
</p>
</gl-tab>
<gl-tab
:title="__('Other versions')"
title-item-class="js-versions-tab"
@click="getPackageVersions"
>
<template v-if="isLoading && !hasVersions">
<packages-list-loader />
</template>
<template v-else-if="hasVersions">
<!-- <package-list-row
v-for="v in packageEntity.versions"
:key="v.id"
:package-entity="{ name: packageEntity.name, ...v }"
:package-link="v.id.toString()"
:disable-delete="true"
:show-package-type="false"
/> -->
</template>
<p v-else class="gl-mt-3" data-testid="no-versions-message">
{{ s__('PackageRegistry|There are no other versions of this package.') }}
</p>
</gl-tab>
</gl-tabs>
<gl-modal
ref="deleteModal"
class="js-delete-modal"
modal-id="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
@primary="confirmPackageDeletion"
@canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE)"
>
<template #modal-title>{{ $options.i18n.deleteModalTitle }}</template>
<gl-sprintf :message="$options.i18n.deleteModalContent">
<template #version>
<strong>{{ packageEntity.version }}</strong>
</template>
<template #name>
<strong>{{ packageEntity.name }}</strong>
</template>
</gl-sprintf>
</gl-modal>
<gl-modal
ref="deleteFileModal"
modal-id="delete-file-modal"
:action-primary="$options.modal.fileDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
@primary="confirmFileDelete"
@canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE_FILE)"
>
<template #modal-title>{{ $options.i18n.deleteFileModalTitle }}</template>
<gl-sprintf v-if="fileToDelete" :message="$options.i18n.deleteFileModalContent">
<template #filename>
<strong>{{ fileToDelete.file_name }}</strong>
</template>
</gl-sprintf>
</gl-modal>
</div>
</template>
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import Translate from '~/vue_shared/translate';
import PackagesApp from '../components/details/app.vue';
Vue.use(Translate);
export default () => {
const el = document.getElementById('js-vue-packages-detail-new');
if (!el) {
return null;
}
const { canDelete, ...datasetOptions } = el.dataset;
return new Vue({
el,
provide: {
canDelete: parseBoolean(canDelete),
titleComponent: 'PackageTitle',
...datasetOptions,
},
render(createElement) {
return createElement(PackagesApp);
},
});
};
import initPackageDetail from '~/packages/details/'; (async function initPackage() {
let app;
initPackageDetail(); if (document.getElementById('js-vue-packages-detail-new')) {
app = await import(
/* webpackChunkName: 'new_package_app' */ `~/packages_and_registries/package_registry/pages/details.js`
);
} else {
app = await import('~/packages/details/');
}
app.default();
})();
...@@ -63,4 +63,29 @@ module PackagesHelper ...@@ -63,4 +63,29 @@ module PackagesHelper
project.container_expiration_policy.nil? && project.container_expiration_policy.nil? &&
project.container_repositories.exists? project.container_repositories.exists?
end end
def package_details_data(project, package = nil)
{
package: package ? package_from_presenter(package) : nil,
can_delete: can?(current_user, :destroy_package, project).to_s,
svg_path: image_path('illustrations/no-packages.svg'),
npm_path: package_registry_instance_url(:npm),
npm_help_path: help_page_path('user/packages/npm_registry/index'),
maven_path: package_registry_project_url(project.id, :maven),
maven_help_path: help_page_path('user/packages/maven_repository/index'),
conan_path: package_registry_project_url(project.id, :conan),
conan_help_path: help_page_path('user/packages/conan_repository/index'),
nuget_path: nuget_package_registry_url(project.id),
nuget_help_path: help_page_path('user/packages/nuget_repository/index'),
pypi_path: pypi_registry_url(project.id),
pypi_setup_path: package_registry_project_url(project.id, :pypi),
pypi_help_path: help_page_path('user/packages/pypi_repository/index'),
composer_path: composer_registry_url(project&.group&.id),
composer_help_path: help_page_path('user/packages/composer_repository/index'),
project_name: project.name,
project_list_url: project_packages_path(project),
group_list_url: project.group ? group_packages_path(project.group) : '',
composer_config_repository_name: composer_config_repository_name(project.group&.id)
}
end
end end
...@@ -40,7 +40,6 @@ class WikiPage ...@@ -40,7 +40,6 @@ class WikiPage
end end
validates :title, presence: true validates :title, presence: true
validates :content, presence: true
validate :validate_path_limits, if: :title_changed? validate :validate_path_limits, if: :title_changed?
validate :validate_content_size_limit, if: :content_changed? validate :validate_content_size_limit, if: :content_changed?
......
...@@ -6,23 +6,7 @@ ...@@ -6,23 +6,7 @@
.row .row
.col-12 .col-12
#js-vue-packages-detail{ data: { package: package_from_presenter(@package), - if Feature.enabled?(:package_details_apollo)
can_delete: can?(current_user, :destroy_package, @project).to_s, #js-vue-packages-detail-new{ data: package_details_data(@project) }
svg_path: image_path('illustrations/no-packages.svg'), - else
npm_path: package_registry_instance_url(:npm), #js-vue-packages-detail{ data: package_details_data(@project, @package) }
npm_help_path: help_page_path('user/packages/npm_registry/index'),
maven_path: package_registry_project_url(@project.id, :maven),
maven_help_path: help_page_path('user/packages/maven_repository/index'),
conan_path: package_registry_project_url(@project.id, :conan),
conan_help_path: help_page_path('user/packages/conan_repository/index'),
nuget_path: nuget_package_registry_url(@project.id),
nuget_help_path: help_page_path('user/packages/nuget_repository/index'),
pypi_path: pypi_registry_url(@project.id),
pypi_setup_path: package_registry_project_url(@project.id, :pypi),
pypi_help_path: help_page_path('user/packages/pypi_repository/index'),
composer_path: composer_registry_url(@project&.group&.id),
composer_help_path: help_page_path('user/packages/composer_repository/index'),
project_name: @project.name,
project_list_url: project_packages_path(@project),
group_list_url: @project.group ? group_packages_path(@project.group) : '',
composer_config_repository_name: composer_config_repository_name(@project.group&.id)} }
...@@ -84,7 +84,6 @@ ...@@ -84,7 +84,6 @@
- mlops - mlops
- mobile_signing_deployment - mobile_signing_deployment
- navigation - navigation
- not_owned
- omnibus_package - omnibus_package
- on_call_schedule_management - on_call_schedule_management
- onboarding - onboarding
......
---
name: package_details_apollo
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64939
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334786
milestone: '14.1'
type: development
group: group::package
default_enabled: false
...@@ -226,7 +226,7 @@ Gitaly Cluster consists of multiple components: ...@@ -226,7 +226,7 @@ Gitaly Cluster consists of multiple components:
- [Load balancer](praefect.md#load-balancer) for distributing requests and providing fault-tolerant access to - [Load balancer](praefect.md#load-balancer) for distributing requests and providing fault-tolerant access to
Praefect nodes. Praefect nodes.
- [Praefect](praefect.md#praefect) nodes for managing the cluster and routing requests to Gitaly nodes. - [Praefect](praefect.md#praefect) nodes for managing the cluster and routing requests to Gitaly nodes.
- [PostgreSQL database](praefect.md#postgresql) for persisting cluster metadata and [PgBouncer](praefect.md#pgbouncer), - [PostgreSQL database](praefect.md#postgresql) for persisting cluster metadata and [PgBouncer](praefect.md#use-pgbouncer),
recommended for pooling Praefect's database connections. recommended for pooling Praefect's database connections.
- Gitaly nodes to provide repository storage and Git access. - Gitaly nodes to provide repository storage and Git access.
......
This diff is collapsed.
...@@ -52,6 +52,20 @@ This content has been moved to a [new location](replication_and_failover.md#conf ...@@ -52,6 +52,20 @@ This content has been moved to a [new location](replication_and_failover.md#conf
} }
``` ```
You can pass additional configuration parameters per database, for example:
```ruby
pgbouncer['databases'] = {
gitlabhq_production: {
...
pool_mode: 'transaction'
}
}
```
Use these parameters with caution. For the complete list of parameters refer to the
[PgBouncer documentation](https://www.pgbouncer.org/config.html#section-databases).
1. Run `gitlab-ctl reconfigure` 1. Run `gitlab-ctl reconfigure`
1. On the node running Puma, make sure the following is set in `/etc/gitlab/gitlab.rb` 1. On the node running Puma, make sure the following is set in `/etc/gitlab/gitlab.rb`
......
...@@ -415,6 +415,10 @@ which is used to track vulnerabilities ...@@ -415,6 +415,10 @@ which is used to track vulnerabilities
as new commits are pushed to the repository. as new commits are pushed to the repository.
The attributes used to generate the location fingerprint also depend on the type of scanning. The attributes used to generate the location fingerprint also depend on the type of scanning.
### Details
The `details` field is an object that supports many different content elements that are displayed when viewing vulnerability information. An example of the various data elements can be seen in the [security-reports repository](https://gitlab.com/gitlab-examples/security/security-reports/-/tree/master/samples/details-example).
#### Dependency Scanning #### Dependency Scanning
The `location` of a Dependency Scanning vulnerability is composed of a `dependency` and a `file`. The `location` of a Dependency Scanning vulnerability is composed of a `dependency` and a `file`.
......
...@@ -4,8 +4,12 @@ module Gitlab ...@@ -4,8 +4,12 @@ module Gitlab
module Pagination module Pagination
module Keyset module Keyset
class Iterator class Iterator
def initialize(scope:, use_union_optimization: false) UnsupportedScopeOrder = Class.new(StandardError)
@scope = scope
def initialize(scope:, use_union_optimization: true)
@scope, success = Gitlab::Pagination::Keyset::SimpleOrderBuilder.build(scope)
raise(UnsupportedScopeOrder, 'The order on the scope does not support keyset pagination') unless success
@order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(scope) @order = Gitlab::Pagination::Keyset::Order.extract_keyset_order_object(scope)
@use_union_optimization = use_union_optimization @use_union_optimization = use_union_optimization
end end
......
...@@ -23,7 +23,8 @@ module QA ...@@ -23,7 +23,8 @@ module QA
end end
def add_to_file_content(content) def add_to_file_content(content)
text_area.set content text_area.click
text_area.send_keys(:home, content) # starts in the beginning of the line
text_area.has_text?(content) # wait for changes to take effect text_area.has_text?(content) # wait for changes to take effect
end end
......
...@@ -44,7 +44,13 @@ RSpec.describe 'Group Packages' do ...@@ -44,7 +44,13 @@ RSpec.describe 'Group Packages' do
it_behaves_like 'packages list', check_project_name: true it_behaves_like 'packages list', check_project_name: true
context 'when package_details_apollo feature flag is off' do
before do
stub_feature_flags(package_details_apollo: false)
end
it_behaves_like 'package details link' it_behaves_like 'package details link'
end
it 'allows you to navigate to the project page' do it 'allows you to navigate to the project page' do
find('[data-testid="root-link"]', text: project.name).click find('[data-testid="root-link"]', text: project.name).click
......
...@@ -23,6 +23,11 @@ RSpec.describe 'PackageFiles' do ...@@ -23,6 +23,11 @@ RSpec.describe 'PackageFiles' do
expect(status_code).to eq(200) expect(status_code).to eq(200)
end end
context 'when package_details_apollo feature flag is off' do
before do
stub_feature_flags(package_details_apollo: false)
end
it 'renders the download link with the correct url', :js do it 'renders the download link with the correct url', :js do
visit project_package_path(project, package) visit project_package_path(project, package)
...@@ -30,6 +35,7 @@ RSpec.describe 'PackageFiles' do ...@@ -30,6 +35,7 @@ RSpec.describe 'PackageFiles' do
expect(page).to have_link(package_file.file_name, href: download_url) expect(page).to have_link(package_file.file_name, href: download_url)
end end
end
it 'does not allow download of package belonging to different project' do it 'does not allow download of package belonging to different project' do
another_package = create(:maven_package) another_package = create(:maven_package)
......
...@@ -37,7 +37,13 @@ RSpec.describe 'Packages' do ...@@ -37,7 +37,13 @@ RSpec.describe 'Packages' do
it_behaves_like 'packages list' it_behaves_like 'packages list'
context 'when package_details_apollo feature flag is off' do
before do
stub_feature_flags(package_details_apollo: false)
end
it_behaves_like 'package details link' it_behaves_like 'package details link'
end
context 'deleting a package' do context 'deleting a package' do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
......
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue';
describe('PackagesApp', () => {
let wrapper;
function createComponent() {
wrapper = shallowMount(PackagesApp, {
provide: {
titleComponent: 'titleComponent',
projectName: 'projectName',
canDelete: 'canDelete',
svgPath: 'svgPath',
npmPath: 'npmPath',
npmHelpPath: 'npmHelpPath',
projectListUrl: 'projectListUrl',
groupListUrl: 'groupListUrl',
},
});
}
const emptyState = () => wrapper.findComponent(GlEmptyState);
afterEach(() => {
wrapper.destroy();
});
it('renders an empty state component', () => {
createComponent();
expect(emptyState().exists()).toBe(true);
});
});
...@@ -27,27 +27,31 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -27,27 +27,31 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
), ),
Gitlab::Pagination::Keyset::ColumnOrderDefinition.new( Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
attribute_name: 'id', attribute_name: 'id',
order_expression: klass.arel_table[:id].send(direction), order_expression: klass.arel_table[:id].send(direction)
add_to_projections: true
) )
]) ])
end end
let(:scope) { project.issues.reorder(custom_reorder) } let(:scope) { project.issues.reorder(custom_reorder) }
subject { described_class.new(scope: scope) } shared_examples 'iterator examples' do
describe '.each_batch' do describe '.each_batch' do
it 'yields an ActiveRecord::Relation when a block is given' do it 'yields an ActiveRecord::Relation when a block is given' do
subject.each_batch(of: 1) do |relation| iterator.each_batch(of: 1) do |relation|
expect(relation).to be_a_kind_of(ActiveRecord::Relation) expect(relation).to be_a_kind_of(ActiveRecord::Relation)
end end
end end
it 'raises error when ordering configuration cannot be automatically determined' do
expect do
described_class.new(scope: MergeRequestDiffCommit.order(:merge_request_diff_id, :relative_order))
end.to raise_error /The order on the scope does not support keyset pagination/
end
it 'accepts a custom batch size' do it 'accepts a custom batch size' do
count = 0 count = 0
subject.each_batch(of: 2) { |relation| count += relation.count(:all) } iterator.each_batch(of: 2) { |relation| count += relation.count(:all) }
expect(count).to eq(9) expect(count).to eq(9)
end end
...@@ -55,11 +59,11 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -55,11 +59,11 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
it 'allows updating of the yielded relations' do it 'allows updating of the yielded relations' do
time = Time.current time = Time.current
subject.each_batch(of: 2) do |relation| iterator.each_batch(of: 2) do |relation|
relation.update_all(updated_at: time) Issue.connection.execute("UPDATE issues SET updated_at = '#{time.to_s(:inspect)}' WHERE id IN (#{relation.reselect(:id).to_sql})")
end end
expect(Issue.where(updated_at: time).count).to eq(9) expect(Issue.pluck(:updated_at)).to all(be_within(5.seconds).of(time))
end end
context 'with ordering direction' do context 'with ordering direction' do
...@@ -67,7 +71,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -67,7 +71,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
it 'orders ascending by default, including secondary order column' do it 'orders ascending by default, including secondary order column' do
positions = [] positions = []
subject.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) } iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) }
expect(positions).to eq(project.issues.order_relative_position_asc.order(id: :asc).pluck(:relative_position, :id)) expect(positions).to eq(project.issues.order_relative_position_asc.order(id: :asc).pluck(:relative_position, :id))
end end
...@@ -79,7 +83,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -79,7 +83,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
it 'orders in reverse of ascending' do it 'orders in reverse of ascending' do
positions = [] positions = []
subject.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) } iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) }
expect(positions).to eq(project.issues.order_relative_position_desc.order(id: :desc).pluck(:relative_position, :id)) expect(positions).to eq(project.issues.order_relative_position_desc.order(id: :desc).pluck(:relative_position, :id))
end end
...@@ -91,7 +95,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -91,7 +95,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
it 'orders ascending with nulls first' do it 'orders ascending with nulls first' do
positions = [] positions = []
subject.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) } iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) }
expect(positions).to eq(project.issues.reorder(::Gitlab::Database.nulls_first_order('relative_position', 'ASC')).order(id: :asc).pluck(:relative_position, :id)) expect(positions).to eq(project.issues.reorder(::Gitlab::Database.nulls_first_order('relative_position', 'ASC')).order(id: :asc).pluck(:relative_position, :id))
end end
...@@ -104,7 +108,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -104,7 +108,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
it 'orders descending' do it 'orders descending' do
positions = [] positions = []
subject.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) } iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:relative_position, :id)) }
expect(positions).to eq(project.issues.reorder(::Gitlab::Database.nulls_last_order('relative_position', 'DESC')).order(id: :desc).pluck(:relative_position, :id)) expect(positions).to eq(project.issues.reorder(::Gitlab::Database.nulls_last_order('relative_position', 'DESC')).order(id: :desc).pluck(:relative_position, :id))
end end
...@@ -117,11 +121,24 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do ...@@ -117,11 +121,24 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do
it 'orders descending' do it 'orders descending' do
positions = [] positions = []
subject.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:id)) } iterator.each_batch(of: 2) { |rel| positions.concat(rel.pluck(:id)) }
expect(positions).to eq(project.issues.reorder(id: :desc).pluck(:id)) expect(positions).to eq(project.issues.reorder(id: :desc).pluck(:id))
end end
end end
end end
end end
end
context 'when use_union_optimization is used' do
subject(:iterator) { described_class.new(scope: scope, use_union_optimization: true) }
include_examples 'iterator examples'
end
context 'when use_union_optimization is not used' do
subject(:iterator) { described_class.new(scope: scope, use_union_optimization: false) }
include_examples 'iterator examples'
end
end end
...@@ -201,11 +201,10 @@ RSpec.describe WikiPage do ...@@ -201,11 +201,10 @@ RSpec.describe WikiPage do
expect(subject.errors.messages).to eq(title: ["can't be blank"]) expect(subject.errors.messages).to eq(title: ["can't be blank"])
end end
it "validates presence of content" do it "does not validate presence of content" do
subject.attributes.delete(:content) subject.attributes.delete(:content)
expect(subject).not_to be_valid expect(subject).to be_valid
expect(subject.errors.messages).to eq(content: ["can't be blank"])
end end
describe '#validate_content_size_limit' do describe '#validate_content_size_limit' do
......
...@@ -20,17 +20,6 @@ RSpec.shared_examples 'User creates wiki page' do ...@@ -20,17 +20,6 @@ RSpec.shared_examples 'User creates wiki page' do
click_link "Create your first page" click_link "Create your first page"
end end
it "shows validation error message if the form is force submitted", :js do
page.within(".wiki-form") do
fill_in(:wiki_content, with: "")
page.execute_script("document.querySelector('.wiki-form').submit()")
page.accept_alert # manually force form submit
end
expect(page).to have_content("The form contains the following error:").and have_content("Content can't be blank")
end
it "disables the submit button", :js do it "disables the submit button", :js do
page.within(".wiki-form") do page.within(".wiki-form") do
fill_in(:wiki_content, with: "") fill_in(:wiki_content, with: "")
......
...@@ -90,19 +90,6 @@ RSpec.shared_examples 'User updates wiki page' do ...@@ -90,19 +90,6 @@ RSpec.shared_examples 'User updates wiki page' do
expect(page).to have_field('wiki[message]', with: 'Update Wiki title') expect(page).to have_field('wiki[message]', with: 'Update Wiki title')
end end
it 'shows a validation error message if the form is force submitted', :js do
fill_in(:wiki_content, with: '')
page.execute_script("document.querySelector('.wiki-form').submit()")
page.accept_alert # manually force form submit
expect(page).to have_selector('.wiki-form')
expect(page).to have_content('Edit Page')
expect(page).to have_content('The form contains the following error:')
expect(page).to have_content("Content can't be blank")
expect(find('textarea#wiki_content').value).to eq('')
end
it "disables the submit button", :js do it "disables the submit button", :js do
page.within(".wiki-form") do page.within(".wiki-form") do
fill_in(:wiki_content, with: "") fill_in(:wiki_content, with: "")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment