diff --git a/app/assets/stylesheets/page_bundles/pipeline.scss b/app/assets/stylesheets/page_bundles/pipeline.scss
index d6ed68a5550..0180fde1efd 100644
--- a/app/assets/stylesheets/page_bundles/pipeline.scss
+++ b/app/assets/stylesheets/page_bundles/pipeline.scss
@@ -294,21 +294,13 @@
}
}
-.stage-column .ci-job-group-dropdown {
+.stage-column {
// stylelint-disable-next-line gitlab/no-gl-class
&,
.gl-new-dropdown-custom-toggle {
width: 100%;
}
- // Reset padding, as inner element will
- // define padding
- // stylelint-disable-next-line gitlab/no-gl-class
- .gl-new-dropdown-item-content,
- .gl-new-dropdown-item-text-wrapper {
- padding: 0;
- }
-
// Set artificial focus on the menu-item to keep
// it consistent with the original dropdown items
// stylelint-disable-next-line gitlab/no-gl-class
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 37965d5375d..2e283edd3b5 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -50,6 +50,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:issues_list_drawer, project)
push_frontend_feature_flag(:notifications_todos_buttons, current_user)
push_force_frontend_feature_flag(:glql_integration, project&.glql_integration_feature_flag_enabled?)
+ push_force_frontend_feature_flag(:work_items_beta, project&.work_items_beta_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_alpha, project&.work_items_alpha_feature_flag_enabled?)
end
@@ -63,7 +64,6 @@ class Projects::IssuesController < Projects::ApplicationController
before_action only: :show do
push_frontend_feature_flag(:work_items_beta, project&.group)
- push_force_frontend_feature_flag(:work_items_beta, project&.work_items_beta_feature_flag_enabled?)
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)
push_frontend_feature_flag(:namespace_level_work_items, project&.group)
push_frontend_feature_flag(:work_items_view_preference, current_user)
diff --git a/app/graphql/types/permission_types/issue.rb b/app/graphql/types/permission_types/issue.rb
index db96a1531ec..780599aec34 100644
--- a/app/graphql/types/permission_types/issue.rb
+++ b/app/graphql/types/permission_types/issue.rb
@@ -8,7 +8,8 @@ module Types
abilities :read_issue, :admin_issue, :update_issue, :reopen_issue,
:read_design, :create_design, :destroy_design,
- :create_note, :update_design, :admin_issue_relation
+ :create_note, :update_design, :move_design,
+ :admin_issue_relation
end
end
end
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index 8854e10945b..33cda684b3d 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -18,7 +18,7 @@ module Types
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
:create_pages, :destroy_pages, :read_pages_content, :admin_operations,
:read_merge_request, :read_design, :create_design, :update_design, :destroy_design,
- :read_environment, :view_edit_page
+ :move_design, :read_environment, :view_edit_page
permission_field :create_snippet
diff --git a/app/helpers/access_tokens_helper.rb b/app/helpers/access_tokens_helper.rb
index 992b50cde12..ffac07244fe 100644
--- a/app/helpers/access_tokens_helper.rb
+++ b/app/helpers/access_tokens_helper.rb
@@ -36,12 +36,19 @@ module AccessTokensHelper
end
def expires_at_field_data
- return {} unless Gitlab::CurrentSettings.require_personal_access_token_expiry?
-
{
- min_date: 1.day.from_now.iso8601
+ min_date: 1.day.from_now.iso8601,
+ max_date: max_date_allowed
}
end
+
+ private
+
+ def max_date_allowed
+ return unless Gitlab::CurrentSettings.require_personal_access_token_expiry?
+
+ ::PersonalAccessToken.max_expiration_lifetime_in_days.days.from_now.iso8601
+ end
end
AccessTokensHelper.prepend_mod
diff --git a/doc/.vale/gitlab_base/SubstitutionWarning.yml b/doc/.vale/gitlab_base/SubstitutionWarning.yml
index 3240ea9f49b..0064edefa04 100644
--- a/doc/.vale/gitlab_base/SubstitutionWarning.yml
+++ b/doc/.vale/gitlab_base/SubstitutionWarning.yml
@@ -66,14 +66,17 @@ swap:
pop-up: "dialog"
popup: "dialog"
repo: "repository"
+ root group: "top-level group"
signed in user: "authenticated user"
signed-in user: "authenticated user"
since: "because' or 'after"
source (?:install|installation): self-compiled installation
source (?:installs|installations): self-compiled installations
+ sub group: "subgroup"
sub-group: "subgroup"
sub-groups: "subgroups"
timezone: "time zone"
+ top level group: "top-level group"
utiliz(?:es?|ing): "use"
VSCode: "VS Code"
we recommend: "you should"
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 9227f6b2583..27b6ec6244f 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -27147,6 +27147,7 @@ Check permissions for the current user on a issue.
|
`createDesign` | [`Boolean!`](#boolean) | If `true`, the user can perform `create_design` on this resource. |
|
`createNote` | [`Boolean!`](#boolean) | If `true`, the user can perform `create_note` on this resource. |
|
`destroyDesign` | [`Boolean!`](#boolean) | If `true`, the user can perform `destroy_design` on this resource. |
+|
`moveDesign` | [`Boolean!`](#boolean) | If `true`, the user can perform `move_design` on this resource. |
|
`readDesign` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_design` on this resource. |
|
`readIssue` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_issue` on this resource. |
|
`reopenIssue` | [`Boolean!`](#boolean) | If `true`, the user can perform `reopen_issue` on this resource. |
@@ -33719,6 +33720,7 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
|
`downloadCode` | [`Boolean!`](#boolean) | If `true`, the user can perform `download_code` on this resource. |
|
`downloadWikiCode` | [`Boolean!`](#boolean) | If `true`, the user can perform `download_wiki_code` on this resource. |
|
`forkProject` | [`Boolean!`](#boolean) | If `true`, the user can perform `fork_project` on this resource. |
+|
`moveDesign` | [`Boolean!`](#boolean) | If `true`, the user can perform `move_design` on this resource. |
|
`pushCode` | [`Boolean!`](#boolean) | If `true`, the user can perform `push_code` on this resource. |
|
`pushToDeleteProtectedBranch` | [`Boolean!`](#boolean) | If `true`, the user can perform `push_to_delete_protected_branch` on this resource. |
|
`readCommitStatus` | [`Boolean!`](#boolean) | If `true`, the user can perform `read_commit_status` on this resource. |
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index e75ba0b3e20..88f3882483f 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -1378,6 +1378,13 @@ Do not use:
- GitLab Runner Kubernetes executor, because this can infringe on the Kubernetes trademark.
+## language model, large language model
+
+When referring to language models, be precise. Not all language models are large,
+and not all models are language models. When in doubt, ask a developer or PM for confirmation.
+
+You can use LLM to refer to a large language model if you spell it out on first use.
+
## later
Use **later** when talking about version numbers.
@@ -1615,6 +1622,10 @@ Use:
- The GitLab model registry supports A, B, and C.
- You can publish a model to your project's model registry.
+## models
+
+For usage, see [language models](#language-model-large-language-model).
+
## n/a, N/A, not applicable
When possible, use **not applicable**. Spelling out the phrase helps non-English speaking users and avoids
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index ef3ac9dcd78..8f0b14778d4 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -708,6 +708,28 @@ Google Workspace Administrator also provides the IdP metadata, Entity ID, and SH
fingerprint. However, GitLab does not need this information to connect to the
Google Workspace SAML application.
+### Set up Microsoft Entra ID
+
+1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/).
+1. [Create a non-gallery application](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/overview-application-gallery#create-your-own-application).
+1. [Configure SSO for that application](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/add-application-portal-setup-sso).
+
+ The following settings in your `gitlab.rb` file correspond to the Microsoft Entra ID fields:
+
+ | `gitlab.rb` setting | Microsoft Entra ID field |
+ | ------------------------------------| ---------------------------------------------- |
+ | `issuer` | **Identifier (Entity ID)** |
+ | `assertion_consumer_service_url` | **Reply URL (Assertion Consumer Service URL)** |
+ | `idp_sso_target_url` | **Login URL** |
+ | `idp_cert_fingerprint` | **Thumbprint** |
+
+1. Set the following attributes:
+ - **Unique User Identifier (Name ID)** to `user.objectID`.
+ - **Name identifier format** to `persistent`. For more information, see how to [manage user SAML identity](../user/group/saml_sso/index.md#manage-user-saml-identity).
+ - **Additional claims** to [supported attributes](#configure-assertions).
+
+For more information, see an [example configuration page](../user/group/saml_sso/example_saml_config.md#azure-active-directory).
+
### Set up other IdPs
Some IdPs have documentation on how to use them as the IdP in SAML configurations.
@@ -3222,6 +3244,12 @@ such as the following:
For example configurations, see the [notes on specific providers](#set-up-identity-providers).
+## Configure SAML with Geo
+
+To configure Geo with SAML, see [Configuring instance-wide SAML](../administration/geo/replication/single_sign_on.md#configuring-instance-wide-saml).
+
+For more information, see [Geo with Single Sign On (SSO)](../administration/geo/replication/single_sign_on.md).
+
## Glossary
| Term | Description |
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 790c17e918a..81598258c6d 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -120,17 +120,13 @@ module QA
def has_skipped_job_in_group?
within_element('disclosure-content') do
- all_elements('ci-job-item', minimum: 1).all? do
- has_selector?('.ci-status-icon-skipped')
- end
+ has_selector?('[aria-label="Status: Skipped"]')
end
end
def has_no_skipped_job_in_group?
within_element('disclosure-content') do
- all_elements('ci-job-item', minimum: 1).all? do
- has_no_selector?('.ci-status-icon-skipped')
- end
+ has_no_selector?('[aria-label="Status: Skipped"]')
end
end
diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt
index e39fc7fa330..8a5a62ae918 100644
--- a/scripts/frontend/quarantined_vue3_specs.txt
+++ b/scripts/frontend/quarantined_vue3_specs.txt
@@ -132,7 +132,6 @@ spec/frontend/ci/job_details/components/log/line_header_spec.js
spec/frontend/ci/job_details/components/log/line_spec.js
spec/frontend/ci/job_details/components/log/log_spec.js
spec/frontend/ci/job_details/components/sidebar/job_sidebar_retry_button_spec.js
-spec/frontend/ci/pipeline_details/graph/components/job_group_dropdown_spec.js
spec/frontend/ci/pipeline_details/header/components/header_badges_spec.js
spec/frontend/ci/pipeline_editor/components/header/validation_segment_spec.js
spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/rules_item_spec.js
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 4ece24e14e5..9bfe6201fc3 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -617,7 +617,7 @@ RSpec.describe 'Pipelines', :js, feature_category: :continuous_integration do
wait_for_requests
- within first('[data-testid="job-item"]') do
+ within first('[data-testid="ci-job-item"]') do
expect(find_by_testid('play-icon')).to be_visible
end
diff --git a/spec/frontend/ci/pipeline_details/graph/components/job_group_dropdown_spec.js b/spec/frontend/ci/pipeline_details/graph/components/job_group_dropdown_spec.js
index 3885841e2d8..299f343ff0e 100644
--- a/spec/frontend/ci/pipeline_details/graph/components/job_group_dropdown_spec.js
+++ b/spec/frontend/ci/pipeline_details/graph/components/job_group_dropdown_spec.js
@@ -1,9 +1,9 @@
import { shallowMount, mount } from '@vue/test-utils';
-import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui';
+import { GlDisclosureDropdown } from '@gitlab/ui';
+import PipelineMiniGraphJobItem from '~/ci/pipeline_mini_graph/job_item.vue';
import JobGroupDropdown from '~/ci/pipeline_details/graph/components/job_group_dropdown.vue';
import JobItem from '~/ci/pipeline_details/graph/components/job_item.vue';
-import { SINGLE_JOB } from '~/ci/pipeline_details/graph/constants';
describe('job group dropdown component', () => {
const group = {
@@ -70,7 +70,7 @@ describe('job group dropdown component', () => {
const findJobItem = () => wrapper.findComponent(JobItem);
const findTriggerButton = () => wrapper.find('button');
const findDisclosureDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
- const findDisclosureDropdownItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem);
+ const findPipelineMiniGraphJobItems = () => wrapper.findAllComponents(PipelineMiniGraphJobItem);
const createComponent = ({ props, mountFn = shallowMount } = {}) => {
wrapper = mountFn(JobGroupDropdown, {
@@ -81,14 +81,6 @@ describe('job group dropdown component', () => {
});
};
- it('renders dropdown with jobs', () => {
- createComponent({ mountFn: mount });
-
- expect(wrapper.findAll('[data-testid="disclosure-content"] > li').length).toBe(
- group.jobs.length,
- );
- });
-
it('renders dropdown', () => {
createComponent();
@@ -125,31 +117,12 @@ describe('job group dropdown component', () => {
it('renders parallel jobs in group', () => {
createComponent({ mountFn: mount });
- const [item1, item2] = findDisclosureDropdownItems().wrappers;
+ const [item1, item2] = findPipelineMiniGraphJobItems().wrappers;
- expect(findDisclosureDropdownItems()).toHaveLength(2);
+ expect(findPipelineMiniGraphJobItems()).toHaveLength(2);
- expect(item1.props('item')).toEqual({
- text: group.jobs[0].name,
- href: group.jobs[0].status.detailsPath,
- });
- expect(item1.findComponent(JobItem).props()).toMatchObject({
- isLink: false,
- job: group.jobs[0],
- type: SINGLE_JOB,
- cssClassJobName: 'gl-p-3',
- });
-
- expect(item2.props('item')).toEqual({
- text: group.jobs[1].name,
- href: group.jobs[1].status.detailsPath,
- });
- expect(item2.findComponent(JobItem).props()).toMatchObject({
- isLink: false,
- job: group.jobs[1],
- type: SINGLE_JOB,
- cssClassJobName: 'gl-p-3',
- });
+ expect(item1.props('job')).toEqual(group.jobs[0]);
+ expect(item2.props('job')).toEqual(group.jobs[1]);
});
describe('tooltip', () => {
diff --git a/spec/frontend/ci/pipeline_mini_graph/job_item_spec.js b/spec/frontend/ci/pipeline_mini_graph/job_item_spec.js
index 0e2be268cdb..8fb87303c6f 100644
--- a/spec/frontend/ci/pipeline_mini_graph/job_item_spec.js
+++ b/spec/frontend/ci/pipeline_mini_graph/job_item_spec.js
@@ -6,17 +6,25 @@ import JobNameComponent from '~/ci/common/private/job_name_component.vue';
import { mockPipelineJob } from './mock_data';
+const { detailedStatus, ...mockJobInfo } = mockPipelineJob;
+
+const mockJobDetailedStatus = {
+ ...mockJobInfo,
+ detailedStatus,
+};
+
+const mockJobStatus = {
+ ...mockJobInfo,
+ status: detailedStatus,
+};
+
describe('JobItem', () => {
let wrapper;
- const defaultProps = {
- job: mockPipelineJob,
- };
-
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(JobItem, {
propsData: {
- ...defaultProps,
+ job: mockPipelineJob,
...props,
},
});
@@ -25,9 +33,12 @@ describe('JobItem', () => {
const findJobNameComponent = () => wrapper.findComponent(JobNameComponent);
const findJobActionButton = () => wrapper.findComponent(JobActionButton);
- describe('when mounted', () => {
+ describe.each([
+ ['has detailedStatus', mockJobDetailedStatus],
+ ['has status', mockJobStatus],
+ ])('when job contains "%s"', (_, job) => {
beforeEach(() => {
- createComponent();
+ createComponent({ props: { job } });
});
describe('job name', () => {
@@ -37,13 +48,13 @@ describe('JobItem', () => {
it('sends the necessary props to the job name component', () => {
expect(findJobNameComponent().props()).toMatchObject({
- name: mockPipelineJob.name,
- status: mockPipelineJob.detailedStatus,
+ name: mockJobInfo.name,
+ status: detailedStatus,
});
});
it('sets the correct tooltip for the job item', () => {
- const tooltip = capitalizeFirstCharacter(mockPipelineJob.detailedStatus.tooltip);
+ const tooltip = capitalizeFirstCharacter(detailedStatus.tooltip);
expect(findJobNameComponent().attributes('title')).toBe(tooltip);
});
@@ -57,9 +68,9 @@ describe('JobItem', () => {
it('sends the necessary props to the job action button', () => {
expect(findJobActionButton().props()).toMatchObject({
- jobId: mockPipelineJob.id,
- jobAction: mockPipelineJob.detailedStatus.action,
- jobName: mockPipelineJob.name,
+ jobId: mockJobInfo.id,
+ jobAction: detailedStatus.action,
+ jobName: mockJobInfo.name,
});
});
diff --git a/spec/frontend/work_items/components/work_item_actions_spec.js b/spec/frontend/work_items/components/work_item_actions_spec.js
index efd3b27ed20..87320564b5b 100644
--- a/spec/frontend/work_items/components/work_item_actions_spec.js
+++ b/spec/frontend/work_items/components/work_item_actions_spec.js
@@ -129,7 +129,6 @@ describe('WorkItemActions component', () => {
hasChildren = false,
canCreateRelatedItem = true,
workItemsBeta = true,
- isDrawer = false,
} = {}) => {
wrapper = shallowMountExtended(WorkItemActions, {
isLoggedIn: isLoggedIn(),
@@ -160,7 +159,6 @@ describe('WorkItemActions component', () => {
hideSubscribe,
hasChildren,
canCreateRelatedItem,
- isDrawer,
},
mocks: {
$toast,
@@ -623,11 +621,5 @@ describe('WorkItemActions component', () => {
expect(findChangeTypeButton().exists()).toBe(false);
});
-
- it('hides the action in case of drawer', () => {
- createComponent({ isDrawer: true });
-
- expect(findChangeTypeButton().exists()).toBe(false);
- });
});
});
diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb
index bf63420aa78..31a166a2ce2 100644
--- a/spec/graphql/types/permission_types/issue_spec.rb
+++ b/spec/graphql/types/permission_types/issue_spec.rb
@@ -7,7 +7,8 @@ RSpec.describe Types::PermissionTypes::Issue do
expected_permissions = [
:read_issue, :admin_issue, :update_issue, :reopen_issue,
:read_design, :create_design, :destroy_design,
- :create_note, :update_design, :admin_issue_relation
+ :create_note, :update_design, :move_design,
+ :admin_issue_relation
]
expected_permissions.each do |permission|
diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb
index 3fbc652c003..9aab559f473 100644
--- a/spec/graphql/types/permission_types/project_spec.rb
+++ b/spec/graphql/types/permission_types/project_spec.rb
@@ -13,8 +13,8 @@ RSpec.describe Types::PermissionTypes::Project do
:create_merge_request_from, :create_wiki, :push_code, :create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label,
:update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content,
- :read_merge_request, :read_design, :create_design, :update_design, :destroy_design, :read_environment,
- :view_edit_page
+ :read_merge_request, :read_design, :create_design, :update_design, :destroy_design, :move_design,
+ :read_environment, :view_edit_page
]
expected_permissions.each do |permission|
diff --git a/spec/helpers/access_tokens_helper_spec.rb b/spec/helpers/access_tokens_helper_spec.rb
index 967321ec122..831364bfd39 100644
--- a/spec/helpers/access_tokens_helper_spec.rb
+++ b/spec/helpers/access_tokens_helper_spec.rb
@@ -67,19 +67,29 @@ RSpec.describe AccessTokensHelper, feature_category: :system_access do
end
describe '#expires_at_field_data', :freeze_time do
+ before do
+ # Test the CE version of `expires_at_field_data` by satisfying the condition in the EE
+ # that calls the `super` method.
+ allow(helper).to receive(:personal_access_token_expiration_policy_enabled?).and_return(false)
+ end
+
it 'returns expected hash' do
expect(helper.expires_at_field_data).to eq({
- min_date: 1.day.from_now.iso8601
+ min_date: 1.day.from_now.iso8601,
+ max_date: 400.days.from_now.iso8601
})
end
- context 'when require_personal_access_token_expiry is true' do
+ context 'when require_personal_access_token_expiry is false' do
before do
stub_application_setting(require_personal_access_token_expiry: false)
end
it 'returns an empty hash' do
- expect(helper.expires_at_field_data).to eq({})
+ expect(helper.expires_at_field_data).to eq({
+ min_date: 1.day.from_now.iso8601,
+ max_date: nil
+ })
end
end
end