diff options
36 files changed, 405 insertions, 61 deletions
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml index 9e11a6606f7..c616fe3de82 100644 --- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml +++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml @@ -104,6 +104,7 @@ instance: variables: QA_SCENARIO: Test::Instance::Image rules: + - !reference [.rules:test:smoke-for-omnibus-mr, rules] - !reference [.rules:test:feature-flags-set, rules] # always run instance to validate ff change - !reference [.rules:test:qa-parallel, rules] - if: $QA_SUITES =~ /Test::Instance::All/ @@ -140,6 +141,7 @@ praefect: QA_CAN_TEST_PRAEFECT: "true" KNAPSACK_TEST_FILE_PATTERN: "qa/specs/features/**/3_create/**/*_spec.rb" rules: + - !reference [.rules:test:smoke-for-omnibus-mr, rules] - !reference [.rules:test:qa-parallel, rules] - if: $QA_SUITES =~ /Test::Instance::All/ @@ -177,6 +179,7 @@ decomposition-single-db: QA_SCENARIO: Test::Instance::Image GITLAB_QA_OPTS: --omnibus-config decomposition_single_db $EXTRA_GITLAB_QA_OPTS rules: + - !reference [.rules:test:smoke-for-omnibus-mr, rules] - !reference [.rules:test:qa-parallel, rules] - if: $QA_SUITES =~ /Test::Instance::All/ @@ -213,6 +216,7 @@ decomposition-multiple-db: GITLAB_ALLOW_SEPARATE_CI_DATABASE: "true" GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db $EXTRA_GITLAB_QA_OPTS rules: + - !reference [.rules:test:smoke-for-omnibus-mr, rules] - !reference [.rules:test:qa-parallel, rules] - if: $QA_SUITES =~ /Test::Instance::All/ diff --git a/.gitlab/ci/qa-common/rules.gitlab-ci.yml b/.gitlab/ci/qa-common/rules.gitlab-ci.yml index 7518f08398f..16440aa4cbc 100644 --- a/.gitlab/ci/qa-common/rules.gitlab-ci.yml +++ b/.gitlab/ci/qa-common/rules.gitlab-ci.yml @@ -155,6 +155,15 @@ - *default-branch - *feature-flags-set-manual +.rules:test:smoke-for-omnibus-mr: + rules: + - if: '$CI_PROJECT_NAME == "omnibus-gitlab" && $PIPELINE_TYPE =~ /TRIGGERED_(CE|EE)_PIPELINE/ && $QA_OMNIBUS_MR_TESTS == "only-smoke-reliable"' + variables: + QA_RSPEC_TAGS: "--tag smoke --tag reliable --tag ~orchestrated --tag ~skip_live_env" + - if: '$CI_PROJECT_NAME == "omnibus-gitlab" && $PIPELINE_TYPE =~ /TRIGGERED_(CE|EE)_PIPELINE/ && $QA_OMNIBUS_MR_TESTS == "except-smoke-reliable"' + variables: + QA_RSPEC_TAGS: "--tag ~smoke --tag ~reliable --tag ~orchestrated --tag ~skip_live_env --tag ~transient" + # ------------------------------------------ # Report # ------------------------------------------ diff --git a/.gitlab/ci/qa-common/variables.gitlab-ci.yml b/.gitlab/ci/qa-common/variables.gitlab-ci.yml index 9498df47ecc..a449d960cff 100644 --- a/.gitlab/ci/qa-common/variables.gitlab-ci.yml +++ b/.gitlab/ci/qa-common/variables.gitlab-ci.yml @@ -17,3 +17,4 @@ variables: RSPEC_FAST_QUARANTINE_FILE: "fast_quarantine-gitlab.txt" # This path is relative to /home/gitlab/qa/ in the QA container RSPEC_FAST_QUARANTINE_PATH: "rspec/${RSPEC_FAST_QUARANTINE_FILE}" + QA_OMNIBUS_MR_TESTS: "only-smoke-reliable" diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue index 0ce11da658c..3e084c45c31 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_drawer.vue @@ -5,6 +5,7 @@ import { GlFormCheckbox, GlFormCombobox, GlFormGroup, + GlFormInput, GlFormSelect, GlFormTextarea, GlIcon, @@ -16,7 +17,9 @@ import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; import { getContentWrapperHeight } from '~/lib/utils/dom_utils'; import { helpPagePath } from '~/helpers/help_page_helper'; import { + allEnvironments, defaultVariableState, + EDIT_VARIABLE_ACTION, ENVIRONMENT_SCOPE_LINK_TITLE, EXPANDED_VARIABLES_NOTE, FLAG_LINK_TITLE, @@ -26,9 +29,11 @@ import { import CiEnvironmentsDropdown from './ci_environments_dropdown.vue'; import { awsTokenList } from './ci_variable_autocomplete_tokens'; -const i18n = { +export const i18n = { addVariable: s__('CiVariables|Add Variable'), cancel: __('Cancel'), + defaultScope: allEnvironments.text, + editVariable: s__('CiVariables|Edit Variable'), environments: __('Environments'), environmentScopeLinkTitle: ENVIRONMENT_SCOPE_LINK_TITLE, expandedField: s__('CiVariables|Expand variable reference'), @@ -57,26 +62,33 @@ export default { GlFormCheckbox, GlFormCombobox, GlFormGroup, + GlFormInput, GlFormSelect, GlFormTextarea, GlIcon, GlLink, GlSprintf, }, - inject: ['environmentScopeLink'], + inject: ['environmentScopeLink', 'isProtectedByDefault'], props: { areEnvironmentsLoading: { type: Boolean, required: true, }, + areScopedVariablesAvailable: { + type: Boolean, + required: false, + default: false, + }, environments: { type: Array, required: false, default: () => [], }, - hasEnvScopeQuery: { + hideEnvironmentScope: { type: Boolean, - required: true, + required: false, + default: false, }, mode: { type: String, @@ -85,22 +97,47 @@ export default { return VARIABLE_ACTIONS.includes(val); }, }, + selectedVariable: { + type: Object, + required: false, + default: () => {}, + }, }, data() { return { - key: defaultVariableState.key, - variableType: defaultVariableState.variableType, + variable: { ...defaultVariableState, ...this.selectedVariable }, }; }, computed: { getDrawerHeaderHeight() { return getContentWrapperHeight(); }, + isExpanded() { + return !this.variable.raw; + }, + isEditing() { + return this.mode === EDIT_VARIABLE_ACTION; + }, + modalActionText() { + return this.isEditing ? this.$options.i18n.editVariable : this.$options.i18n.addVariable; + }, + }, + mounted() { + if (this.isProtectedByDefault && !this.isEditing) { + this.variable = { ...this.variable, protected: true }; + } }, methods: { close() { this.$emit('close-form'); }, + setRaw(expanded) { + this.variable = { ...this.variable, raw: !expanded }; + }, + submit() { + this.$emit(this.isEditing ? 'update-variable' : 'add-variable', this.variable); + this.close(); + }, }, awsTokenList, flagLink: helpPagePath('ci/variables/index', { @@ -119,20 +156,25 @@ export default { @close="close" > <template #title> - <h2 class="gl-m-0">{{ $options.i18n.addVariable }}</h2> + <h2 class="gl-m-0">{{ modalActionText }}</h2> </template> <gl-form-group :label="$options.i18n.type" label-for="ci-variable-type" - class="gl-border-none gl-mb-n5" + class="gl-border-none" + :class="{ + 'gl-mb-n5': !hideEnvironmentScope, + 'gl-mb-n1': hideEnvironmentScope, + }" > <gl-form-select id="ci-variable-type" - v-model="variableType" + v-model="variable.variableType" :options="$options.variableOptions" /> </gl-form-group> <gl-form-group + v-if="!hideEnvironmentScope" class="gl-border-none gl-mb-n5" label-for="ci-variable-env" data-testid="environment-scope" @@ -154,11 +196,18 @@ export default { </div> </template> <ci-environments-dropdown + v-if="areScopedVariablesAvailable" class="gl-mb-5" + has-env-scope-query :are-environments-loading="areEnvironmentsLoading" :environments="environments" - :has-env-scope-query="hasEnvScopeQuery" - selected-environment-scope="" + :selected-environment-scope="variable.environmentScope" + /> + <gl-form-input + v-else + :value="$options.i18n.defaultScope" + class="gl-w-full gl-mb-5" + readonly /> </gl-form-group> <gl-form-group class="gl-border-none gl-mb-n8"> @@ -177,17 +226,21 @@ export default { </gl-link> </div> </template> - <gl-form-checkbox data-testid="ci-variable-protected-checkbox"> + <gl-form-checkbox v-model="variable.protected" data-testid="ci-variable-protected-checkbox"> {{ $options.i18n.protectedField }} <p class="gl-text-secondary"> {{ $options.i18n.protectedDescription }} </p> </gl-form-checkbox> - <gl-form-checkbox data-testid="ci-variable-masked-checkbox"> + <gl-form-checkbox v-model="variable.masked" data-testid="ci-variable-masked-checkbox"> {{ $options.i18n.maskedField }} <p class="gl-text-secondary">{{ $options.i18n.maskedDescription }}</p> </gl-form-checkbox> - <gl-form-checkbox data-testid="ci-variable-expanded-checkbox"> + <gl-form-checkbox + data-testid="ci-variable-expanded-checkbox" + :checked="isExpanded" + @change="setRaw" + > {{ $options.i18n.expandedField }} <p class="gl-text-secondary"> <gl-sprintf :message="$options.i18n.expandedDescription" class="gl-text-secondary"> @@ -199,7 +252,7 @@ export default { </gl-form-checkbox> </gl-form-group> <gl-form-combobox - v-model="key" + v-model="variable.key" :token-list="$options.awsTokenList" :label-text="$options.i18n.key" class="gl-border-none gl-pb-0! gl-mb-n5" @@ -222,11 +275,15 @@ export default { /> </gl-form-group> <div class="gl-display-flex gl-justify-content-end"> - <gl-button category="primary" class="gl-mr-3" data-testid="cancel-button" @click="close" + <gl-button category="secondary" class="gl-mr-3" data-testid="cancel-button" @click="close" >{{ $options.i18n.cancel }} </gl-button> - <gl-button category="primary" variant="confirm" data-testid="confirm-button" - >{{ $options.i18n.addVariable }} + <gl-button + category="primary" + variant="confirm" + data-testid="ci-variable-confirm-btn" + @click="submit" + >{{ modalActionText }} </gl-button> </div> </gl-drawer> diff --git a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue index f4e1da9b34f..482f6da5617 100644 --- a/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue +++ b/app/assets/javascripts/ci/ci_variable_list/components/ci_variable_settings.vue @@ -139,7 +139,10 @@ export default { <ci-variable-drawer v-if="showDrawer" :are-environments-loading="areEnvironmentsLoading" - :has-env-scope-query="hasEnvScopeQuery" + :are-scoped-variables-available="areScopedVariablesAvailable" + :environments="environments" + :hide-environment-scope="hideEnvironmentScope" + :selected-variable="selectedVariable" :mode="mode" v-on="$listeners" @close-form="closeForm" diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_invite_members.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_invite_members.vue index b41d126be68..232cdcd2198 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_invite_members.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_invite_members.vue @@ -15,7 +15,7 @@ export default { }, computed: { triggerSource() { - return `${this.issuableType}-assignee-dropdown`; + return `${this.issuableType}_assignee_dropdown`; }, }, }; diff --git a/app/assets/javascripts/super_sidebar/components/create_menu.vue b/app/assets/javascripts/super_sidebar/components/create_menu.vue index 3645606515f..7918f8c8840 100644 --- a/app/assets/javascripts/super_sidebar/components/create_menu.vue +++ b/app/assets/javascripts/super_sidebar/components/create_menu.vue @@ -83,7 +83,7 @@ export default { <invite-members-trigger v-if="isInvitedMembers(groupItem)" :key="`${groupItem.text}-trigger`" - trigger-source="top-nav" + trigger-source="top_nav" :trigger-element="$options.TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN" /> <gl-disclosure-dropdown-item v-else :key="groupItem.text" :item="groupItem" /> diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue index 4b4aa7f96ca..f9527884adc 100644 --- a/app/assets/javascripts/work_items/components/work_item_assignees.vue +++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue @@ -388,7 +388,7 @@ export default { :display-text="__('Invite members')" trigger-element="side-nav" icon="plus" - trigger-source="work-item-assignees-dropdown" + trigger-source="work_item_assignees_dropdown" classes="gl-display-block gl-text-body! gl-hover-text-decoration-none gl-pb-2" /> </gl-dropdown-item> diff --git a/app/helpers/nav/new_dropdown_helper.rb b/app/helpers/nav/new_dropdown_helper.rb index 306c4d8694e..5274ace3d8a 100644 --- a/app/helpers/nav/new_dropdown_helper.rb +++ b/app/helpers/nav/new_dropdown_helper.rb @@ -157,7 +157,7 @@ module Nav partial: partial, component: 'invite_members', data: { - trigger_source: 'top-nav', + trigger_source: 'top_nav', trigger_element: 'text-emoji' } ) diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index e5c66c2c432..fbfaaa49b39 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -15,7 +15,7 @@ = render_if_exists 'groups/group_members/create_service_account' .js-invite-members-trigger{ data: { variant: 'confirm', classes: 'gl-md-w-auto gl-w-full', - trigger_source: 'group-members-page', + trigger_source: 'group_members_page', display_text: _('Invite members') } } = render 'groups/invite_groups_modal', group: @group, reload_page_on_submit: true diff --git a/app/views/projects/_invite_members_empty_project.html.haml b/app/views/projects/_invite_members_empty_project.html.haml index 18d06c7d0bb..d6cab06f773 100644 --- a/app/views/projects/_invite_members_empty_project.html.haml +++ b/app/views/projects/_invite_members_empty_project.html.haml @@ -6,4 +6,4 @@ .js-invite-members-trigger{ data: { variant: 'confirm', classes: 'gl-mb-8 gl-xs-w-full', display_text: s_('InviteMember|Invite members'), - trigger_source: 'project-empty-page' } } + trigger_source: 'project_empty_page' } } diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 4a29402bfe7..38633c9e5f1 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,6 +1,6 @@ - breadcrumb_title s_("CompareRevisions|Compare revisions") -- page_title _("CompareRevisions|Compare revisions") +- page_title s_("CompareRevisions|Compare revisions") .prepend-top-20 #js-compare-selector{ data: project_compare_selector_data(@project, @merge_request, @compare_params) } diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 6b6aaaad802..1f39bbe12a2 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -26,7 +26,7 @@ - if can_admin_project_member?(@project) .js-invite-members-trigger{ data: { variant: 'confirm', classes: 'gl-md-w-auto gl-w-full gl-md-ml-3 gl-md-mt-0 gl-mt-3', - trigger_source: 'project-members-page', + trigger_source: 'project_members_page', display_text: _('Invite members') } } - else - if project_can_be_shared? diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml index a27bb506c87..0ffce0ac571 100644 --- a/app/views/shared/issuable/_sidebar_assignees.html.haml +++ b/app/views/shared/issuable/_sidebar_assignees.html.haml @@ -45,4 +45,4 @@ options: options, wrapper_class: 'js-sidebar-assignee-dropdown', track_label: 'edit_assignee', - trigger_source: "#{issuable_type}-assignee-dropdown" + trigger_source: "#{issuable_type}_assignee_dropdown" diff --git a/app/views/shared/issuable/_sidebar_reviewers.html.haml b/app/views/shared/issuable/_sidebar_reviewers.html.haml index b360fac0a55..4b07d8d0850 100644 --- a/app/views/shared/issuable/_sidebar_reviewers.html.haml +++ b/app/views/shared/issuable/_sidebar_reviewers.html.haml @@ -42,4 +42,4 @@ options: options, wrapper_class: 'js-sidebar-reviewer-dropdown', track_label: 'edit_reviewer', - trigger_source: "#{issuable_type}-reviewer-dropdown" + trigger_source: "#{issuable_type}_reviewer_dropdown" diff --git a/config/feature_flags/development/explain_current_blob.yml b/config/feature_flags/development/explain_current_blob.yml new file mode 100644 index 00000000000..e296748a3f7 --- /dev/null +++ b/config/feature_flags/development/explain_current_blob.yml @@ -0,0 +1,8 @@ +--- +name: explain_current_blob +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128342/ +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/420959 +milestone: '16.3' +type: development +group: group::ai framework +default_enabled: false diff --git a/config/feature_flags/development/push_ai_to_load_identified_issue_json.yml b/config/feature_flags/development/push_ai_to_load_identified_issue_json.yml new file mode 100644 index 00000000000..265b561c760 --- /dev/null +++ b/config/feature_flags/development/push_ai_to_load_identified_issue_json.yml @@ -0,0 +1,8 @@ +--- +name: push_ai_to_load_identified_issue_json +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128342 +rollout_issue_url: +milestone: '16.3' +type: development +group: group::ai framework +default_enabled: false diff --git a/data/deprecations/16-3-CI-job-token-scope-update.yml b/data/deprecations/16-3-CI-job-token-scope-update.yml new file mode 100644 index 00000000000..5867cedcc53 --- /dev/null +++ b/data/deprecations/16-3-CI-job-token-scope-update.yml @@ -0,0 +1,28 @@ +# +# REQUIRED FIELDS +# +- title: "Job token allowlist covers public and internal projects" # (required) Clearly explain the change, or planned change. For example, "The `confidential` field for a `Note` is deprecated" or "CI/CD job names will be limited to 250 characters." + removal_milestone: "16.6" # (required) The milestone when this feature is planned to be removed + announcement_milestone: "16.3" # (required) The milestone when this feature was first announced as deprecated. + breaking_change: true # (required) Change to false if this is not a breaking change. + reporter: jocelynjane # (required) GitLab username of the person reporting the change + stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/420678 # (required) Link to the deprecation issue in GitLab + body: | # (required) Do not modify this line, instead modify the lines below. + Starting in 16.6, projects that are **public** or **internal** will no longer authorize job token requests from projects that are **not** on the project's allowlist when [**Limit access to this project**](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#allow-access-to-your-project-with-a-job-token) is enabled. + + If you have [public or internal](https://docs.gitlab.com/ee/user/public_access.html#change-project-visibility) projects with the **Limit access to this project** setting enabled, you must add any projects which make job token requests to your project's allowlist for continued authorization. +# +# OPTIONAL END OF SUPPORT FIELDS +# +# If an End of Support period applies, the announcement should be shared with GitLab Support +# in the `#spt_managers` channel in Slack, and mention `@gitlab-com/support` in this MR. +# + end_of_support_milestone: # (optional) Use "XX.YY" format. The milestone when support for this feature will end. + # + # OTHER OPTIONAL FIELDS + # + tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate] + documentation_url: # (optional) This is a link to the current documentation page + image_url: # (optional) This is a link to a thumbnail image depicting the feature + video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index a3a644d1d49..1138cc8d0dd 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -909,6 +909,27 @@ Previous work helped [align the vulnerabilities calls for pipeline security tabs </div> </div> +<div class="milestone-wrapper" data-milestone="16.6"> + +## GitLab 16.6 + +<div class="deprecation breaking-change" data-milestone="16.6"> + +### Job token allowlist covers public and internal projects + +<div class="deprecation-notes"> +- Announced in GitLab <span class="milestone">16.3</span> +- Removal in GitLab <span class="milestone">16.6</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change)) +- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/420678). +</div> + +Starting in 16.6, projects that are **public** or **internal** will no longer authorize job token requests from projects that are **not** on the project's allowlist when [**Limit access to this project**](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#allow-access-to-your-project-with-a-job-token) is enabled. + +If you have [public or internal](https://docs.gitlab.com/ee/user/public_access.html#change-project-visibility) projects with the **Limit access to this project** setting enabled, you must add any projects which make job token requests to your project's allowlist for continued authorization. + +</div> +</div> + <div class="milestone-wrapper" data-milestone="16.5"> ## GitLab 16.5 diff --git a/doc/user/project/integrations/webhook_events.md b/doc/user/project/integrations/webhook_events.md index c4dd8dcf812..8bb1c433af5 100644 --- a/doc/user/project/integrations/webhook_events.md +++ b/doc/user/project/integrations/webhook_events.md @@ -1521,7 +1521,8 @@ Deployment events are triggered when a deployment: - Fails - Is cancelled -The `deployable_id` in the payload is the ID of the CI/CD job. +The `deployable_id` and `deployable_url` in the payload represent a CI/CD job that executed the deployment. +When the deployment event occurs by [API](../../../ci/environments/external_deployment_tools.md) or [`trigger` jobs](../../../ci/pipelines/downstream_pipelines.md), `deployable_url` is `null`. Request header: diff --git a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml index 7b2fb49b65e..c737da7a579 100644 --- a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.53.0' + DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.54.0' .dast-auto-deploy: image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}" diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml index 1e482ccca82..9b37d8774d9 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - AUTO_DEPLOY_IMAGE_VERSION: 'v2.53.0' + AUTO_DEPLOY_IMAGE_VERSION: 'v2.54.0' .auto-deploy: image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}" diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml index 6eac691b293..54ce5f06ad0 100644 --- a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - AUTO_DEPLOY_IMAGE_VERSION: 'v2.53.0' + AUTO_DEPLOY_IMAGE_VERSION: 'v2.54.0' .auto-deploy: image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}" diff --git a/lib/gitlab/data_builder/deployment.rb b/lib/gitlab/data_builder/deployment.rb index b26f9a61ee1..915d606feb9 100644 --- a/lib/gitlab/data_builder/deployment.rb +++ b/lib/gitlab/data_builder/deployment.rb @@ -9,7 +9,7 @@ module Gitlab def build(deployment, status, status_changed_at) # Deployments will not have a deployable when created using the API. deployable_url = - if deployment.deployable + if deployment.deployable.instance_of?(::Ci::Build) Gitlab::UrlBuilder.build(deployment.deployable) end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 65dcb7bbfe0..b4b9e846699 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1904,6 +1904,9 @@ msgstr "" msgid "AI|Apply AI-generated description" msgstr "" +msgid "AI|Ask GitLab Duo" +msgstr "" + msgid "AI|Ask a question" msgstr "" @@ -2015,9 +2018,6 @@ msgstr "" msgid "AI|You are not allowed to copy any part of this output into issues, comments, GitLab source code, commit messages, merge requests or any other user interface in the %{gitlabOrg} or %{gitlabCom} groups." msgstr "" -msgid "AI|You can ask AI for more information." -msgstr "" - msgid "AI|finding" msgstr "" @@ -10135,6 +10135,9 @@ msgstr "" msgid "CiVariables|Do you want to delete the variable %{key}?" msgstr "" +msgid "CiVariables|Edit Variable" +msgstr "" + msgid "CiVariables|Environments" msgstr "" diff --git a/qa/qa/specs/features/api/5_package/container_registry/saas/container_registry_spec.rb b/qa/qa/specs/features/api/5_package/container_registry/saas/container_registry_spec.rb index d2b1cbd2c95..32100fbce32 100644 --- a/qa/qa/specs/features/api/5_package/container_registry/saas/container_registry_spec.rb +++ b/qa/qa/specs/features/api/5_package/container_registry/saas/container_registry_spec.rb @@ -57,11 +57,9 @@ module QA - docker build -t $IMAGE_TAG . - docker push $IMAGE_TAG - docker pull $IMAGE_TAG - tags: - - "runner-for-#{project.name}" test: - image: dwdraju/alpine-curl-jq:latest + image: registry.gitlab.com/gitlab-ci-utils/curl-jq:latest stage: test script: - 'id=$(curl --header "PRIVATE-TOKEN: #{masked_token}" "https://${CI_SERVER_HOST}/api/v4/projects/#{project.id}/registry/repositories" | jq ".[0].id")' @@ -72,8 +70,6 @@ module QA - if [ $status_code -ne 200 ]; then exit 1; fi; - 'status_code=$(curl --head --output /dev/null --write-out "%{http_code}\n" --header "PRIVATE-TOKEN: #{masked_token}" "https://${CI_SERVER_HOST}/api/v4/projects/#{project.id}/registry/repositories/$id/tags/master")' - if [ $status_code -ne 404 ]; then exit 1; fi; - tags: - - "runner-for-#{project.name}" YAML end @@ -96,7 +92,7 @@ module QA end Support::Retrier.retry_until( - max_duration: 10, + max_duration: 30, sleep_interval: 1, message: "Waiting for pipeline to start" ) do diff --git a/scripts/process_custom_semgrep_results.sh b/scripts/process_custom_semgrep_results.sh index 1fdd8e486f3..0eccef00973 100755 --- a/scripts/process_custom_semgrep_results.sh +++ b/scripts/process_custom_semgrep_results.sh @@ -22,7 +22,7 @@ jq -crM '.vulnerabilities | echo "Resulting file:" cat findings.txt -EXISTING_COMMENT_ID=$(curl "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \ +EXISTING_COMMENT_ID=$(curl "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes?per_page=100" \ --header "Private-Token: $CUSTOM_SAST_RULES_BOT_PAT" | jq -crM 'map( select( .author.id == (env.BOT_USER_ID | tonumber) ) | .id ) | first') diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb index 138031ffaac..dd64ddcede5 100644 --- a/spec/features/groups/members/manage_members_spec.rb +++ b/spec/features/groups/members/manage_members_spec.rb @@ -85,7 +85,7 @@ RSpec.describe 'Groups > Members > Manage members', feature_category: :groups_an end end - it_behaves_like 'inviting members', 'group-members-page' do + it_behaves_like 'inviting members', 'group_members_page' do let_it_be(:entity) { group } let_it_be(:members_page_path) { group_group_members_path(entity) } let_it_be(:subentity) { create(:group, parent: group) } diff --git a/spec/features/projects/members/manage_members_spec.rb b/spec/features/projects/members/manage_members_spec.rb index 0e3ac5ff3ac..76b2a73e170 100644 --- a/spec/features/projects/members/manage_members_spec.rb +++ b/spec/features/projects/members/manage_members_spec.rb @@ -173,7 +173,7 @@ RSpec.describe 'Projects > Members > Manage members', :js, feature_category: :on end end - it_behaves_like 'inviting members', 'project-members-page' do + it_behaves_like 'inviting members', 'project_members_page' do let_it_be(:entity) { project } let_it_be(:members_page_path) { project_project_members_path(entity) } let_it_be(:subentity) { project } diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_drawer_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_drawer_spec.js index 762c9611dac..cb839503931 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_drawer_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_drawer_spec.js @@ -1,42 +1,73 @@ -import { GlDrawer, GlFormSelect } from '@gitlab/ui'; +import { GlDrawer, GlFormInput, GlFormSelect } from '@gitlab/ui'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import CiVariableDrawer from '~/ci/ci_variable_list/components/ci_variable_drawer.vue'; +import CiEnvironmentsDropdown from '~/ci/ci_variable_list/components/ci_environments_dropdown.vue'; +import CiVariableDrawer, { i18n } from '~/ci/ci_variable_list/components/ci_variable_drawer.vue'; import { ADD_VARIABLE_ACTION, + EDIT_VARIABLE_ACTION, variableOptions, + projectString, variableTypes, } from '~/ci/ci_variable_list/constants'; +import { mockVariablesWithScopes } from '../mocks'; describe('CI Variable Drawer', () => { let wrapper; + const mockProjectVariable = mockVariablesWithScopes(projectString)[0]; + const mockProjectVariableFileType = mockVariablesWithScopes(projectString)[1]; + const mockEnvScope = 'staging'; + const mockEnvironments = ['*', 'dev', 'staging', 'production']; + const defaultProps = { areEnvironmentsLoading: false, - hasEnvScopeQuery: true, + areScopedVariablesAvailable: true, + environments: mockEnvironments, + hideEnvironmentScope: false, + selectedVariable: {}, mode: ADD_VARIABLE_ACTION, }; - const createComponent = ({ mountFn = shallowMountExtended, props = {} } = {}) => { + const defaultProvide = { + isProtectedByDefault: true, + environmentScopeLink: '/help/environments', + }; + + const createComponent = ({ + mountFn = shallowMountExtended, + props = {}, + provide = {}, + stubs = {}, + } = {}) => { wrapper = mountFn(CiVariableDrawer, { propsData: { ...defaultProps, ...props, }, provide: { - environmentScopeLink: '/help/environments', + ...defaultProvide, + ...provide, }, + stubs, }); }; + const findConfirmBtn = () => wrapper.findByTestId('ci-variable-confirm-btn'); + const findDisabledEnvironmentScopeDropdown = () => wrapper.findComponent(GlFormInput); const findDrawer = () => wrapper.findComponent(GlDrawer); + const findEnvironmentScopeDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown); + const findExpandedCheckbox = () => wrapper.findByTestId('ci-variable-expanded-checkbox'); + const findMaskedCheckbox = () => wrapper.findByTestId('ci-variable-masked-checkbox'); + const findProtectedCheckbox = () => wrapper.findByTestId('ci-variable-protected-checkbox'); + const findTitle = () => findDrawer().find('h2'); const findTypeDropdown = () => wrapper.findComponent(GlFormSelect); describe('validations', () => { - beforeEach(() => { - createComponent({ mountFn: mountExtended }); - }); - describe('type dropdown', () => { + beforeEach(() => { + createComponent({ mountFn: mountExtended }); + }); + it('adds each type option as a dropdown item', () => { expect(findTypeDropdown().findAll('option')).toHaveLength(variableOptions.length); @@ -50,20 +81,169 @@ describe('CI Variable Drawer', () => { variableTypes.envType, ); }); + + it('renders the selected variable type', () => { + createComponent({ + mountFn: mountExtended, + props: { + areEnvironmentsLoading: true, + selectedVariable: mockProjectVariableFileType, + }, + }); + + expect(findTypeDropdown().element.value).toBe(variableTypes.fileType); + }); + }); + + describe('environment scope dropdown', () => { + it('passes correct props to the dropdown', () => { + createComponent({ + props: { + areEnvironmentsLoading: true, + selectedVariable: { ...mockProjectVariable, environmentScope: mockEnvScope }, + }, + stubs: { CiEnvironmentsDropdown }, + }); + + expect(findEnvironmentScopeDropdown().props()).toMatchObject({ + areEnvironmentsLoading: true, + environments: mockEnvironments, + selectedEnvironmentScope: mockEnvScope, + }); + }); + + it('hides environment scope dropdown when hideEnvironmentScope is true', () => { + createComponent({ + props: { hideEnvironmentScope: true }, + stubs: { CiEnvironmentsDropdown }, + }); + + expect(findEnvironmentScopeDropdown().exists()).toBe(false); + }); + + it('disables the environment scope dropdown when areScopedVariablesAvailable is false', () => { + createComponent({ + mountFn: mountExtended, + props: { areScopedVariablesAvailable: false }, + }); + + expect(findEnvironmentScopeDropdown().exists()).toBe(false); + expect(findDisabledEnvironmentScopeDropdown().attributes('readonly')).toBe('readonly'); + }); + }); + + describe('protected flag', () => { + beforeEach(() => { + createComponent(); + }); + + it('is true by default when isProtectedByDefault is true', () => { + expect(findProtectedCheckbox().attributes('checked')).toBeDefined(); + }); + + it('is not checked when isProtectedByDefault is false', () => { + createComponent({ provide: { isProtectedByDefault: false } }); + + expect(findProtectedCheckbox().attributes('checked')).toBeUndefined(); + }); + + it('inherits value of selected variable when editing', () => { + createComponent({ + props: { + selectedVariable: mockProjectVariableFileType, + mode: EDIT_VARIABLE_ACTION, + }, + }); + + expect(findProtectedCheckbox().attributes('checked')).toBeUndefined(); + }); + }); + + describe('masked flag', () => { + beforeEach(() => { + createComponent(); + }); + + it('is false by default', () => { + expect(findMaskedCheckbox().attributes('checked')).toBeUndefined(); + }); + + it('inherits value of selected variable when editing', () => { + createComponent({ + props: { + selectedVariable: mockProjectVariableFileType, + mode: EDIT_VARIABLE_ACTION, + }, + }); + + expect(findMaskedCheckbox().attributes('checked')).toBeDefined(); + }); + }); + + describe('expanded flag', () => { + beforeEach(() => { + createComponent(); + }); + + it('is true by default when adding a variable', () => { + expect(findExpandedCheckbox().attributes('checked')).toBeDefined(); + }); + + it('inherits value of selected variable when editing', () => { + createComponent({ + props: { + selectedVariable: mockProjectVariableFileType, + mode: EDIT_VARIABLE_ACTION, + }, + }); + + expect(findExpandedCheckbox().attributes('checked')).toBeUndefined(); + }); + + it("sets the variable's raw value", async () => { + await findExpandedCheckbox().vm.$emit('change'); + await findConfirmBtn().vm.$emit('click'); + + const sentRawValue = wrapper.emitted('add-variable')[0][0].raw; + expect(sentRawValue).toBe(!defaultProps.raw); + }); }); }); describe('drawer events', () => { - beforeEach(() => { + it('emits `close-form` when closing the drawer', async () => { createComponent(); - }); - it('emits `close-form` when closing the drawer', async () => { expect(wrapper.emitted('close-form')).toBeUndefined(); await findDrawer().vm.$emit('close'); expect(wrapper.emitted('close-form')).toHaveLength(1); }); + + describe('when adding a variable', () => { + beforeEach(() => { + createComponent({ stubs: { GlDrawer } }); + }); + + it('title and confirm button renders the correct text', () => { + expect(findTitle().text()).toBe(i18n.addVariable); + expect(findConfirmBtn().text()).toBe(i18n.addVariable); + }); + }); + + describe('when editing a variable', () => { + beforeEach(() => { + createComponent({ + props: { mode: EDIT_VARIABLE_ACTION }, + stubs: { GlDrawer }, + }); + }); + + it('title and confirm button renders the correct text', () => { + expect(findTitle().text()).toBe(i18n.editVariable); + expect(findConfirmBtn().text()).toBe(i18n.editVariable); + }); + }); }); }); diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js index f5737c61eea..79dd638e2bd 100644 --- a/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js +++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js @@ -77,6 +77,21 @@ describe('Ci variable table', () => { selectedVariable: {}, }); }); + + it('passes props down correctly to the ci drawer', async () => { + createComponent({ featureFlags: { ciVariableDrawer: true } }); + + await findCiVariableTable().vm.$emit('set-selected-variable'); + + expect(findCiVariableDrawer().props()).toEqual({ + areEnvironmentsLoading: defaultProps.areEnvironmentsLoading, + areScopedVariablesAvailable: defaultProps.areScopedVariablesAvailable, + environments: defaultProps.environments, + hideEnvironmentScope: defaultProps.hideEnvironmentScope, + mode: ADD_VARIABLE_ACTION, + selectedVariable: {}, + }); + }); }); describe.each` diff --git a/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js index 501048bf056..8c42e61548f 100644 --- a/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js +++ b/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js @@ -26,7 +26,7 @@ describe('Sidebar invite members component', () => { }); it('has expected attributes on the trigger', () => { - expect(findDirectInviteLink().props('triggerSource')).toBe('issue-assignee-dropdown'); + expect(findDirectInviteLink().props('triggerSource')).toBe('issue_assignee_dropdown'); }); }); }); diff --git a/spec/helpers/nav/new_dropdown_helper_spec.rb b/spec/helpers/nav/new_dropdown_helper_spec.rb index 26dadd3b4f1..db394739b60 100644 --- a/spec/helpers/nav/new_dropdown_helper_spec.rb +++ b/spec/helpers/nav/new_dropdown_helper_spec.rb @@ -37,7 +37,7 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do partial: partial, component: 'invite_members', data: { - trigger_source: 'top-nav', + trigger_source: 'top_nav', trigger_element: 'text-emoji' } ) diff --git a/spec/lib/gitlab/data_builder/deployment_spec.rb b/spec/lib/gitlab/data_builder/deployment_spec.rb index bbcfa1973ea..bf97f40e97f 100644 --- a/spec/lib/gitlab/data_builder/deployment_spec.rb +++ b/spec/lib/gitlab/data_builder/deployment_spec.rb @@ -50,6 +50,15 @@ RSpec.describe Gitlab::DataBuilder::Deployment, feature_category: :continuous_de expect(data[:deployable_url]).to be_nil end + it 'does not include the deployable URL when deployable is bridge' do + project = create(:project, :repository) + bridge = create(:ci_bridge, project: project) + deployment = create(:deployment, status: :failed, project: project, deployable: bridge) + data = described_class.build(deployment, 'failed', Time.current) + + expect(data[:deployable_url]).to be_nil + end + context 'when commit does not exist in the repository' do let_it_be(:project) { create(:project, :repository) } let_it_be(:deployment) { create(:deployment, project: project) } diff --git a/spec/support/finder_collection_allowlist.yml b/spec/support/finder_collection_allowlist.yml index 5de8e8cdca2..e7dd9cea922 100644 --- a/spec/support/finder_collection_allowlist.yml +++ b/spec/support/finder_collection_allowlist.yml @@ -7,6 +7,7 @@ - Namespaces::FreeUserCap::UsersFinder # Reason: There is no need to have anything else besides the count - Groups::EnvironmentScopesFinder # Reason: There is no need to have anything else besides the simple strucutre with the scope name - Security::RelatedPipelinesFinder # Reason: There is no need to have anything else besides the IDs of pipelines +- Llm::ExtraResourceFinder # Reason: The finder does not deal with DB-backend resource for now. # Temporary excludes (aka TODOs) # For example: diff --git a/spec/views/projects/empty.html.haml_spec.rb b/spec/views/projects/empty.html.haml_spec.rb index 2b19b364365..c478b446864 100644 --- a/spec/views/projects/empty.html.haml_spec.rb +++ b/spec/views/projects/empty.html.haml_spec.rb @@ -73,7 +73,7 @@ RSpec.describe 'projects/empty' do expect(rendered).to have_content('Invite your team') expect(rendered).to have_content('Add members to this project and start collaborating with your team.') expect(rendered).to have_selector('.js-invite-members-trigger') - expect(rendered).to have_selector('[data-trigger-source=project-empty-page]') + expect(rendered).to have_selector('[data-trigger-source=project_empty_page]') end context 'when user does not have permissions to invite members' do |