+
### CiRunner.projects default sort is changing to `id_desc`
diff --git a/doc/user/compliance/compliance_center/index.md b/doc/user/compliance/compliance_center/index.md
index 6bc6be8ddb6..4a42a70a7e7 100644
--- a/doc/user/compliance/compliance_center/index.md
+++ b/doc/user/compliance/compliance_center/index.md
@@ -111,9 +111,9 @@ You can sort the compliance report on:
You can filter the compliance violations report on:
-- Project.
-- Date range of merge.
-- Target branch.
+- The project that the violation was found on.
+- The date range of violation.
+- The target branch of the violation.
Select a row to see details of the compliance violation.
diff --git a/doc/user/packages/container_registry/troubleshoot_container_registry.md b/doc/user/packages/container_registry/troubleshoot_container_registry.md
index acef00640ef..cb0bcf3f35b 100644
--- a/doc/user/packages/container_registry/troubleshoot_container_registry.md
+++ b/doc/user/packages/container_registry/troubleshoot_container_registry.md
@@ -145,3 +145,24 @@ This is typically a result of [a performance issue with `kaniko` and HTTP/2](htt
The current workaround is to use HTTP/1.1 when pushing with `kaniko`.
To use HTTP/1.1, set the `GODEBUG` environment variable to `"http2client=0"`.
+
+## `docker login` command fails with `access forbidden`
+
+The container registry [returns the GitLab API URL to the Docker client](../../../administration/packages/container_registry.md#architecture-of-gitlab-container-registry)
+to validate credentials. The Docker client uses basic auth, so the request contains
+the `Authorization` header. If the `Authorization` header is missing in the request to the
+`/jwt/auth` endpoint configured in the `token_realm` for the registry configuration,
+you receive an `access forbidden` error message.
+
+For example:
+
+```plaintext
+> docker login gitlab.example.com:4567
+
+Username: user
+Password:
+Error response from daemon: Get "https://gitlab.company.com:4567/v2/": denied: access forbidden
+```
+
+To avoid this error, ensure the `Authorization` header is not stripped from the request.
+For example, a proxy in front of GitLab might be redirecting to the `/jwt/auth` endpoint.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c57f178a662..31facc5689b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -54564,6 +54564,9 @@ msgstr ""
msgid "WorkItem|Select type"
msgstr ""
+msgid "WorkItem|Show labels"
+msgstr ""
+
msgid "WorkItem|Someone edited the description at the same time you did. If you save it will overwrite their changes. Please confirm you'd like to save your edits."
msgstr ""
diff --git a/package.json b/package.json
index ff2cfdb2620..e7484d71612 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/svgs": "3.69.0",
- "@gitlab/ui": "68.0.0",
+ "@gitlab/ui": "68.1.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20231004090414",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
diff --git a/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js b/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
index 15c6930cc80..a40e860d9fe 100644
--- a/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
+++ b/spec/frontend/work_items/components/shared/work_item_link_child_contents_spec.js
@@ -40,11 +40,16 @@ describe('WorkItemLinkChildContents', () => {
const findScopedLabel = () => findAllLabels().at(1);
const findRemoveButton = () => wrapper.findComponent(GlButton);
- const createComponent = ({ canUpdate = true, childItem = workItemTask } = {}) => {
+ const createComponent = ({
+ canUpdate = true,
+ childItem = workItemTask,
+ showLabels = true,
+ } = {}) => {
wrapper = shallowMountExtended(WorkItemLinkChildContents, {
propsData: {
canUpdate,
childItem,
+ showLabels,
},
});
};
@@ -128,19 +133,6 @@ describe('WorkItemLinkChildContents', () => {
expect(findMetadataComponent().exists()).toBe(false);
});
-
- it('renders labels', () => {
- const mockLabel = mockLabels[0];
-
- expect(findAllLabels()).toHaveLength(mockLabels.length);
- expect(findRegularLabel().props()).toMatchObject({
- title: mockLabel.title,
- backgroundColor: mockLabel.color,
- description: mockLabel.description,
- scoped: false,
- });
- expect(findScopedLabel().props('scoped')).toBe(true); // Second label is scoped
- });
});
describe('item menu', () => {
@@ -164,4 +156,31 @@ describe('WorkItemLinkChildContents', () => {
expect(wrapper.emitted('removeChild')).toEqual([[workItemTask]]);
});
});
+
+ describe('item labels', () => {
+ it('renders normal and scoped label', () => {
+ createComponent({ childItem: workItemObjectiveWithChild });
+
+ const mockLabel = mockLabels[0];
+
+ expect(findAllLabels()).toHaveLength(mockLabels.length);
+ expect(findRegularLabel().props()).toMatchObject({
+ title: mockLabel.title,
+ backgroundColor: mockLabel.color,
+ description: mockLabel.description,
+ scoped: false,
+ });
+ expect(findScopedLabel().props('scoped')).toBe(true); // Second label is scoped
+ });
+
+ it.each`
+ expectedAssertion | showLabels
+ ${'does not render labels'} | ${true}
+ ${'renders label'} | ${false}
+ `('$expectedAssertion when showLabels is $showLabels', ({ showLabels }) => {
+ createComponent({ showLabels, childItem: workItemObjectiveWithChild });
+
+ expect(findAllLabels().exists()).toBe(showLabels);
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
index 9addf6c3450..36af0c5b3c8 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
@@ -91,6 +91,7 @@ describe('WorkItemLinkChild', () => {
childItem: workItemObjectiveWithChild,
canUpdate: true,
showTaskIcon: false,
+ showLabels: true,
});
});
});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
index 0b88b3ff5b4..f8b2736c0f8 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
@@ -1,5 +1,6 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
+import { GlToggle } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -93,6 +94,7 @@ describe('WorkItemLinks', () => {
const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
const findAbuseCategorySelector = () => wrapper.findComponent(AbuseCategorySelector);
const findWorkItemLinkChildrenWrapper = () => wrapper.findComponent(WorkItemChildrenWrapper);
+ const findShowLabelsToggle = () => wrapper.findComponent(GlToggle);
afterEach(() => {
mockApollo = null;
@@ -278,4 +280,21 @@ describe('WorkItemLinks', () => {
expect(groupResponseWithAddChildPermission).toHaveBeenCalled();
});
});
+
+ it.each`
+ toggleValue
+ ${true}
+ ${false}
+ `(
+ 'passes showLabels as $toggleValue to child items when toggle is $toggleValue',
+ async ({ toggleValue }) => {
+ await createComponent();
+
+ findShowLabelsToggle().vm.$emit('change', toggleValue);
+
+ await nextTick();
+
+ expect(findWorkItemLinkChildrenWrapper().props('showLabels')).toBe(toggleValue);
+ },
+ );
});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
index f30fded0b45..6c1d1035c3d 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
@@ -1,4 +1,5 @@
import { nextTick } from 'vue';
+import { GlToggle } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import WidgetWrapper from '~/work_items/components/widget_wrapper.vue';
import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
@@ -20,6 +21,7 @@ describe('WorkItemTree', () => {
const findForm = () => wrapper.findComponent(WorkItemLinksForm);
const findWidgetWrapper = () => wrapper.findComponent(WidgetWrapper);
const findWorkItemLinkChildrenWrapper = () => wrapper.findComponent(WorkItemChildrenWrapper);
+ const findShowLabelsToggle = () => wrapper.findComponent(GlToggle);
const createComponent = ({
workItemType = 'Objective',
@@ -126,4 +128,21 @@ describe('WorkItemTree', () => {
expect(wrapper.emitted('addChild')).toEqual([[]]);
});
+
+ it.each`
+ toggleValue
+ ${true}
+ ${false}
+ `(
+ 'passes showLabels as $toggleValue to child items when toggle is $toggleValue',
+ async ({ toggleValue }) => {
+ createComponent();
+
+ findShowLabelsToggle().vm.$emit('change', toggleValue);
+
+ await nextTick();
+
+ expect(findWorkItemLinkChildrenWrapper().props('showLabels')).toBe(toggleValue);
+ },
+ );
});
diff --git a/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap b/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap
index bbc19a011a5..20af8584e37 100644
--- a/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap
+++ b/spec/frontend/work_items/components/work_item_relationships/__snapshots__/work_item_relationship_list_spec.js.snap
@@ -22,6 +22,7 @@ exports[`WorkItemRelationshipList renders linked item list 1`] = `
diff --git a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
index 7178fa1aae7..0faea0e4862 100644
--- a/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
+++ b/spec/frontend/work_items/components/work_item_relationships/work_item_relationships_spec.js
@@ -1,6 +1,6 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlToggle } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -82,6 +82,7 @@ describe('WorkItemRelationships', () => {
wrapper.findAllComponents(WorkItemRelationshipList);
const findAddButton = () => wrapper.findByTestId('link-item-add-button');
const findWorkItemRelationshipForm = () => wrapper.findComponent(WorkItemAddRelationshipForm);
+ const findShowLabelsToggle = () => wrapper.findComponent(GlToggle);
it('shows loading icon when query is not processed', () => {
createComponent();
@@ -99,6 +100,11 @@ describe('WorkItemRelationships', () => {
expect(findLinkedItemsHelpLink().attributes('href')).toBe(
'/help/user/okrs.md#linked-items-in-okrs',
);
+ expect(findShowLabelsToggle().props()).toMatchObject({
+ value: true,
+ labelPosition: 'left',
+ label: 'Show labels',
+ });
});
it('renders blocking linked item lists', async () => {
@@ -153,6 +159,29 @@ describe('WorkItemRelationships', () => {
expect(findWorkItemRelationshipForm().exists()).toBe(false);
});
+ it.each`
+ toggleValue
+ ${true}
+ ${false}
+ `(
+ 'passes showLabels as $toggleValue to child items when toggle is $toggleValue',
+ async ({ toggleValue }) => {
+ await createComponent({
+ workItemQueryHandler: jest
+ .fn()
+ .mockResolvedValue(workItemByIidResponseFactory({ linkedItems: mockLinkedItems })),
+ });
+
+ findShowLabelsToggle().vm.$emit('change', toggleValue);
+
+ await nextTick();
+
+ expect(findAllWorkItemRelationshipListComponents().at(0).props('showLabels')).toBe(
+ toggleValue,
+ );
+ },
+ );
+
describe('when project context', () => {
it('calls the project work item query', () => {
createComponent();
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index 4c7c56631fc..2b4cd3736c0 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe 'cross-database foreign keys' do
'issues.closed_by_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422154
'issues.updated_by_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422154
'issue_assignees.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422154
+ 'lfs_file_locks.user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/430838
'merge_requests.assignee_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
'merge_requests.updated_by_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
'merge_requests.merge_user_id', # https://gitlab.com/gitlab-org/gitlab/-/issues/422080
diff --git a/spec/migrations/20231102142554_migrate_zoekt_shards_to_zoekt_nodes_spec.rb b/spec/migrations/20231102142554_migrate_zoekt_shards_to_zoekt_nodes_spec.rb
new file mode 100644
index 00000000000..5f1d691f923
--- /dev/null
+++ b/spec/migrations/20231102142554_migrate_zoekt_shards_to_zoekt_nodes_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe MigrateZoektShardsToZoektNodes, feature_category: :global_search do
+ let!(:migration) { described_class.new }
+
+ let(:attributes) do
+ {
+ index_base_url: "https://index.example.com",
+ search_base_url: "https://search.example.com",
+ uuid: SecureRandom.uuid,
+ used_bytes: 10,
+ total_bytes: 100
+ }.with_indifferent_access
+ end
+
+ let(:zoekt_shards) { table(:zoekt_shards) }
+ let(:zoekt_nodes) { table(:zoekt_nodes) }
+
+ let(:shard) do
+ zoekt_shards.create!(attributes)
+ end
+
+ let(:node) do
+ zoekt_nodes.create!(attributes)
+ end
+
+ describe '#up' do
+ it 'migrates zoekt_shard records to zoekt_nodes' do
+ shard
+ expect { migrate! }.to change { zoekt_nodes.count }.from(0).to(1)
+ expect(zoekt_nodes.first.attributes.with_indifferent_access).to include(attributes)
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all zoekt_node records' do
+ node
+ expect { migration.down }.to change { zoekt_nodes.count }.from(1).to(0)
+ end
+ end
+end
diff --git a/spec/migrations/20231103223224_backfill_zoekt_node_id_on_indexed_namespaces_spec.rb b/spec/migrations/20231103223224_backfill_zoekt_node_id_on_indexed_namespaces_spec.rb
new file mode 100644
index 00000000000..60f08071af6
--- /dev/null
+++ b/spec/migrations/20231103223224_backfill_zoekt_node_id_on_indexed_namespaces_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe BackfillZoektNodeIdOnIndexedNamespaces, feature_category: :global_search do
+ let!(:migration) { described_class.new }
+
+ let(:namespace) { table(:namespaces).create!(name: 'name', path: 'path') }
+
+ let(:zoekt_indexed_namespaces) { table(:zoekt_indexed_namespaces) }
+ let(:zoekt_shards) { table(:zoekt_shards) }
+ let(:zoekt_nodes) { table(:zoekt_nodes) }
+
+ let(:indexed_namespace) do
+ zoekt_indexed_namespaces.create!(
+ zoekt_shard_id: shard.id,
+ namespace_id: namespace.id
+ )
+ end
+
+ let(:attributes) do
+ {
+ index_base_url: "https://index.example.com",
+ search_base_url: "https://search.example.com",
+ uuid: SecureRandom.uuid,
+ used_bytes: 10,
+ total_bytes: 100
+ }.with_indifferent_access
+ end
+
+ let(:shard) do
+ zoekt_shards.create!(attributes)
+ end
+
+ let(:node) do
+ zoekt_nodes.create!(attributes)
+ end
+
+ describe '#up' do
+ it 'backfills zoekt_node_id with zoekt_shard_id' do
+ node
+ expect(indexed_namespace.zoekt_node_id).to be_nil
+ expect(indexed_namespace.zoekt_shard_id).to eq(shard.id)
+ migrate!
+ expect(indexed_namespace.reload.zoekt_node_id).to eq(node.id)
+ end
+
+ context 'when there is somehow more than one zoekt node' do
+ let(:node) do
+ zoekt_nodes.create!(
+ index_base_url: "https://index.example.com",
+ search_base_url: "https://search.example.com",
+ uuid: SecureRandom.uuid,
+ used_bytes: 10,
+ total_bytes: 100,
+ created_at: 5.days.ago
+ )
+ end
+
+ let(:node_2) do
+ zoekt_nodes.create!(
+ index_base_url: "https://index2.example.com",
+ search_base_url: "https://search2example.com",
+ uuid: SecureRandom.uuid,
+ used_bytes: 10,
+ total_bytes: 100
+ )
+ end
+
+ it 'uses the latest zoekt node' do
+ expect(node_2.created_at).to be > node.created_at
+ expect(indexed_namespace.zoekt_node_id).to be_nil
+ migrate!
+ expect(indexed_namespace.reload.zoekt_node_id).to eq(node_2.id)
+ end
+ end
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 59701f52f28..f2f79311fdf 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -162,6 +162,8 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
it { is_expected.to validate_inclusion_of(:user_defaults_to_private_profile).in_array([true, false]) }
+ it { is_expected.to validate_inclusion_of(:allow_project_creation_for_guest_and_below).in_array([true, false]) }
+
it { is_expected.to validate_inclusion_of(:deny_all_requests_except_allowed).in_array([true, false]) }
it 'ensures max_pages_size is an integer greater than 0 (or equal to 0 to indicate unlimited/maximum)' do
diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb
index 01674e941d5..30605c81312 100644
--- a/spec/support/shared_examples/features/work_items_shared_examples.rb
+++ b/spec/support/shared_examples/features/work_items_shared_examples.rb
@@ -399,11 +399,14 @@ RSpec.shared_examples 'work items notifications' do
it 'displays toast when notification is toggled' do
click_button _('More actions'), match: :first
- expect(page).not_to have_button(class: 'gl-toggle is-checked')
+ within_testid('notifications-toggle-form') do
+ expect(page).not_to have_button(class: 'gl-toggle is-checked')
- click_button(class: 'gl-toggle')
+ click_button(class: 'gl-toggle')
+
+ expect(page).to have_button(class: 'gl-toggle is-checked')
+ end
- expect(page).to have_button(class: 'gl-toggle is-checked')
expect(page).to have_css('.gl-toast', text: _('Notifications turned on.'))
end
end
diff --git a/yarn.lock b/yarn.lock
index 0c6973456bb..7573404abbb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1274,10 +1274,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.69.0.tgz#bf76b8ffbe72a783807761a38abe8aaedcfe8c12"
integrity sha512-Zu8Fcjhi3Bk26jZOptcD5F4SHWC7/KuAe00NULViCeswKdoda1k19B+9oCSbsbxY7vMoFuD20kiCJdBCpxb3HA==
-"@gitlab/ui@68.0.0":
- version "68.0.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-68.0.0.tgz#06228d889a1a49215b0d715b4b3dd7e583a39ff2"
- integrity sha512-El0BEBqil9hqsLSHl0AuhSikYaeNcg3M6adKti/lkKn0KbTKSl48CsJy/sObQY0khuu70+OoQFeuF85KwWnxEw==
+"@gitlab/ui@68.1.0":
+ version "68.1.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-68.1.0.tgz#9a050b3c9460875debf8ba71d06bb7ea7fe3350d"
+ integrity sha512-4+xhEYSGq59fdFJclAz1t3zBoVBmi0ZZD8EfCBkhHESKZamUt/5G6cEouB5jQ9NOlp4BkQeEv80e09fJ145rkQ==
dependencies:
"@floating-ui/dom" "1.2.9"
bootstrap-vue "2.23.1"