Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-10-10 18:15:04 +00:00
parent 1252510698
commit 4965365aeb
212 changed files with 2548 additions and 870 deletions

View File

@ -80,6 +80,8 @@ include:
SKIP_IMAGE_VERIFICATION: "true" SKIP_IMAGE_VERIFICATION: "true"
# set specific arch list # set specific arch list
ARCH_LIST: amd64 ARCH_LIST: amd64
# use larger runner for complex rails build jobs
HIGH_CAPACITY_RUNNER_TAG: high-cpu
trigger: trigger:
project: '${CI_PROJECT_NAMESPACE}/$[[ inputs.cng_path ]]' project: '${CI_PROJECT_NAMESPACE}/$[[ inputs.cng_path ]]'
branch: $TRIGGER_BRANCH branch: $TRIGGER_BRANCH

View File

@ -16,7 +16,7 @@
- export GOPATH=$CI_PROJECT_DIR/.go - export GOPATH=$CI_PROJECT_DIR/.go
- mkdir -p $GOPATH - mkdir -p $GOPATH
- source scripts/utils.sh - source scripts/utils.sh
- log_disk_usage before_script # https://gitlab.com/gitlab-org/gitlab/-/issues/478880 - log_disk_usage "false" # https://gitlab.com/gitlab-org/gitlab/-/issues/478880
.default-before_script: .default-before_script:
before_script: before_script:

View File

@ -102,7 +102,7 @@ include:
- rspec_section rspec_parallelized_job "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag ~quarantine --tag ~level:background_migration --tag ~click_house" - rspec_section rspec_parallelized_job "--fail-fast=${RSPEC_FAIL_FAST_THRESHOLD} --tag ~quarantine --tag ~level:background_migration --tag ~click_house"
after_script: after_script:
- source scripts/utils.sh - source scripts/utils.sh
- log_disk_usage after_script # https://gitlab.com/gitlab-org/gitlab/-/issues/478880 - log_disk_usage # https://gitlab.com/gitlab-org/gitlab/-/issues/478880
- bundle exec gem list gitlab_quality-test_tooling - bundle exec gem list gitlab_quality-test_tooling
- | - |
section_start "failed-test-issues" "Report test failures" section_start "failed-test-issues" "Report test failures"

View File

@ -2558,6 +2558,13 @@
- <<: *if-default-refs - <<: *if-default-refs
changes: *code-backstage-qa-patterns changes: *code-backstage-qa-patterns
.static-analysis:rules:ensure-application-settings-have-definition-file:
rules:
- <<: *if-default-refs
changes:
- db/structure.sql
- config/application_setting_columns/*.yml
.static-analysis:rules:haml-lint: .static-analysis:rules:haml-lint:
rules: rules:
- <<: *if-default-refs - <<: *if-default-refs

View File

@ -169,6 +169,16 @@ feature-flags-usage:
paths: paths:
- tmp/feature_flags/ - tmp/feature_flags/
ensure-application-settings-have-definition-file:
image: ruby:${RUBY_VERSION}-alpine
extends:
- .static-analysis-base
- .static-analysis:rules:ensure-application-settings-have-definition-file
variables:
USE_BUNDLE_INSTALL: "false"
script:
- run_timed_command "scripts/cells/ci-ensure-application-settings-have-definition-file.rb"
semgrep-appsec-custom-rules: semgrep-appsec-custom-rules:
stage: lint stage: lint
extends: extends:

View File

@ -105,11 +105,6 @@ build-cng:
variables: variables:
# use larger runner for complex rails build jobs # use larger runner for complex rails build jobs
HIGH_CAPACITY_RUNNER_TAG: e2e HIGH_CAPACITY_RUNNER_TAG: e2e
# quality specific fork, see: https://gitlab.com/gitlab-org/quality/quality-engineering/team-tasks/-/issues/2839
trigger:
project: ${CI_PROJECT_NAMESPACE}/quality/quality-engineering/CNG-mirror
branch: $TRIGGER_BRANCH
strategy: depend
download-knapsack-report: download-knapsack-report:
extends: extends:

37
.lefthook/gitleaks.sh Executable file
View File

@ -0,0 +1,37 @@
#!/bin/bash
set -euo pipefail
MINIMUM_VERSION="v8.20"
SCRIPT_NAME=$(basename "$0")
HOOK_TYPE="${1:-}"
if ! command -v gitleaks &>/dev/null; then
cat >&2 <<EOF
WARNING: gitleaks is not installed. Skipping secrets detection.
Please install at least version v$MINIMUM_VERSION using "asdf install" or see:
https://gitlab.com/gitlab-com/gl-security/security-research/gitleaks-endpoint-installer.
EOF
exit 0
fi
if [ -z "$HOOK_TYPE" ]; then
cat >&2 <<EOF
ERROR: Hook type argument is required.
Usage: ./$SCRIPT_NAME [pre-commit|pre-push]
Please specify 'pre-commit' or 'pre-push' as the argument.
EOF
exit 1
fi
if [ "$HOOK_TYPE" == "pre-commit" ]; then
gitleaks git --pre-commit --staged --no-banner --redact --verbose
elif [ "$HOOK_TYPE" == "pre-push" ]; then
BASE_COMMIT=$(git merge-base origin/master HEAD)
gitleaks git --log-opts="$BASE_COMMIT..HEAD" --no-banner --redact --verbose
else
cat >&2 <<EOF
ERROR: Unsupported hook type '$HOOK_TYPE'.
Usage: ./$SCRIPT_NAME [pre-commit|pre-push]
EOF
exit 1
fi

View File

@ -1986,7 +1986,6 @@ Layout/LineLength:
- 'ee/spec/services/quick_actions/interpret_service_spec.rb' - 'ee/spec/services/quick_actions/interpret_service_spec.rb'
- 'ee/spec/services/requirements_management/export_csv_service_spec.rb' - 'ee/spec/services/requirements_management/export_csv_service_spec.rb'
- 'ee/spec/services/resource_events/change_weight_service_spec.rb' - 'ee/spec/services/resource_events/change_weight_service_spec.rb'
- 'ee/spec/services/search/global_service_spec.rb'
- 'ee/spec/services/search/snippet_service_spec.rb' - 'ee/spec/services/search/snippet_service_spec.rb'
- 'ee/spec/services/security/ingestion/finding_map_collection_spec.rb' - 'ee/spec/services/security/ingestion/finding_map_collection_spec.rb'
- 'ee/spec/services/security/ingestion/ingest_report_service_spec.rb' - 'ee/spec/services/security/ingestion/ingest_report_service_spec.rb'
@ -3865,7 +3864,6 @@ Layout/LineLength:
- 'spec/requests/api/feature_flags_spec.rb' - 'spec/requests/api/feature_flags_spec.rb'
- 'spec/requests/api/features_spec.rb' - 'spec/requests/api/features_spec.rb'
- 'spec/requests/api/files_spec.rb' - 'spec/requests/api/files_spec.rb'
- 'spec/requests/api/generic_packages_spec.rb'
- 'spec/requests/api/go_proxy_spec.rb' - 'spec/requests/api/go_proxy_spec.rb'
- 'spec/requests/api/graphql/boards/board_list_issues_query_spec.rb' - 'spec/requests/api/graphql/boards/board_list_issues_query_spec.rb'
- 'spec/requests/api/graphql/boards/board_list_query_spec.rb' - 'spec/requests/api/graphql/boards/board_list_query_spec.rb'

View File

@ -325,7 +325,6 @@ Rails/StrongParams:
- 'ee/app/controllers/security/projects_controller.rb' - 'ee/app/controllers/security/projects_controller.rb'
- 'ee/app/controllers/smartcard_controller.rb' - 'ee/app/controllers/smartcard_controller.rb'
- 'ee/app/controllers/subscriptions/groups_controller.rb' - 'ee/app/controllers/subscriptions/groups_controller.rb'
- 'ee/app/controllers/subscriptions/hand_raise_leads_controller.rb'
- 'ee/app/controllers/subscriptions_controller.rb' - 'ee/app/controllers/subscriptions_controller.rb'
- 'ee/app/controllers/users/base_identity_verification_controller.rb' - 'ee/app/controllers/users/base_identity_verification_controller.rb'
- 'ee/app/controllers/users/registrations_identity_verification_controller.rb' - 'ee/app/controllers/users/registrations_identity_verification_controller.rb'

View File

@ -733,7 +733,6 @@ RSpec/ContextWording:
- 'ee/spec/services/requirements_management/export_csv_service_spec.rb' - 'ee/spec/services/requirements_management/export_csv_service_spec.rb'
- 'ee/spec/services/resource_access_tokens/create_service_spec.rb' - 'ee/spec/services/resource_access_tokens/create_service_spec.rb'
- 'ee/spec/services/resource_access_tokens/revoke_service_spec.rb' - 'ee/spec/services/resource_access_tokens/revoke_service_spec.rb'
- 'ee/spec/services/search/global_service_spec.rb'
- 'ee/spec/services/search/snippet_service_spec.rb' - 'ee/spec/services/search/snippet_service_spec.rb'
- 'ee/spec/services/security/ingestion/tasks/ingest_vulnerabilities/create_spec.rb' - 'ee/spec/services/security/ingestion/tasks/ingest_vulnerabilities/create_spec.rb'
- 'ee/spec/services/security/ingestion/tasks/update_vulnerability_uuids_spec.rb' - 'ee/spec/services/security/ingestion/tasks/update_vulnerability_uuids_spec.rb'

View File

@ -1 +1 @@
6175a5f017f01e11959045c0dc24d9ca237865e6 39a5e315d1b3e112db13930382d11061bfa95964

View File

@ -292,7 +292,7 @@
{"name":"graphiql-rails","version":"1.10.0","platform":"ruby","checksum":"b557f989a737c8b9e985142609bec52fb1e9393a701eb50e02a7c14422891040"}, {"name":"graphiql-rails","version":"1.10.0","platform":"ruby","checksum":"b557f989a737c8b9e985142609bec52fb1e9393a701eb50e02a7c14422891040"},
{"name":"graphlient","version":"0.8.0","platform":"ruby","checksum":"98c408da1d083454e9f5e274f3b0b6261e2a0c2b5f2ed7b3ef9441d46f8e7cb1"}, {"name":"graphlient","version":"0.8.0","platform":"ruby","checksum":"98c408da1d083454e9f5e274f3b0b6261e2a0c2b5f2ed7b3ef9441d46f8e7cb1"},
{"name":"graphlyte","version":"1.0.0","platform":"ruby","checksum":"b5af4ab67dde6e961f00ea1c18f159f73b52ed11395bb4ece297fe628fa1804d"}, {"name":"graphlyte","version":"1.0.0","platform":"ruby","checksum":"b5af4ab67dde6e961f00ea1c18f159f73b52ed11395bb4ece297fe628fa1804d"},
{"name":"graphql","version":"2.3.16","platform":"ruby","checksum":"6ac9966998914c5b470e527c7105d936db70af2306ec0445f88953404bce49c7"}, {"name":"graphql","version":"2.3.17","platform":"ruby","checksum":"5e03655bf8ab07cd9710821f7f991ca6f5e86cf632261350ab185527169554e3"},
{"name":"graphql-client","version":"0.23.0","platform":"ruby","checksum":"f238b8e451676baad06bd15f95396e018192243dcf12c4e6d13fb41d9a2babc1"}, {"name":"graphql-client","version":"0.23.0","platform":"ruby","checksum":"f238b8e451676baad06bd15f95396e018192243dcf12c4e6d13fb41d9a2babc1"},
{"name":"graphql-docs","version":"5.0.0","platform":"ruby","checksum":"76baca6e5a803a4b6a9fbbbfdbf16742b7c4c546c8592b6e1a7aa4e79e562d04"}, {"name":"graphql-docs","version":"5.0.0","platform":"ruby","checksum":"76baca6e5a803a4b6a9fbbbfdbf16742b7c4c546c8592b6e1a7aa4e79e562d04"},
{"name":"grpc","version":"1.63.0","platform":"aarch64-linux","checksum":"dc75c5fd570b819470781d9512105dddfdd11d984f38b8e60bb946f92d1f79ee"}, {"name":"grpc","version":"1.63.0","platform":"aarch64-linux","checksum":"dc75c5fd570b819470781d9512105dddfdd11d984f38b8e60bb946f92d1f79ee"},

View File

@ -919,7 +919,7 @@ GEM
faraday (~> 2.0) faraday (~> 2.0)
graphql-client graphql-client
graphlyte (1.0.0) graphlyte (1.0.0)
graphql (2.3.16) graphql (2.3.17)
base64 base64
fiber-storage fiber-storage
graphql-client (0.23.0) graphql-client (0.23.0)

View File

@ -293,7 +293,7 @@
{"name":"graphiql-rails","version":"1.10.0","platform":"ruby","checksum":"b557f989a737c8b9e985142609bec52fb1e9393a701eb50e02a7c14422891040"}, {"name":"graphiql-rails","version":"1.10.0","platform":"ruby","checksum":"b557f989a737c8b9e985142609bec52fb1e9393a701eb50e02a7c14422891040"},
{"name":"graphlient","version":"0.8.0","platform":"ruby","checksum":"98c408da1d083454e9f5e274f3b0b6261e2a0c2b5f2ed7b3ef9441d46f8e7cb1"}, {"name":"graphlient","version":"0.8.0","platform":"ruby","checksum":"98c408da1d083454e9f5e274f3b0b6261e2a0c2b5f2ed7b3ef9441d46f8e7cb1"},
{"name":"graphlyte","version":"1.0.0","platform":"ruby","checksum":"b5af4ab67dde6e961f00ea1c18f159f73b52ed11395bb4ece297fe628fa1804d"}, {"name":"graphlyte","version":"1.0.0","platform":"ruby","checksum":"b5af4ab67dde6e961f00ea1c18f159f73b52ed11395bb4ece297fe628fa1804d"},
{"name":"graphql","version":"2.3.16","platform":"ruby","checksum":"6ac9966998914c5b470e527c7105d936db70af2306ec0445f88953404bce49c7"}, {"name":"graphql","version":"2.3.17","platform":"ruby","checksum":"5e03655bf8ab07cd9710821f7f991ca6f5e86cf632261350ab185527169554e3"},
{"name":"graphql-client","version":"0.23.0","platform":"ruby","checksum":"f238b8e451676baad06bd15f95396e018192243dcf12c4e6d13fb41d9a2babc1"}, {"name":"graphql-client","version":"0.23.0","platform":"ruby","checksum":"f238b8e451676baad06bd15f95396e018192243dcf12c4e6d13fb41d9a2babc1"},
{"name":"graphql-docs","version":"5.0.0","platform":"ruby","checksum":"76baca6e5a803a4b6a9fbbbfdbf16742b7c4c546c8592b6e1a7aa4e79e562d04"}, {"name":"graphql-docs","version":"5.0.0","platform":"ruby","checksum":"76baca6e5a803a4b6a9fbbbfdbf16742b7c4c546c8592b6e1a7aa4e79e562d04"},
{"name":"grpc","version":"1.63.0","platform":"aarch64-linux","checksum":"dc75c5fd570b819470781d9512105dddfdd11d984f38b8e60bb946f92d1f79ee"}, {"name":"grpc","version":"1.63.0","platform":"aarch64-linux","checksum":"dc75c5fd570b819470781d9512105dddfdd11d984f38b8e60bb946f92d1f79ee"},

View File

@ -929,7 +929,7 @@ GEM
faraday (~> 2.0) faraday (~> 2.0)
graphql-client graphql-client
graphlyte (1.0.0) graphlyte (1.0.0)
graphql (2.3.16) graphql (2.3.17)
base64 base64
fiber-storage fiber-storage
graphql-client (0.23.0) graphql-client (0.23.0)

View File

@ -6,7 +6,7 @@ require "guard/rspec/dsl"
cmd = ENV['GUARD_CMD'] || (ENV['SPRING'] ? 'spring rspec' : 'bundle exec rspec') cmd = ENV['GUARD_CMD'] || (ENV['SPRING'] ? 'spring rspec' : 'bundle exec rspec')
directories %w[app ee keeps lib rubocop tooling spec] directories %w[app ee keeps lib rubocop scripts spec tooling]
rspec_context_for = proc do |context_path| rspec_context_for = proc do |context_path|
OpenStruct.new(to_s: "spec").tap do |rspec| # rubocop:disable Style/OpenStructUse OpenStruct.new(to_s: "spec").tap do |rspec| # rubocop:disable Style/OpenStructUse
@ -45,6 +45,7 @@ guard_setup = proc do |context_path|
watch(%r{^#{context_path}(lib/.+)\.rb$}) { |m| rspec.spec.call(m[1]) } watch(%r{^#{context_path}(lib/.+)\.rb$}) { |m| rspec.spec.call(m[1]) }
watch(%r{^#{context_path}(rubocop/.+)\.rb$}) { |m| rspec.spec.call(m[1]) } watch(%r{^#{context_path}(rubocop/.+)\.rb$}) { |m| rspec.spec.call(m[1]) }
watch(%r{^#{context_path}(tooling/.+)\.rb$}) { |m| rspec.spec.call(m[1]) } watch(%r{^#{context_path}(tooling/.+)\.rb$}) { |m| rspec.spec.call(m[1]) }
watch(%r{^#{context_path}(scripts/.+)\.rb$}) { |m| rspec.spec.call(m[1].tr('-', '_')) }
# Rails files # Rails files
rails = rails_context_for.call(context_path, %w[erb haml slim]) rails = rails_context_for.call(context_path, %w[erb haml slim])

View File

@ -65,7 +65,7 @@ export default {
<template #header> <template #header>
<div <div
class="gl-flex gl-min-h-8 gl-items-center gl-border-b-1 gl-border-b-gray-200 !gl-p-4 gl-border-b-solid" class="gl-flex gl-min-h-8 gl-items-center gl-border-b-1 gl-border-b-dropdown !gl-p-4 gl-border-b-solid"
> >
<span class="gl-grow gl-pr-2 gl-text-sm gl-font-bold"> <span class="gl-grow gl-pr-2 gl-text-sm gl-font-bold">
{{ n__('%d pending comment', '%d pending comments', draftsCount) }} {{ n__('%d pending comment', '%d pending comments', draftsCount) }}

View File

@ -133,6 +133,7 @@ export default {
:column-index="columnIndex" :column-index="columnIndex"
@toggleNewForm="toggleNewForm" @toggleNewForm="toggleNewForm"
@setFilters="$emit('setFilters', $event)" @setFilters="$emit('setFilters', $event)"
@cannot-find-active-item="$emit('cannot-find-active-item')"
/> />
</div> </div>
<div <div

View File

@ -7,6 +7,7 @@ import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue'; import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue'; import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { removeParams, updateHistory } from '~/lib/utils/url_utility';
import { defaultSortableOptions, DRAG_DELAY } from '~/sortable/constants'; import { defaultSortableOptions, DRAG_DELAY } from '~/sortable/constants';
import { mapWorkItemWidgetsToIssuableFields } from '~/issues/list/utils'; import { mapWorkItemWidgetsToIssuableFields } from '~/issues/list/utils';
import { import {
@ -19,6 +20,7 @@ import {
DEFAULT_BOARD_LIST_ITEMS_SIZE, DEFAULT_BOARD_LIST_ITEMS_SIZE,
BoardType, BoardType,
} from 'ee_else_ce/boards/constants'; } from 'ee_else_ce/boards/constants';
import { DETAIL_VIEW_QUERY_PARAM_NAME } from '~/work_items/constants';
import { calculateNewPosition } from 'ee_else_ce/boards/boards_util'; import { calculateNewPosition } from 'ee_else_ce/boards/boards_util';
import { setError } from '../graphql/cache_updates'; import { setError } from '../graphql/cache_updates';
import BoardColumn from './board_column.vue'; import BoardColumn from './board_column.vue';
@ -88,6 +90,7 @@ export default {
return { return {
boardHeight: null, boardHeight: null,
highlightedLists: [], highlightedLists: [],
columnsThatCannotFindActiveItem: 0,
}; };
}, },
computed: { computed: {
@ -245,6 +248,14 @@ export default {
isLastList(index) { isLastList(index) {
return this.boardListsToUse.length - 1 === index; return this.boardListsToUse.length - 1 === index;
}, },
handleCannotFindActiveItem() {
this.columnsThatCannotFindActiveItem += 1;
if (this.columnsThatCannotFindActiveItem === this.boardListsToUse.length) {
updateHistory({
url: removeParams([DETAIL_VIEW_QUERY_PARAM_NAME]),
});
}
},
}, },
}; };
</script> </script>
@ -281,6 +292,7 @@ export default {
@setActiveList="$emit('setActiveList', $event)" @setActiveList="$emit('setActiveList', $event)"
@setFilters="$emit('setFilters', $event)" @setFilters="$emit('setFilters', $event)"
@addNewListAfter="$emit('setAddColumnFormVisibility', $event)" @addNewListAfter="$emit('setAddColumnFormVisibility', $event)"
@cannot-find-active-item="handleCannotFindActiveItem"
/> />
<transition mode="out-in" name="slide" @after-enter="afterFormEnters"> <transition mode="out-in" name="slide" @after-enter="afterFormEnters">

View File

@ -7,6 +7,7 @@ import { ESC_KEY_CODE } from '~/lib/utils/keycodes';
import { defaultSortableOptions, DRAG_DELAY } from '~/sortable/constants'; import { defaultSortableOptions, DRAG_DELAY } from '~/sortable/constants';
import { sortableStart, sortableEnd } from '~/sortable/utils'; import { sortableStart, sortableEnd } from '~/sortable/utils';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { getParameterByName } from '~/lib/utils/url_utility';
import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql'; import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql';
import BoardNewIssue from 'ee_else_ce/boards/components/board_new_issue.vue'; import BoardNewIssue from 'ee_else_ce/boards/components/board_new_issue.vue';
@ -18,6 +19,7 @@ import {
listIssuablesQueries, listIssuablesQueries,
ListType, ListType,
} from 'ee_else_ce/boards/constants'; } from 'ee_else_ce/boards/constants';
import { DETAIL_VIEW_QUERY_PARAM_NAME } from '~/work_items/constants';
import { import {
addItemToList, addItemToList,
removeItemFromList, removeItemFromList,
@ -90,6 +92,7 @@ export default {
addItemToListInProgress: false, addItemToListInProgress: false,
updateIssueOrderInProgress: false, updateIssueOrderInProgress: false,
dragCancelled: false, dragCancelled: false,
hasMadeDrawerAttempt: false,
}; };
}, },
apollo: { apollo: {
@ -128,6 +131,28 @@ export default {
message: s__('Boards|An error occurred while fetching a list. Please try again.'), message: s__('Boards|An error occurred while fetching a list. Please try again.'),
}); });
}, },
result({ data }) {
if (this.hasMadeDrawerAttempt) {
return;
}
const queryParam = getParameterByName(DETAIL_VIEW_QUERY_PARAM_NAME);
if (!data || !queryParam) {
return;
}
const { iid, full_path: fullPath } = JSON.parse(atob(queryParam));
const boardItem = this.boardListItems.find(
(item) => item.iid === iid && item.referencePath.includes(fullPath),
);
if (boardItem) {
this.setActiveWorkItem(boardItem);
} else {
this.$emit('cannot-find-active-item');
}
this.hasMadeDrawerAttempt = true;
},
}, },
toList: { toList: {
query() { query() {
@ -603,16 +628,19 @@ export default {
}); });
} finally { } finally {
this.addItemToListInProgress = false; this.addItemToListInProgress = false;
this.$apollo.mutate({ this.setActiveWorkItem(issuable);
mutation: setActiveBoardItemMutation,
variables: {
boardItem: issuable,
listId: this.list.id,
isIssue: this.isIssueBoard,
},
});
} }
}, },
setActiveWorkItem(boardItem) {
this.$apollo.mutate({
mutation: setActiveBoardItemMutation,
variables: {
boardItem,
listId: this.list.id,
isIssue: this.isIssueBoard,
},
});
},
}, },
}; };
</script> </script>

View File

@ -145,14 +145,18 @@ export default {
return Number(this.pageInfo.hasNextPage); return Number(this.pageInfo.hasNextPage);
}, },
fields() { fields() {
return [ if (this.canBulkDestroyArtifacts) {
this.canBulkDestroyArtifacts && { return [
key: 'checkbox', {
label: '', key: 'checkbox',
thClass: 'gl-w-1/20', label: '',
}, thClass: 'gl-w-1/20',
...this.$options.fields, },
]; ...this.$options.fields,
];
}
return this.$options.fields;
}, },
anyArtifactsSelected() { anyArtifactsSelected() {
return Boolean(this.selectedArtifacts.length); return Boolean(this.selectedArtifacts.length);

View File

@ -180,7 +180,7 @@ export default {
:data-testid="testid" :data-testid="testid"
> >
<template #list-item> <template #list-item>
<div class="-gl-my-1 -gl-ml-2 gl-flex gl-items-center gl-justify-between"> <div class="-gl-my-2 -gl-ml-2 gl-flex gl-items-center gl-justify-between">
<job-name-component <job-name-component
v-gl-tooltip.viewport.left v-gl-tooltip.viewport.left
:title="tooltipText" :title="tooltipText"

View File

@ -137,7 +137,7 @@ export default {
<template #header> <template #header>
<div <div
class="gl-flex gl-min-h-8 gl-items-center gl-border-b-1 gl-border-b-gray-200 !gl-p-4 gl-text-sm gl-font-bold gl-leading-1 gl-border-b-solid" class="gl-flex gl-min-h-8 gl-items-center gl-border-b-1 gl-border-b-dropdown !gl-p-4 gl-text-sm gl-font-bold gl-leading-1 gl-border-b-solid"
> >
<template v-if="isLoading"> <template v-if="isLoading">
<span>{{ $options.i18n.stage }}</span> <span>{{ $options.i18n.stage }}</span>

View File

@ -133,7 +133,7 @@ export default {
<template #header> <template #header>
<div <div
data-testid="pipeline-stage-dropdown-menu-title" data-testid="pipeline-stage-dropdown-menu-title"
class="gl-flex gl-min-h-8 gl-items-center gl-border-b-1 gl-border-b-gray-200 !gl-p-4 gl-text-sm gl-font-bold gl-leading-1 gl-border-b-solid" class="gl-flex gl-min-h-8 gl-items-center gl-border-b-1 gl-border-b-dropdown !gl-p-4 gl-text-sm gl-font-bold gl-leading-1 gl-border-b-solid"
> >
<span>{{ dropdownHeaderText }}</span> <span>{{ dropdownHeaderText }}</span>
</div> </div>

View File

@ -144,11 +144,11 @@ export default {
<template #header> <template #header>
<div <div
aria-hidden="true" aria-hidden="true"
class="gl-flex gl-min-h-8 gl-items-center gl-border-b-1 gl-border-b-gray-200 !gl-p-4 gl-text-sm gl-font-bold gl-text-gray-900 gl-border-b-solid" class="gl-flex gl-min-h-8 gl-items-center gl-border-b-1 gl-border-b-dropdown !gl-p-4 gl-text-sm gl-font-bold gl-text-gray-900 gl-border-b-solid"
> >
{{ $options.i18n.downloadArtifacts }} {{ $options.i18n.downloadArtifacts }}
</div> </div>
<div v-if="hasArtifacts" class="gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid"> <div v-if="hasArtifacts" class="gl-border-b-1 gl-border-b-dropdown gl-border-b-solid">
<gl-search-box-by-type <gl-search-box-by-type
ref="searchInput" ref="searchInput"
v-model.trim="searchQuery" v-model.trim="searchQuery"

View File

@ -89,7 +89,7 @@ export default {
<template v-if="shouldRenderCreateButton" #footer> <template v-if="shouldRenderCreateButton" #footer>
<gl-button <gl-button
category="tertiary" category="tertiary"
class="!gl-justify-start !gl-rounded-tl-none !gl-rounded-tr-none !gl-border-t-1 gl-border-t-gray-200 !gl-pl-7 gl-border-t-solid" class="!gl-justify-start !gl-rounded-tl-none !gl-rounded-tr-none !gl-border-t-1 gl-border-t-dropdown !gl-pl-7 gl-border-t-solid"
:class="{ 'gl-mt-3': !filteredResults.length }" :class="{ 'gl-mt-3': !filteredResults.length }"
@click="selectAgent(searchTerm)" @click="selectAgent(searchTerm)"
> >

View File

@ -170,7 +170,7 @@ export default {
<gl-search-box-by-type <gl-search-box-by-type
ref="searchValue" ref="searchValue"
v-model="searchValue" v-model="searchValue"
class="add-reaction-search gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid" class="add-reaction-search gl-border-b-1 gl-border-b-dropdown gl-border-b-solid"
borderless borderless
autofocus autofocus
debounce="500" debounce="500"
@ -218,7 +218,7 @@ export default {
<template v-if="newCustomEmojiPath" #footer> <template v-if="newCustomEmojiPath" #footer>
<div <div
class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-gray-200 !gl-p-2 !gl-pt-0 gl-border-t-solid" class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-dropdown !gl-p-2 !gl-pt-0 gl-border-t-solid"
> >
<gl-button <gl-button
:href="newCustomEmojiPath" :href="newCustomEmojiPath"

View File

@ -37,7 +37,7 @@ export default {
'gl-text-base', 'gl-text-base',
'gl-font-normal', 'gl-font-normal',
'gl-leading-normal', 'gl-leading-normal',
'gl-shadow-inner-1-gray-200', 'gl-shadow-inner-1-dropdown',
'gl-py-3', 'gl-py-3',
'gl-px-4', 'gl-px-4',
'gl-mb-0', 'gl-mb-0',

View File

@ -135,7 +135,7 @@ export default {
<template v-if="shouldRenderSelectButton" #footer> <template v-if="shouldRenderSelectButton" #footer>
<gl-button <gl-button
category="tertiary" category="tertiary"
class="!gl-justify-start !gl-rounded-tl-none !gl-rounded-tr-none !gl-border-t-1 gl-border-t-gray-200 !gl-pl-7 gl-border-t-solid" class="!gl-justify-start !gl-rounded-tl-none !gl-rounded-tr-none !gl-border-t-1 gl-border-t-dropdown !gl-pl-7 gl-border-t-solid"
:class="{ 'gl-mt-3': !filteredNamespacesList.length }" :class="{ 'gl-mt-3': !filteredNamespacesList.length }"
@click="onSelect(searchTerm)" @click="onSelect(searchTerm)"
> >

View File

@ -324,7 +324,7 @@ export default {
<gl-dropdown <gl-dropdown
:text="__('Recent searches')" :text="__('Recent searches')"
class="filtered-search-history-dropdown-wrapper" class="filtered-search-history-dropdown-wrapper"
toggle-class="filtered-search-history-dropdown-toggle-button !gl-shadow-none !gl-border-r-gray-200 !gl-border-1 !gl-rounded-none" toggle-class="filtered-search-history-dropdown-toggle-button !gl-shadow-none !gl-border-r-dropdown !gl-border-1 !gl-rounded-none"
:disabled="loading" :disabled="loading"
> >
<div v-if="!$options.hasLocalStorage" class="gl-px-5"> <div v-if="!$options.hasLocalStorage" class="gl-px-5">

View File

@ -99,7 +99,7 @@ export default {
<template #footer> <template #footer>
<div <div
v-if="isCreateEnvironmentShown" v-if="isCreateEnvironmentShown"
class="gl-border-t-1 gl-border-t-gray-200 gl-p-2 gl-border-t-solid" class="gl-border-t-1 gl-border-t-dropdown gl-p-2 gl-border-t-solid"
> >
<gl-button <gl-button
category="tertiary" category="tertiary"

View File

@ -25,10 +25,11 @@ export default {
</script> </script>
<template> <template>
<settings-block id="incident-management-settings" data-testid="incidents-settings-content"> <settings-block
<template #title> id="incident-management-settings"
<span ref="sectionHeader">{{ $options.i18n.headerText }}</span> :title="$options.i18n.headerText"
</template> data-testid="incidents-settings-content"
>
<template #description> <template #description>
<span ref="sectionSubHeader">{{ $options.i18n.subHeaderText }}</span> <span ref="sectionSubHeader">{{ $options.i18n.subHeaderText }}</span>
</template> </template>

View File

@ -48,7 +48,12 @@ import axios from '~/lib/utils/axios_utils';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
import { isPositiveInteger } from '~/lib/utils/number_utils'; import { isPositiveInteger } from '~/lib/utils/number_utils';
import { scrollUp } from '~/lib/utils/scroll_utils'; import { scrollUp } from '~/lib/utils/scroll_utils';
import { getParameterByName, joinPaths } from '~/lib/utils/url_utility'; import {
getParameterByName,
joinPaths,
removeParams,
updateHistory,
} from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { import {
OPERATORS_IS, OPERATORS_IS,
@ -85,8 +90,12 @@ import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_ro
import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants'; import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue'; import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue';
import { WORK_ITEM_TYPE_ENUM_OBJECTIVE } from '~/work_items/constants'; import {
WORK_ITEM_TYPE_ENUM_OBJECTIVE,
DETAIL_VIEW_QUERY_PARAM_NAME,
} from '~/work_items/constants';
import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue'; import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue';
import { makeDrawerUrlParam } from '~/work_items/utils';
import { import {
CREATED_DESC, CREATED_DESC,
i18n, i18n,
@ -244,6 +253,7 @@ export default {
} }
this.pageInfo = data[this.namespace]?.issues.pageInfo ?? {}; this.pageInfo = data[this.namespace]?.issues.pageInfo ?? {};
this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery(); this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
this.checkDrawerParams();
}, },
error(error) { error(error) {
this.issuesError = this.$options.i18n.errorFetchingIssues; this.issuesError = this.$options.i18n.errorFetchingIssues;
@ -538,7 +548,10 @@ export default {
return this.tabCounts[this.state] ?? 0; return this.tabCounts[this.state] ?? 0;
}, },
urlParams() { urlParams() {
return { const show = this.activeIssuable
? makeDrawerUrlParam(this.activeIssuable, this.fullPath)
: undefined;
const base = {
sort: urlSortParams[this.sortKey], sort: urlSortParams[this.sortKey],
state: this.state, state: this.state,
...this.urlFilterParams, ...this.urlFilterParams,
@ -547,6 +560,10 @@ export default {
page_after: this.pageParams.afterCursor ?? undefined, page_after: this.pageParams.afterCursor ?? undefined,
page_before: this.pageParams.beforeCursor ?? undefined, page_before: this.pageParams.beforeCursor ?? undefined,
}; };
if (show) {
return { ...base, show };
}
return base;
}, },
// due to the issues with cache-and-network, we need this hack to check if there is any data for the query in the cache. // due to the issues with cache-and-network, we need this hack to check if there is any data for the query in the cache.
// if we have cached data, we disregard the loading state // if we have cached data, we disregard the loading state
@ -577,6 +594,11 @@ export default {
if (newValue.fullPath !== oldValue.fullPath) { if (newValue.fullPath !== oldValue.fullPath) {
this.updateData(getParameterByName(PARAM_SORT)); this.updateData(getParameterByName(PARAM_SORT));
} }
if (newValue.query.show) {
this.checkDrawerParams();
} else {
this.activeIssuable = null;
}
}, },
}, },
created() { created() {
@ -820,7 +842,6 @@ export default {
handleSelectIssuable(issuable) { handleSelectIssuable(issuable) {
this.activeIssuable = { this.activeIssuable = {
...issuable, ...issuable,
fullPath: this.fullPath,
}; };
}, },
updateIssuablesCache(workItem) { updateIssuablesCache(workItem) {
@ -831,9 +852,13 @@ export default {
}); });
const activeIssuable = issuesList[this.namespace].issues.nodes.find( const activeIssuable = issuesList[this.namespace].issues.nodes.find(
(issue) => issue.iid === workItem.iid, (issue) => getIdFromGraphQLId(issue.id) === getIdFromGraphQLId(workItem.id),
); );
if (!activeIssuable) {
return;
}
// when we change issuable state, it's moved to a different tab // when we change issuable state, it's moved to a different tab
// to ensure that we show 20 items of the first page, we need to refetch issuables // to ensure that we show 20 items of the first page, we need to refetch issuables
if (!activeIssuable.state.includes(workItem.state.toLowerCase())) { if (!activeIssuable.state.includes(workItem.state.toLowerCase())) {
@ -883,6 +908,29 @@ export default {
client.writeQuery({ query: getIssuesQuery, variables: this.queryVariables, data }); client.writeQuery({ query: getIssuesQuery, variables: this.queryVariables, data });
}, },
checkDrawerParams() {
const queryParam = getParameterByName(DETAIL_VIEW_QUERY_PARAM_NAME);
if (this.activeIssuable || !queryParam) {
return;
}
const params = JSON.parse(atob(queryParam));
if (params.id) {
const issue = this.issues.find((i) => getIdFromGraphQLId(i.id) === params.id);
if (issue) {
this.activeIssuable = {
...issue,
// we need fullPath here to prevent cache invalidation
fullPath: params.full_path,
};
} else {
updateHistory({
url: removeParams([DETAIL_VIEW_QUERY_PARAM_NAME]),
});
}
}
},
}, },
}; };
</script> </script>

View File

@ -314,7 +314,7 @@ export default {
<template #footer> <template #footer>
<div <div
class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-gray-200 !gl-p-2 !gl-pt-0 gl-border-t-solid" class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-dropdown !gl-p-2 !gl-pt-0 gl-border-t-solid"
> >
<gl-button <gl-button
category="tertiary" category="tertiary"

View File

@ -44,8 +44,11 @@ export default {
}, },
}, },
watch: { watch: {
count(newVal) { count: {
this.open = newVal > 0; handler(newVal) {
this.open = newVal > 0;
},
immediate: true,
}, },
}, },
methods: { methods: {

View File

@ -187,7 +187,7 @@ export default {
<template v-if="directlyInviteMembers" #footer> <template v-if="directlyInviteMembers" #footer>
<div <div
class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-gray-200 !gl-p-2 !gl-pt-0 gl-border-t-solid" class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-dropdown !gl-p-2 !gl-pt-0 gl-border-t-solid"
> >
<invite-members-trigger <invite-members-trigger
trigger-element="button" trigger-element="button"

View File

@ -171,7 +171,7 @@ export default {
</template> </template>
<template #footer> <template #footer>
<div <div
class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-gray-200 !gl-p-2 !gl-pt-0 gl-border-t-solid" class="gl-flex gl-flex-col gl-border-t-1 gl-border-t-dropdown !gl-p-2 !gl-pt-0 gl-border-t-solid"
> >
<gl-button <gl-button
v-for="(item, idx) in extraLinks" v-for="(item, idx) in extraLinks"

View File

@ -214,11 +214,11 @@ export default {
@action="setPromoteModalVisibility(true)" @action="setPromoteModalVisibility(true)"
/> />
<gl-disclosure-dropdown-group v-if="canReadMilestone" bordered class="!gl-border-t-gray-200"> <gl-disclosure-dropdown-group v-if="canReadMilestone" bordered class="!gl-border-t-dropdown">
<gl-disclosure-dropdown-item :item="copyIdItem" :data-clipboard-text="id" /> <gl-disclosure-dropdown-item :item="copyIdItem" :data-clipboard-text="id" />
</gl-disclosure-dropdown-group> </gl-disclosure-dropdown-group>
<gl-disclosure-dropdown-group v-if="showDelete" bordered class="!gl-border-t-gray-200"> <gl-disclosure-dropdown-group v-if="showDelete" bordered class="!gl-border-t-dropdown">
<gl-disclosure-dropdown-item :item="deleteItem" @action="setDeleteModalVisibility(true)" /> <gl-disclosure-dropdown-item :item="deleteItem" @action="setDeleteModalVisibility(true)" />
</gl-disclosure-dropdown-group> </gl-disclosure-dropdown-group>

View File

@ -16,8 +16,7 @@ export default {
</script> </script>
<template> <template>
<settings-block id="organization-settings-advanced"> <settings-block id="organization-settings-advanced" :title="$options.i18n.settingsBlock.title">
<template #title>{{ $options.i18n.settingsBlock.title }}</template>
<template #description>{{ $options.i18n.settingsBlock.description }}</template> <template #description>{{ $options.i18n.settingsBlock.description }}</template>
<template #default> <template #default>
<change-url /> <change-url />

View File

@ -100,8 +100,11 @@ export default {
</script> </script>
<template> <template>
<settings-block id="organization-settings" default-expanded> <settings-block
<template #title>{{ $options.i18n.settingsBlock.title }}</template> id="organization-settings"
:title="$options.i18n.settingsBlock.title"
default-expanded
>
<template #description>{{ $options.i18n.settingsBlock.description }}</template> <template #description>{{ $options.i18n.settingsBlock.description }}</template>
<template #default> <template #default>
<form-errors-alert v-model="errors" :scroll-on-error="true" /> <form-errors-alert v-model="errors" :scroll-on-error="true" />

View File

@ -48,8 +48,7 @@ export default {
</script> </script>
<template> <template>
<settings-block id="organization-settings-visibility"> <settings-block id="organization-settings-visibility" :title="$options.i18n.settingsBlock.title">
<template #title>{{ $options.i18n.settingsBlock.title }}</template>
<template #description>{{ $options.i18n.settingsBlock.description }}</template> <template #description>{{ $options.i18n.settingsBlock.description }}</template>
<template #default> <template #default>
<gl-form :id="$options.formId"> <gl-form :id="$options.formId">

View File

@ -70,7 +70,7 @@ export default {
<div data-testid="tag-name-search"> <div data-testid="tag-name-search">
<gl-search-box-by-type <gl-search-box-by-type
:value="query" :value="query"
class="gl-border-b-1 gl-border-gray-200 gl-border-b-solid" class="gl-border-b-1 gl-border-dropdown gl-border-b-solid"
borderless borderless
autofocus autofocus
@input="onSearchBoxInput" @input="onSearchBoxInput"
@ -95,7 +95,7 @@ export default {
{{ $options.i18n.noResults }} {{ $options.i18n.noResults }}
</div> </div>
</div> </div>
<div class="gl-border-t-1 gl-border-gray-200 gl-py-3 gl-border-t-solid"> <div class="gl-border-t-1 gl-border-dropdown gl-py-3 gl-border-t-solid">
<gl-button <gl-button
category="tertiary" category="tertiary"
class="!gl-justify-start !gl-rounded-none" class="!gl-justify-start !gl-rounded-none"

View File

@ -240,7 +240,7 @@ export default {
v-if="isFocused" v-if="isFocused"
v-outside.click.focusin="closeDropdown" v-outside.click.focusin="closeDropdown"
data-testid="header-search-dropdown-menu" data-testid="header-search-dropdown-menu"
class="header-search-dropdown-menu gl-absolute gl-z-2 gl-mt-3 !gl-w-full !gl-min-w-full !gl-max-w-none gl-overflow-y-auto gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-200 gl-bg-white gl-shadow-x0-y2-b4-s0" class="header-search-dropdown-menu gl-absolute gl-z-2 gl-mt-3 !gl-w-full !gl-min-w-full !gl-max-w-none gl-overflow-y-auto gl-rounded-base gl-border-1 gl-border-solid gl-border-dropdown gl-bg-white gl-shadow-x0-y2-b4-s0"
> >
<div class="header-search-dropdown-content gl-py-2"> <div class="header-search-dropdown-content gl-py-2">
<dropdown-keyboard-navigation <dropdown-keyboard-navigation

View File

@ -18,8 +18,14 @@ export function isExpanded(sectionArg) {
export function expandSection(sectionArg) { export function expandSection(sectionArg) {
const $section = $(sectionArg); const $section = $(sectionArg);
const title = $section.find('.js-settings-toggle-trigger-only').text();
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse')); $section
.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only) .gl-button-text')
.text(__('Collapse'));
$section
.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)')
.attr('aria-label', `${__('Collapse')} ${title}`);
$section.addClass('expanded'); $section.addClass('expanded');
if (!$section.hasClass('no-animate')) { if (!$section.hasClass('no-animate')) {
$section $section
@ -34,8 +40,15 @@ export function expandSection(sectionArg) {
export function closeSection(sectionArg) { export function closeSection(sectionArg) {
const $section = $(sectionArg); const $section = $(sectionArg);
const title = $section.find('.js-settings-toggle-trigger-only').text();
$section
.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only) .gl-button-text')
.text(__('Expand'));
$section
.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)')
.attr('aria-label', `${__('Expand')} ${title}`);
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Expand'));
$section.removeClass('expanded'); $section.removeClass('expanded');
if (!$section.hasClass('no-animate')) { if (!$section.hasClass('no-animate')) {
$section $section

View File

@ -68,7 +68,7 @@ export default {
</button> </button>
</template> </template>
<template #header> <template #header>
<span class="gl-border-b-1 gl-border-gray-200 gl-p-4 gl-border-b-solid"> <span class="gl-border-b-1 gl-border-dropdown gl-p-4 gl-border-b-solid">
{{ $options.i18n.header }} {{ $options.i18n.header }}
</span> </span>
</template> </template>

View File

@ -163,7 +163,7 @@ export default {
</template> </template>
<template v-if="!organizationSwitchingEnabled" #footer> <template v-if="!organizationSwitchingEnabled" #footer>
<div class="gl-border-t gl-mt-2 gl-border-t-gray-200 gl-px-4 gl-pt-3"> <div class="gl-border-t gl-mt-2 gl-border-t-dropdown gl-px-4 gl-pt-3">
<div class="gl-text-sm gl-font-bold"> <div class="gl-text-sm gl-font-bold">
{{ $options.i18n.switchOrganizations }} {{ $options.i18n.switchOrganizations }}
</div> </div>

View File

@ -1,5 +1,13 @@
<script> <script>
import { GlLoadingIcon, GlKeysetPagination, GlButton, GlBadge, GlTab, GlTabs } from '@gitlab/ui'; import {
GlLoadingIcon,
GlKeysetPagination,
GlLink,
GlButton,
GlBadge,
GlTab,
GlTabs,
} from '@gitlab/ui';
import * as Sentry from '~/sentry/sentry_browser_wrapper'; import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { createAlert } from '~/alert'; import { createAlert } from '~/alert';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
@ -14,6 +22,7 @@ const STATUS_BY_TAB = [['pending'], ['done'], ['pending', 'done']];
export default { export default {
components: { components: {
GlLink,
GlLoadingIcon, GlLoadingIcon,
GlKeysetPagination, GlKeysetPagination,
GlButton, GlButton,
@ -209,6 +218,12 @@ export default {
@prev="prevPage" @prev="prevPage"
@next="nextPage" @next="nextPage"
/> />
<div class="gl-mt-5 gl-text-center">
<gl-link href="https://gitlab.com/gitlab-org/gitlab/-/issues/498315" target="_blank">{{
s__('Todos|Leave feedback')
}}</gl-link>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -79,9 +79,9 @@ export default {
type: String, type: String,
required: false, required: false,
}, },
pipelineIid: { pipelineMiniGraphVariables: {
type: Number, type: Object,
required: false, required: true,
}, },
buildsWithCoverage: { buildsWithCoverage: {
type: Array, type: Array,
@ -128,10 +128,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
sourceProjectFullPath: {
type: String,
required: true,
},
targetProjectFullPath: { targetProjectFullPath: {
type: String, type: String,
required: true, required: true,
@ -155,13 +151,14 @@ export default {
return this.hasPipeline && !this.ciStatus; return this.hasPipeline && !this.ciStatus;
}, },
status() { status() {
return this.pipeline.details && this.pipeline.details.status return this.pipeline?.details?.status || {};
? this.pipeline.details.status
: {};
}, },
artifacts() { artifacts() {
return this.pipeline?.details?.artifacts; return this.pipeline?.details?.artifacts;
}, },
hasArtifacts() {
return Boolean(this.pipeline?.details?.artifacts?.length);
},
hasStages() { hasStages() {
return this.pipeline?.details?.stages?.length > 0; return this.pipeline?.details?.stages?.length > 0;
}, },
@ -210,9 +207,6 @@ export default {
this.buildsWithCoverage.length, this.buildsWithCoverage.length,
); );
}, },
pipelineMiniGraphQueryId() {
return this.pipelineIid?.toString() || null;
},
isMergeTrain() { isMergeTrain() {
return Boolean(this.pipeline.flags?.merge_train_pipeline); return Boolean(this.pipeline.flags?.merge_train_pipeline);
}, },
@ -307,9 +301,9 @@ export default {
<div class="gl-inline-flex gl-grow gl-items-center gl-justify-between"> <div class="gl-inline-flex gl-grow gl-items-center gl-justify-between">
<div> <div>
<pipeline-mini-graph <pipeline-mini-graph
v-if="isGraphQLPipelineMiniGraph && pipelineMiniGraphQueryId" v-if="isGraphQLPipelineMiniGraph && pipelineMiniGraphVariables.iid"
:iid="pipelineMiniGraphQueryId" :iid="pipelineMiniGraphVariables.iid"
:full-path="sourceProjectFullPath" :full-path="pipelineMiniGraphVariables.fullPath"
:is-merge-train="isMergeTrain" :is-merge-train="isMergeTrain"
:pipeline-etag="pipelineEtag" :pipeline-etag="pipelineEtag"
/> />
@ -323,6 +317,7 @@ export default {
/> />
</div> </div>
<pipeline-artifacts <pipeline-artifacts
v-if="hasArtifacts"
:pipeline-id="pipeline.id" :pipeline-id="pipeline.id"
:artifacts="artifacts" :artifacts="artifacts"
class="gl-ml-3" class="gl-ml-3"

View File

@ -1,8 +1,14 @@
<script> <script>
import { sanitize } from '~/lib/dompurify'; import { sanitize } from '~/lib/dompurify';
import { n__ } from '~/locale'; import { n__, __ } from '~/locale';
import { createAlert } from '~/alert';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPENAME_CI_PIPELINE } from '~/graphql_shared/constants';
import { getQueryHeaders, toggleQueryPollingByVisibility } from '~/ci/pipeline_details/graph/utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { PIPELINE_MINI_GRAPH_POLL_INTERVAL } from '~/ci/pipeline_details/constants';
import MergeRequestStore from '../stores/mr_widget_store'; import MergeRequestStore from '../stores/mr_widget_store';
import getMergePipeline from '../queries/get_merge_pipeline.query.graphql';
import ArtifactsApp from './artifacts_list_app.vue'; import ArtifactsApp from './artifacts_list_app.vue';
import DeploymentList from './deployment/deployment_list.vue'; import DeploymentList from './deployment/deployment_list.vue';
import MrWidgetContainer from './mr_widget_container.vue'; import MrWidgetContainer from './mr_widget_container.vue';
@ -38,7 +44,39 @@ export default {
default: false, default: false,
}, },
}, },
data() {
return {
mergePipeline: {},
};
},
apollo: {
mergePipeline: {
context() {
return getQueryHeaders(this.mr.pipelineEtag);
},
query: getMergePipeline,
skip() {
return !this.useMergePipelineQuery;
},
variables() {
return {
fullPath: this.mr.targetProjectFullPath,
id: convertToGraphQLId(TYPENAME_CI_PIPELINE, this.mr.mergePipeline.id),
};
},
pollInterval: PIPELINE_MINI_GRAPH_POLL_INTERVAL,
update({ project }) {
return project?.pipeline || {};
},
error() {
createAlert({ message: __('There was a problem fetching the merge pipeline.') });
},
},
},
computed: { computed: {
useMergePipelineQuery() {
return this.isPostMerge && this.glFeatures?.ciGraphqlPipelineMiniGraph;
},
branch() { branch() {
return this.isPostMerge ? this.mr.targetBranch : this.mr.sourceBranch; return this.isPostMerge ? this.mr.targetBranch : this.mr.sourceBranch;
}, },
@ -57,6 +95,17 @@ export default {
pipeline() { pipeline() {
return this.isPostMerge ? this.mr.mergePipeline : this.mr.pipeline; return this.isPostMerge ? this.mr.mergePipeline : this.mr.pipeline;
}, },
pipelineMiniGraphVariables() {
return this.isPostMerge
? {
fullPath: this.mergePipeline?.project?.fullPath,
iid: this.mergePipeline?.iid,
}
: {
fullPath: this.mr.pipelineProjectPath || '',
iid: this.mr.pipelineIid || '',
};
},
showCollapsedDeployments() { showCollapsedDeployments() {
return this.deployments.length > 3; return this.deployments.length > 3;
}, },
@ -74,6 +123,11 @@ export default {
return this.isPostMerge ? this.mr?.mergePipeline?.details?.status?.text : this.mr.ciStatus; return this.isPostMerge ? this.mr?.mergePipeline?.details?.status?.text : this.mr.ciStatus;
}, },
}, },
mounted() {
if (this.useMergePipelineQuery) {
toggleQueryPollingByVisibility(this.$apollo.queries.mergePipeline);
}
},
}; };
</script> </script>
<template> <template>
@ -82,7 +136,7 @@ export default {
:pipeline="pipeline" :pipeline="pipeline"
:pipeline-coverage-delta="mr.pipelineCoverageDelta" :pipeline-coverage-delta="mr.pipelineCoverageDelta"
:pipeline-etag="mr.pipelineEtag" :pipeline-etag="mr.pipelineEtag"
:pipeline-iid="mr.pipelineIid" :pipeline-mini-graph-variables="pipelineMiniGraphVariables"
:builds-with-coverage="mr.buildsWithCoverage" :builds-with-coverage="mr.buildsWithCoverage"
:ci-status="ciStatus" :ci-status="ciStatus"
:has-ci="mr.hasCI" :has-ci="mr.hasCI"
@ -95,7 +149,6 @@ export default {
:retargeted="mr.retargeted" :retargeted="mr.retargeted"
:target-project-id="mr.targetProjectId" :target-project-id="mr.targetProjectId"
:iid="mr.iid" :iid="mr.iid"
:source-project-full-path="mr.sourceProjectFullPath"
:target-project-full-path="mr.targetProjectFullPath" :target-project-full-path="mr.targetProjectFullPath"
/> />
<template #footer> <template #footer>

View File

@ -0,0 +1,13 @@
query getMergePipeline($fullPath: ID!, $id: CiPipelineID!) {
project(fullPath: $fullPath) {
id
pipeline(id: $id) {
id
iid
project {
id
fullPath
}
}
}
}

View File

@ -3,6 +3,7 @@ import { STATUS_CLOSED, STATUS_MERGED, STATUS_OPEN } from '~/issues/constants';
import { formatDate, getTimeago, newDate, timeagoLanguageCode } from '~/lib/utils/datetime_utility'; import { formatDate, getTimeago, newDate, timeagoLanguageCode } from '~/lib/utils/datetime_utility';
import { machine } from '~/lib/utils/finite_state_machine'; import { machine } from '~/lib/utils/finite_state_machine';
import { badgeState } from '~/merge_requests/components/merge_request_header.vue'; import { badgeState } from '~/merge_requests/components/merge_request_header.vue';
import { cleanLeadingSeparator } from '~/lib/utils/url_utility';
import { import {
MTWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY,
MT_MERGE_STRATEGY, MT_MERGE_STRATEGY,
@ -141,7 +142,10 @@ export default class MergeRequestStore {
this.isPipelineSkipped = this.ciStatus === 'skipped'; this.isPipelineSkipped = this.ciStatus === 'skipped';
this.pipelineDetailedStatus = pipelineStatus; this.pipelineDetailedStatus = pipelineStatus;
this.isPipelineActive = data.pipeline ? data.pipeline.active : false; this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
this.pipelineIid = data.pipeline?.iid; this.pipelineIid = data.pipeline?.iid?.toString() || '';
this.pipelineProjectPath = data.pipeline?.project_path
? cleanLeadingSeparator(data.pipeline?.project_path)
: '';
this.isPipelineBlocked = this.isPipelineBlocked =
data.only_allow_merge_if_pipeline_succeeds && pipelineStatus?.group === 'manual'; data.only_allow_merge_if_pipeline_succeeds && pipelineStatus?.group === 'manual';
this.faviconOverlayPath = data.favicon_overlay_path; this.faviconOverlayPath = data.favicon_overlay_path;

View File

@ -47,9 +47,11 @@ export default {
> >
<gl-sprintf :message="$options.i18n.message"> <gl-sprintf :message="$options.i18n.message">
<template #link="{ content }"> <template #link="{ content }">
<help-page-link href="subscriptions/gitlab_com/index" anchor="change-the-linked-group">{{ <help-page-link
content href="subscriptions/gitlab_com/index"
}}</help-page-link> anchor="link-subscription-to-a-group"
>{{ content }}</help-page-link
>
</template> </template>
</gl-sprintf> </gl-sprintf>
</gl-modal> </gl-modal>

View File

@ -173,7 +173,7 @@ export default {
> >
<template #header> <template #header>
<div <div
class="gl-min-h-8 gl-border-b-1 gl-border-b-gray-200 !gl-p-4 gl-text-sm gl-font-bold gl-border-b-solid" class="gl-min-h-8 gl-border-b-1 gl-border-b-dropdown !gl-p-4 gl-text-sm gl-font-bold gl-border-b-solid"
> >
{{ __('Manage') }} {{ __('Manage') }}
</div> </div>

View File

@ -35,6 +35,12 @@ export default {
toggleButtonText() { toggleButtonText() {
return this.expanded ? this.$options.i18n.collapseText : this.$options.i18n.expandText; return this.expanded ? this.$options.i18n.collapseText : this.$options.i18n.expandText;
}, },
toggleButtonAriaLabel() {
return `${this.toggleButtonText} ${this.$scopedSlots.title || this.title}`;
},
expandedClass() {
return this.expanded ? 'expanded' : '';
},
collapseId() { collapseId() {
return this.id || uniqueId('settings-block-'); return this.id || uniqueId('settings-block-');
}, },
@ -54,8 +60,24 @@ export default {
</script> </script>
<template> <template>
<section :id="id" class="vue-settings-block settings no-animate"> <section :id="id" class="vue-settings-block settings no-animate" :class="expandedClass">
<div class="gl-flex gl-items-start gl-justify-between"> <div class="gl-flex gl-items-start gl-justify-between gl-gap-x-3">
<div class="-gl-mr-3 gl-shrink-0 gl-px-2 gl-py-0 sm:gl-mr-0 sm:gl-p-2">
<gl-button
category="tertiary"
size="small"
class="settings-toggle gl-shrink-0 !gl-pl-2 !gl-pr-0"
icon="chevron-lg-right"
button-text-classes="gl-sr-only"
:aria-label="toggleButtonAriaLabel"
:aria-expanded="ariaExpanded"
:aria-controls="collapseId"
data-testid="settings-block-toggle"
@click="toggleExpanded"
>
{{ toggleButtonText }}
</gl-button>
</div>
<div class="gl-grow"> <div class="gl-grow">
<h2 <h2
role="button" role="button"
@ -63,34 +85,16 @@ export default {
class="gl-heading-2 !gl-mb-2 gl-cursor-pointer" class="gl-heading-2 !gl-mb-2 gl-cursor-pointer"
:aria-expanded="ariaExpanded" :aria-expanded="ariaExpanded"
:aria-controls="collapseId" :aria-controls="collapseId"
data-testid="settings-block-title"
@click="toggleExpanded" @click="toggleExpanded"
> >
<slot v-if="$scopedSlots.title" name="title"></slot> {{ title }}
<template v-else>{{ title }}</template>
</h2> </h2>
<p class="gl-m-0 gl-text-subtle"><slot name="description"></slot></p> <p class="gl-m-0 gl-text-subtle"><slot name="description"></slot></p>
</div> </div>
<div class="gl-shrink-0 gl-px-2">
<gl-button
class="gl-min-w-12 gl-shrink-0"
:aria-expanded="ariaExpanded"
:aria-controls="collapseId"
data-testid="settings-block-toggle"
@click="toggleExpanded"
>
<span aria-hidden="true">
{{ toggleButtonText }}
</span>
<span class="gl-sr-only">
{{ toggleButtonText }}
<slot v-if="$scopedSlots.title" name="title"></slot>
<template v-else>{{ title }}</template>
</span>
</gl-button>
</div>
</div> </div>
<gl-collapse :id="collapseId" v-model="expanded" data-testid="settings-block-content"> <gl-collapse :id="collapseId" v-model="expanded" data-testid="settings-block-content">
<div class="gl-pt-5"> <div class="gl-pl-7 gl-pt-5 sm:gl-pl-8">
<slot></slot> <slot></slot>
</div> </div>
</gl-collapse> </gl-collapse>

View File

@ -100,7 +100,9 @@ export default {
return this.issuable.iid; return this.issuable.iid;
}, },
workItemFullPath() { workItemFullPath() {
return this.issuable.namespace?.fullPath; return (
this.issuable.namespace?.fullPath || this.issuable.reference?.split(this.issuableSymbol)[0]
);
}, },
author() { author() {
return this.issuable.author || {}; return this.issuable.author || {};
@ -267,6 +269,7 @@ export default {
return; return;
} }
this.$emit('select-issuable', { this.$emit('select-issuable', {
id: this.issuable.id,
iid: this.issuableIid, iid: this.issuableIid,
webUrl: this.issuable.webUrl, webUrl: this.issuable.webUrl,
fullPath: this.workItemFullPath, fullPath: this.workItemFullPath,

View File

@ -6,6 +6,7 @@ import PageSizeSelector from '~/vue_shared/components/page_size_selector.vue';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility'; import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { DRAG_DELAY } from '~/sortable/constants'; import { DRAG_DELAY } from '~/sortable/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -307,7 +308,9 @@ export default {
this.$emit('page-size-change', newPageSize); this.$emit('page-size-change', newPageSize);
}, },
isIssuableActive(issuable) { isIssuableActive(issuable) {
return Boolean(issuable.iid === this.activeIssuable?.iid); return Boolean(
getIdFromGraphQLId(issuable.id) === getIdFromGraphQLId(this.activeIssuable?.id),
);
}, },
}, },
PAGE_SIZE_STORAGE_KEY, PAGE_SIZE_STORAGE_KEY,

View File

@ -244,7 +244,7 @@ export default {
<slot name="list-item" :item="item">{{ item.text }}</slot> <slot name="list-item" :item="item">{{ item.text }}</slot>
</template> </template>
<template v-if="showFooter" #footer> <template v-if="showFooter" #footer>
<div class="gl-border-t-1 gl-border-t-gray-200 !gl-p-2 gl-border-t-solid"> <div class="gl-border-t-1 gl-border-t-dropdown !gl-p-2 gl-border-t-solid">
<slot name="footer"></slot> <slot name="footer"></slot>
</div> </div>
</template> </template>

View File

@ -482,7 +482,7 @@ export default {
}); });
}, },
openInModal({ event, modalWorkItem, context }) { openInModal({ event, modalWorkItem, context }) {
if (!this.workItemsAlphaEnabled || context === LINKED_ITEMS_ANCHOR) { if (!this.workItemsAlphaEnabled || context === LINKED_ITEMS_ANCHOR || this.isDrawer) {
return; return;
} }
@ -896,7 +896,7 @@ export default {
</section> </section>
</section> </section>
<work-item-detail-modal <work-item-detail-modal
v-if="!isModal" v-if="!isModal && !isDrawer"
ref="modal" ref="modal"
:parent-id="workItem.id" :parent-id="workItem.id"
:work-item-id="modalWorkItemId" :work-item-id="modalWorkItemId"

View File

@ -5,8 +5,10 @@ import { __ } from '~/locale';
import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql'; import deleteWorkItemMutation from '~/work_items/graphql/delete_work_item.mutation.graphql';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants'; import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import { DETAIL_VIEW_QUERY_PARAM_NAME } from '~/work_items/constants';
import * as Sentry from '~/sentry/sentry_browser_wrapper'; import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl, setUrlParams, updateHistory, removeParams } from '~/lib/utils/url_utility';
import { makeDrawerItemFullPath, makeDrawerUrlParam } from '../utils';
export default { export default {
name: 'WorkItemDrawer', name: 'WorkItemDrawer',
@ -51,14 +53,7 @@ export default {
}, },
computed: { computed: {
activeItemFullPath() { activeItemFullPath() {
if (this.activeItem?.fullPath) { return makeDrawerItemFullPath(this.activeItem, this.fullPath, this.issuableType);
return this.activeItem.fullPath;
}
const delimiter = this.issuableType === TYPE_EPIC ? '&' : '#';
if (!this.activeItem.referencePath) {
return undefined;
}
return this.activeItem.referencePath.split(delimiter)[0];
}, },
modalIsGroup() { modalIsGroup() {
return this.issuableType.toLowerCase() === TYPE_EPIC; return this.issuableType.toLowerCase() === TYPE_EPIC;
@ -75,6 +70,17 @@ export default {
); );
}, },
}, },
watch: {
activeItem: {
deep: true,
immediate: true,
handler(newValue) {
if (newValue?.iid) {
this.setDrawerParams();
}
},
},
},
methods: { methods: {
async deleteWorkItem({ workItemId }) { async deleteWorkItem({ workItemId }) {
try { try {
@ -119,6 +125,17 @@ export default {
this.copyTooltipText = this.$options.i18n.copyTooltipText; this.copyTooltipText = this.$options.i18n.copyTooltipText;
}, 2000); }, 2000);
}, },
setDrawerParams() {
const params = makeDrawerUrlParam(this.activeItem, this.fullPath, this.issuableType);
updateHistory({
// we're using `show` to match the modal view parameter
url: setUrlParams({ [DETAIL_VIEW_QUERY_PARAM_NAME]: params }),
});
},
handleClose() {
updateHistory({ url: removeParams([DETAIL_VIEW_QUERY_PARAM_NAME]) });
this.$emit('close');
},
handleClickOutside(event) { handleClickOutside(event) {
for (const selector of this.$options.defaultExcludedSelectors) { for (const selector of this.$options.defaultExcludedSelectors) {
const excludedElements = document.querySelectorAll(selector); const excludedElements = document.querySelectorAll(selector);
@ -136,7 +153,7 @@ export default {
} }
} }
} }
this.$emit('close'); this.handleClose();
}, },
}, },
i18n: { i18n: {
@ -164,7 +181,7 @@ export default {
header-sticky header-sticky
header-height="calc(var(--top-bar-height) + var(--performance-bar-height))" header-height="calc(var(--top-bar-height) + var(--performance-bar-height))"
class="gl-w-full gl-leading-reset lg:gl-w-[480px] xl:gl-w-[768px] min-[1440px]:gl-w-[912px]" class="gl-w-full gl-leading-reset lg:gl-w-[480px] xl:gl-w-[768px] min-[1440px]:gl-w-[912px]"
@close="$emit('close')" @close="handleClose"
> >
<template #title> <template #title>
<div class="gl-text gl-flex gl-w-full gl-items-center gl-gap-x-2 xl:gl-px-4"> <div class="gl-text gl-flex gl-w-full gl-items-center gl-gap-x-2 xl:gl-px-4">

View File

@ -15,7 +15,7 @@ import {
convertToUrlParams, convertToUrlParams,
} from 'ee_else_ce/issues/list/utils'; } from 'ee_else_ce/issues/list/utils';
import { TYPENAME_USER } from '~/graphql_shared/constants'; import { TYPENAME_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { import {
STATUS_ALL, STATUS_ALL,
STATUS_CLOSED, STATUS_CLOSED,
@ -65,8 +65,13 @@ import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_ro
import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue'; import WorkItemDrawer from '~/work_items/components/work_item_drawer.vue';
import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants'; import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { getParameterByName } from '~/lib/utils/url_utility'; import { getParameterByName, removeParams, updateHistory } from '~/lib/utils/url_utility';
import { STATE_CLOSED, STATE_OPEN, WORK_ITEM_TYPE_ENUM_EPIC } from '../constants'; import {
STATE_CLOSED,
STATE_OPEN,
WORK_ITEM_TYPE_ENUM_EPIC,
DETAIL_VIEW_QUERY_PARAM_NAME,
} from '../constants';
import getWorkItemsQuery from '../graphql/list/get_work_items.query.graphql'; import getWorkItemsQuery from '../graphql/list/get_work_items.query.graphql';
import getWorkItemStateCountsQuery from '../graphql/list/get_work_item_state_counts.query.graphql'; import getWorkItemStateCountsQuery from '../graphql/list/get_work_item_state_counts.query.graphql';
import { sortOptions, urlSortParams } from './list/constants'; import { sortOptions, urlSortParams } from './list/constants';
@ -163,6 +168,7 @@ export default {
document.title = `Issues · ${data.project.name} · GitLab`; document.title = `Issues · ${data.project.name} · GitLab`;
} }
} }
this.checkDrawerParams();
}, },
error(error) { error(error) {
this.error = s__( this.error = s__(
@ -416,6 +422,11 @@ export default {
if (newValue.fullPath !== oldValue.fullPath) { if (newValue.fullPath !== oldValue.fullPath) {
this.updateData(getParameterByName(PARAM_SORT)); this.updateData(getParameterByName(PARAM_SORT));
} }
if (newValue.query[DETAIL_VIEW_QUERY_PARAM_NAME] && !this.$apollo.queries.workItems.loading) {
this.checkDrawerParams();
} else {
this.activeItem = null;
}
}, },
}, },
created() { created() {
@ -564,6 +575,29 @@ export default {
this.sortKey = deriveSortKey({ sort, sortMap: urlSortParams }); this.sortKey = deriveSortKey({ sort, sortMap: urlSortParams });
this.state = state || STATUS_OPEN; this.state = state || STATUS_OPEN;
}, },
checkDrawerParams() {
const queryParam = getParameterByName(DETAIL_VIEW_QUERY_PARAM_NAME);
if (!queryParam) {
return;
}
const params = JSON.parse(atob(queryParam));
if (params.id) {
const issue = this.workItems.find((i) => getIdFromGraphQLId(i.id) === params.id);
if (issue) {
this.activeItem = {
...issue,
// we need fullPath here to prevent cache invalidation
fullPath: params.full_path,
};
} else {
updateHistory({
url: removeParams([DETAIL_VIEW_QUERY_PARAM_NAME]),
});
}
}
},
}, },
}; };
</script> </script>

View File

@ -2,6 +2,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { queryToObject } from '~/lib/utils/url_utility'; import { queryToObject } from '~/lib/utils/url_utility';
import AccessorUtilities from '~/lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import { import {
NEW_WORK_ITEM_IID, NEW_WORK_ITEM_IID,
@ -248,6 +249,40 @@ export const getShowLabelsFromLocalStorage = (showLabelsLocalStorageKey, default
return null; return null;
}; };
/**
* @param {{fullPath?: string, referencePath?: string}} activeItem
* @param {string} fullPath
* @param {string} issuableType
* @returns {string}
*/
export const makeDrawerItemFullPath = (activeItem, fullPath, issuableType = TYPE_ISSUE) => {
if (activeItem?.fullPath) {
return activeItem.fullPath;
}
const delimiter = issuableType === TYPE_EPIC ? '&' : '#';
if (!activeItem?.referencePath) {
return fullPath;
}
return activeItem.referencePath.split(delimiter)[0];
};
/**
* since legacy epics don't have GID matching the work item ID, we need additional parameters
* @param {{iid: string, id: string}} activeItem
* @param {string} fullPath
* @param {string} issuableType
* @returns {{iid: string, full_path: string, id: number}}
*/
export const makeDrawerUrlParam = (activeItem, fullPath, issuableType = TYPE_ISSUE) => {
return btoa(
JSON.stringify({
iid: activeItem.iid,
full_path: makeDrawerItemFullPath(activeItem, fullPath, issuableType),
id: getIdFromGraphQLId(activeItem.id),
}),
);
};
export const getNewWorkItemAutoSaveKey = (fullPath, workItemType) => { export const getNewWorkItemAutoSaveKey = (fullPath, workItemType) => {
if (!workItemType || !fullPath) return ''; if (!workItemType || !fullPath) return '';
return `new-${fullPath}-${workItemType.toLowerCase()}-draft`; return `new-${fullPath}-${workItemType.toLowerCase()}-draft`;

View File

@ -42,6 +42,14 @@
&.animating { &.animating {
overflow: hidden; overflow: hidden;
} }
.settings-toggle svg {
@apply gl-transition;
}
&.expanded .settings-toggle svg {
transform: rotate(90deg);
}
} }
.vue-settings-block { .vue-settings-block {
@ -163,4 +171,4 @@
background-color: var(--orange-50, $orange-50); background-color: var(--orange-50, $orange-50);
border: 1px solid var(--orange-200, $orange-200); border: 1px solid var(--orange-200, $orange-200);
border-radius: $gl-border-radius-base; border-radius: $gl-border-radius-base;
} }

View File

@ -2,15 +2,15 @@
- if callout? - if callout?
= callout = callout
.gl-flex.gl-justify-between.gl-items-start.gl-gap-x-3.gl-pt-5 .gl-flex.gl-justify-between.gl-items-start.gl-gap-x-3.gl-pt-5
.gl-shrink-0.gl-px-2.gl-py-0.-gl-mr-3.sm:gl-p-2.sm:gl-mr-0
= render Pajamas::ButtonComponent.new(category: :tertiary, size: :small, icon: 'chevron-lg-right', icon_classes: '!-gl-mx-2', button_text_classes: 'gl-sr-only', button_options: @button_options.merge(class: 'settings-toggle js-settings-toggle', 'aria-label': aria_label)) do
= button_text
.gl-grow .gl-grow
%h2{ class: title_classes } %h2{ class: title_classes }
= heading || @heading = heading || @heading
- if description || @description - if description || @description
%p.gl-text-subtle.gl-m-0 %p.gl-text-subtle.gl-m-0
= description || @description = description || @description
.gl-shrink-0.gl-px-2
= render Pajamas::ButtonComponent.new(button_options: @button_options.merge(class: 'gl-min-w-12 js-settings-toggle')) do
= button_text
.settings-content .settings-content
.gl-mt-5 .gl-pl-7.sm:gl-pl-8.gl-mt-5
= body = body

View File

@ -41,5 +41,9 @@ module Layouts
def button_text def button_text
@expanded ? _('Collapse') : _('Expand') @expanded ? _('Collapse') : _('Expand')
end end
def aria_label
@expanded ? "#{_('Collapse')} #{@heading}" : "#{_('Expand')} #{@heading}"
end
end end
end end

View File

@ -3,6 +3,7 @@
module StreamDiffs module StreamDiffs
extend ActiveSupport::Concern extend ActiveSupport::Concern
include ActionController::Live include ActionController::Live
include DiffHelper
def diffs def diffs
return render_404 unless rapid_diffs_enabled? return render_404 unless rapid_diffs_enabled?
@ -11,7 +12,7 @@ module StreamDiffs
offset = { offset_index: params.permit(:offset)[:offset].to_i } offset = { offset_index: params.permit(:offset)[:offset].to_i }
stream_diff_files(options.merge(offset)) stream_diff_files(streaming_diff_options.merge(offset))
rescue StandardError => e rescue StandardError => e
Gitlab::AppLogger.error("Error streaming diffs: #{e.message}") Gitlab::AppLogger.error("Error streaming diffs: #{e.message}")
response.stream.write e.message response.stream.write e.message
@ -29,8 +30,8 @@ module StreamDiffs
raise NotImplementedError raise NotImplementedError
end end
def options def streaming_diff_options
{} diff_options
end end
def view def view

View File

@ -41,6 +41,7 @@ class GroupsController < Groups::ApplicationController
push_force_frontend_feature_flag(:work_items_beta, group.work_items_beta_feature_flag_enabled?) push_force_frontend_feature_flag(:work_items_beta, group.work_items_beta_feature_flag_enabled?)
push_force_frontend_feature_flag(:work_items_alpha, group.work_items_alpha_feature_flag_enabled?) push_force_frontend_feature_flag(:work_items_alpha, group.work_items_alpha_feature_flag_enabled?)
push_frontend_feature_flag(:issues_grid_view) push_frontend_feature_flag(:issues_grid_view)
push_frontend_feature_flag(:issues_list_drawer, group)
push_force_frontend_feature_flag(:namespace_level_work_items, group.namespace_work_items_enabled?) push_force_frontend_feature_flag(:namespace_level_work_items, group.namespace_work_items_enabled?)
end end

View File

@ -10,11 +10,12 @@ module Projects
commit commit
end end
def options def streaming_diff_options
opts = diff_options opts = super
opts[:offset_index] = params.permit(:offset)[:offset].to_i
opts[:ignore_whitespace_change] = true if params.permit(:format)[:format] == 'diff' opts[:ignore_whitespace_change] = true if params.permit(:format)[:format] == 'diff'
opts[:use_extra_viewer_as_main] = false opts[:use_extra_viewer_as_main] = false
opts opts
end end
end end

View File

@ -11,10 +11,6 @@ module Projects
@merge_request @merge_request
end end
def options
{}
end
def stream_diff_files(options) def stream_diff_files(options)
if !!ActiveModel::Type::Boolean.new.cast(params[:diff_blobs]) if !!ActiveModel::Type::Boolean.new.cast(params[:diff_blobs])
stream_diff_blobs(options) stream_diff_blobs(options)

View File

@ -11,6 +11,7 @@ class ProjectSetting < ApplicationRecord
belongs_to :project, inverse_of: :project_setting belongs_to :project, inverse_of: :project_setting
scope :for_projects, ->(projects) { where(project_id: projects) } scope :for_projects, ->(projects) { where(project_id: projects) }
scope :with_namespace, -> { joins(project: :namespace) }
attr_encrypted :cube_api_key, attr_encrypted :cube_api_key,
mode: :per_attribute_iv, mode: :per_attribute_iv,

View File

@ -12,6 +12,10 @@ class MergeRequests::PipelineEntity < Grape::Entity
project_pipeline_path(pipeline.project, pipeline) project_pipeline_path(pipeline.project, pipeline)
end end
expose :project_path do |pipeline|
project_path(pipeline.project)
end
expose :flags do expose :flags do
expose :merged_result_pipeline?, as: :merge_request_pipeline # deprecated, use merged_result_pipeline going forward expose :merged_result_pipeline?, as: :merge_request_pipeline # deprecated, use merged_result_pipeline going forward
expose :merged_result_pipeline?, as: :merged_result_pipeline expose :merged_result_pipeline?, as: :merged_result_pipeline

View File

@ -6,6 +6,7 @@ module Import
MEMBER_DELETE_BATCH_SIZE = 1_000 MEMBER_DELETE_BATCH_SIZE = 1_000
GROUP_FINDER_MEMBER_RELATIONS = %i[direct inherited shared_from_groups].freeze GROUP_FINDER_MEMBER_RELATIONS = %i[direct inherited shared_from_groups].freeze
PROJECT_FINDER_MEMBER_RELATIONS = %i[direct inherited invited_groups shared_into_ancestors].freeze PROJECT_FINDER_MEMBER_RELATIONS = %i[direct inherited invited_groups shared_into_ancestors].freeze
RELATION_BATCH_SLEEP = 5
def initialize(import_source_user) def initialize(import_source_user)
@import_source_user = import_source_user @import_source_user = import_source_user
@ -73,6 +74,9 @@ module Import
user_reference_column: user_reference_column user_reference_column: user_reference_column
) do |model_relation, placeholder_references| ) do |model_relation, placeholder_references|
reassign_placeholder_records_batch(model_relation, placeholder_references, user_reference_column) reassign_placeholder_records_batch(model_relation, placeholder_references, user_reference_column)
# TODO: Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/493977
Kernel.sleep RELATION_BATCH_SLEEP
end end
rescue NameError => e rescue NameError => e
::Import::Framework::Logger.error( ::Import::Framework::Logger.error(

View File

@ -4,7 +4,7 @@
id: 'js-gitpod-settings', id: 'js-gitpod-settings',
expanded: expanded) do |c| expanded: expanded) do |c|
- c.with_description do - c.with_description do
#js-gitpod-settings-help-text{ data: {"message" => gitpod_enable_description, "message-url" => "https://gitpod.io/" } } %span#js-gitpod-settings-help-text{ data: {"message" => gitpod_enable_description, "message-url" => "https://gitpod.io/" } }
= link_to sprite_icon('question-o'), help_page_path('integration/gitpod.md'), target: '_blank', class: 'has-tooltip', title: _('More information') = link_to sprite_icon('question-o'), help_page_path('integration/gitpod.md'), target: '_blank', class: 'has-tooltip', title: _('More information')
- c.with_body do - c.with_body do
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-gitpod-settings'), html: { class: 'fieldset-form', id: 'gitpod-settings' } do |f| = gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-gitpod-settings'), html: { class: 'fieldset-form', id: 'gitpod-settings' } do |f|

View File

@ -3,6 +3,6 @@
- if group.linked_to_subscription? - if group.linked_to_subscription?
= render Pajamas::AlertComponent.new(variant: :tip, dismissible: false, alert_options: { class: 'gl-mb-5', data: { testid: 'group-has-linked-subscription-alert' }}) do |c| = render Pajamas::AlertComponent.new(variant: :tip, dismissible: false, alert_options: { class: 'gl-mb-5', data: { testid: 'group-has-linked-subscription-alert' }}) do |c|
- c.with_body do - c.with_body do
= html_escape(_("This group can't be removed because it is linked to a subscription. To remove this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "<a href=\"#{help_page_path('subscriptions/gitlab_com/index.md', anchor: 'change-the-linked-group')}\">".html_safe, linkEnd: '</a>'.html_safe } = html_escape(_("This group can't be removed because it is linked to a subscription. To remove this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "<a href=\"#{help_page_path('subscriptions/gitlab_com/index.md', anchor: 'link-subscription-to-a-group')}\">".html_safe, linkEnd: '</a>'.html_safe }
.js-confirm-danger{ data: group_confirm_modal_data(group: group, remove_form_id: remove_form_id) } .js-confirm-danger{ data: group_confirm_modal_data(group: group, remove_form_id: remove_form_id) }

View File

@ -21,5 +21,5 @@
- if group.paid? - if group.paid?
= render Pajamas::AlertComponent.new(variant: :tip, dismissible: false, alert_options: { class: 'gl-mb-5' }) do |c| = render Pajamas::AlertComponent.new(variant: :tip, dismissible: false, alert_options: { class: 'gl-mb-5' }) do |c|
- c.with_body do - c.with_body do
= html_escape(_("This group can't be transferred because it is linked to a subscription. To transfer this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "<a href=\"#{help_page_path('subscriptions/gitlab_com/index.md', anchor: 'change-the-linked-group')}\">".html_safe, linkEnd: '</a>'.html_safe } = html_escape(_("This group can't be transferred because it is linked to a subscription. To transfer this group, %{linkStart}link the subscription%{linkEnd} with a different group.")) % { linkStart: "<a href=\"#{help_page_path('subscriptions/gitlab_com/index.md', anchor: 'link-subscription-to-a-group')}\">".html_safe, linkEnd: '</a>'.html_safe }
.js-transfer-group-form{ data: initial_data } .js-transfer-group-form{ data: initial_data }

View File

@ -12,6 +12,9 @@ module Import
sidekiq_options retry: 5, dead: false sidekiq_options retry: 5, dead: false
sidekiq_options max_retries_after_interruption: 20 sidekiq_options max_retries_after_interruption: 20
# TODO: Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/493977
concurrency_limit -> { 4 }
sidekiq_retries_exhausted do |msg, exception| sidekiq_retries_exhausted do |msg, exception|
new.perform_failure(exception, msg['args']) new.perform_failure(exception, msg['args'])
end end

View File

@ -5,7 +5,7 @@ class PostReceive
idempotent! idempotent!
deduplicate :none deduplicate :none
data_consistency :sticky, feature_flag: :load_balanacing_for_sticky_post_receive_worker data_consistency :sticky
sidekiq_options retry: 3 sidekiq_options retry: 3
include Gitlab::Experiment::Dsl include Gitlab::Experiment::Dsl

View File

@ -0,0 +1,12 @@
---
api_type:
attr: allow_top_level_group_owners_to_create_service_accounts
clusterwide: false
column: allow_top_level_group_owners_to_create_service_accounts
db_type: boolean
default: 'false'
description:
encrypted: false
gitlab_com_different_than_default: false
jihu: false
not_null: true

View File

@ -8,6 +8,6 @@ default: "'{}'::jsonb"
description: "[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/408314) in description: "[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/408314) in
GitLab 17.0. For available options, see [Options for `default_branch_protection_defaults`](groups.md#options-for-default_branch_protection_defaults)." GitLab 17.0. For available options, see [Options for `default_branch_protection_defaults`](groups.md#options-for-default_branch_protection_defaults)."
encrypted: false encrypted: false
gitlab_com_different_than_default: false gitlab_com_different_than_default: true
jihu: false jihu: false
not_null: true not_null: true

View File

@ -10,6 +10,6 @@ description: Specifies whether users who have not confirmed their email should b
`unconfirmed_users_delete_after_days` days. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352514) `unconfirmed_users_delete_after_days` days. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352514)
in GitLab 16.1. Self-managed, Premium and Ultimate only. in GitLab 16.1. Self-managed, Premium and Ultimate only.
encrypted: false encrypted: false
gitlab_com_different_than_default: false gitlab_com_different_than_default: true
jihu: false jihu: false
not_null: true not_null: true

View File

@ -1,11 +1,14 @@
--- ---
api_type: api_type: boolean
attr: pre_receive_secret_detection_enabled attr: pre_receive_secret_detection_enabled
clusterwide: true clusterwide: true
column: pre_receive_secret_detection_enabled column: pre_receive_secret_detection_enabled
db_type: boolean db_type: boolean
default: 'false' default: 'false'
description: description: Allow projects to enable secret push protection. This does not enable
secret push protection. When you enable this feature, you accept the [GitLab Testing
Agreement](https://handbook.gitlab.com/handbook/legal/testing-agreement/). Ultimate
only.
encrypted: false encrypted: false
gitlab_com_different_than_default: true gitlab_com_different_than_default: true
jihu: false jihu: false

View File

@ -1,9 +0,0 @@
---
name: load_balanacing_for_sticky_post_receive_worker
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/402254
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/167317
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/495040
milestone: '17.5'
group: group::source code
type: worker
default_enabled: false

View File

@ -0,0 +1,37 @@
- title: "GitLab Runner Docker Machine executor is deprecated"
# The milestones for the deprecation announcement, and the removal.
removal_milestone: "20.0"
announcement_milestone: "17.5"
# Change breaking_change to false if needed.
breaking_change: true
# The stage and GitLab username of the person reporting the change,
# and a link to the deprecation issue
reporter: DarrenEastman
stage: verify
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/498268
# Use the impact calculator https://gitlab-com.gitlab.io/gl-infra/breaking-change-impact-calculator/?
impact: medium
scope: [instance, group, project]
resolution_role: [Admin, Owner, Maintainer]
manual_task: true
body: | # (required) Don't change this line.
The [GitLab Runner Docker Machine executor](https://docs.gitlab.com/runner/executors/docker_machine.html) is deprecated and will be fully removed from the product as a supported feature in GitLab 20.0 (May 2027). The replacement for Docker Machine, [GitLab Runner Autoscaler](https://docs.gitlab.com/runner/runner_autoscale/) with GitLab developed plugins for Amazon Web Services (AWS) EC2, Google Compute Engine (GCE) and Microsoft Azure virtual machines (VMs) is generally available. With this announcement, the GitLab Runner team will no longer accept community contributions for the GitLab maintained Docker Machine fork, or resolve newly identified bugs.
# ==============================
# OPTIONAL END-OF-SUPPORT FIELDS
# ==============================
#
# If an End of Support period applies:
# 1) Share this announcement in the `#spt_managers` Support channel in Slack
# 2) Mention `@gitlab-com/support` in this merge request.
#
# When support for this feature ends, in XX.YY milestone format.
end_of_support_milestone:
# Array of tiers the feature is currently available to,
# like [Free, Silver, Gold, Core, Premium, Ultimate]
tiers:
# Links to documentation and thumbnail image
documentation_url:
image_url:
# Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
video_url:

View File

@ -0,0 +1,8 @@
---
migration_job_name: RequeueBackfillPCiPipelineVariablesProjectId
description: Requeue backfill sharding key `p_ci_pipeline_variables.project_id` from `p_ci_pipelines` for gitlab.com
feature_category: continuous_integration
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/168493
milestone: '17.5'
queued_migration_version: 20241008064311
finalized_by: # version of the migration that finalized this BBM

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddTempMigratingColumnToNamespaceHistoricalStatistics < Gitlab::Database::Migration[2.2]
milestone '17.5'
def up
add_column :vulnerability_namespace_historical_statistics, :migrating, :boolean, default: false, null: false
end
def down
remove_column :vulnerability_namespace_historical_statistics, :migrating
end
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
class RequeueBackfillPCiPipelineVariablesProjectId < Gitlab::Database::Migration[2.2]
milestone '17.5'
restrict_gitlab_migration gitlab_schema: :gitlab_ci
MIGRATION = "BackfillPCiPipelineVariablesProjectId"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 75_000
SUB_BATCH_SIZE = 250
TABLE_NAME = :p_ci_pipeline_variables
BATCH_COLUMN = :id
JOB_ARGS = %i[project_id p_ci_pipelines project_id pipeline_id partition_id]
def up
return unless Gitlab.com_except_jh?
delete_batched_background_migration(MIGRATION, TABLE_NAME, BATCH_COLUMN, JOB_ARGS)
queue_batched_background_migration(
MIGRATION,
TABLE_NAME,
BATCH_COLUMN,
*JOB_ARGS,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
return unless Gitlab.com_except_jh?
delete_batched_background_migration(MIGRATION, TABLE_NAME, BATCH_COLUMN, JOB_ARGS)
end
end

View File

@ -0,0 +1 @@
5485b5a71ea889318831649bbdbc829b0565e7c01126cf9907544f6e39450bae

View File

@ -0,0 +1 @@
201b3763cefe1ada643eb517712f354aa4c10a7c872d99357e6e2b48848bf2d8

View File

@ -20658,7 +20658,8 @@ CREATE TABLE vulnerability_namespace_historical_statistics (
unknown integer DEFAULT 0 NOT NULL, unknown integer DEFAULT 0 NOT NULL,
info integer DEFAULT 0 NOT NULL, info integer DEFAULT 0 NOT NULL,
date date NOT NULL, date date NOT NULL,
letter_grade smallint NOT NULL letter_grade smallint NOT NULL,
migrating boolean DEFAULT false NOT NULL
); );
CREATE SEQUENCE vulnerability_namespace_historical_statistics_id_seq CREATE SEQUENCE vulnerability_namespace_historical_statistics_id_seq

View File

@ -182,6 +182,8 @@ Backups do not include:
- [Mattermost data](../../integration/mattermost/index.md#back-up-gitlab-mattermost) - [Mattermost data](../../integration/mattermost/index.md#back-up-gitlab-mattermost)
- Redis (and thus Sidekiq jobs) - Redis (and thus Sidekiq jobs)
- [Object storage](#object-storage) on Linux package (Omnibus) / Docker / Self-compiled installations - [Object storage](#object-storage) on Linux package (Omnibus) / Docker / Self-compiled installations
- [Global server hooks](../server_hooks.md#create-global-server-hooks-for-all-repositories)
- [File hooks](../file_hooks.md)
WARNING: WARNING:
GitLab does not back up any configuration files (`/etc/gitlab`), TLS keys and certificates, or system GitLab does not back up any configuration files (`/etc/gitlab`), TLS keys and certificates, or system

View File

@ -454,8 +454,12 @@ You can use the [AWS CLI](https://aws.amazon.com/cli/) to verify that access to
The S3 bucket contains a combination of **infrastructure logs** and **application logs** from the GitLab [log system](../../administration/logs/index.md). The logs in the bucket are encrypted using an AWS KMS key that is managed by GitLab. If you choose to enable [BYOK](../../administration/dedicated/create_instance.md#encrypted-data-at-rest-byok), the application logs are not encrypted with the key you provide. The S3 bucket contains a combination of **infrastructure logs** and **application logs** from the GitLab [log system](../../administration/logs/index.md). The logs in the bucket are encrypted using an AWS KMS key that is managed by GitLab. If you choose to enable [BYOK](../../administration/dedicated/create_instance.md#encrypted-data-at-rest-byok), the application logs are not encrypted with the key you provide.
<!-- vale gitlab_base.Spelling = NO -->
The logs in the S3 bucket are organized by date in `YYYY/MM/DD/HH` format. For example, there would be a directory like `2023/10/12/13`. That directory would contain the logs from October 12, 2023 at 1300 UTC. The logs are streamed into the bucket with [Amazon Kinesis Data Firehose](https://aws.amazon.com/firehose/). The logs in the S3 bucket are organized by date in `YYYY/MM/DD/HH` format. For example, there would be a directory like `2023/10/12/13`. That directory would contain the logs from October 12, 2023 at 1300 UTC. The logs are streamed into the bucket with [Amazon Kinesis Data Firehose](https://aws.amazon.com/firehose/).
<!-- vale gitlab_base.Spelling = YES -->
## Troubleshooting ## Troubleshooting
### Outbound Private Link ### Outbound Private Link

View File

@ -596,7 +596,7 @@ If one or more of these values is significantly high, this could indicate a prob
If you get a **secondary** site in a broken state and want to reset the replication state, If you get a **secondary** site in a broken state and want to reset the replication state,
to start again from scratch, there are a few steps that can help you: to start again from scratch, there are a few steps that can help you:
1. Stop Sidekiq and the Geo LogCursor. 1. Stop Sidekiq and the Geo Log Cursor.
It's possible to make Sidekiq stop gracefully, but making it stop getting new jobs and It's possible to make Sidekiq stop gracefully, but making it stop getting new jobs and
wait until the current jobs to finish processing. wait until the current jobs to finish processing.

View File

@ -59,7 +59,7 @@ When running Gitaly in Kubernetes, you must:
A pod can rotate for many reasons. Understanding and planing the service lifecycle helps minimize disruption. A pod can rotate for many reasons. Understanding and planing the service lifecycle helps minimize disruption.
For example, in Gitaly's case, a Kubernetes `StatefulSet` rotates on `spec.template` object changes, which can happen during Helm Chart upgrades (labels, or image tag) or pod resource requests or limits updates. For example, with Gitaly, a Kubernetes `StatefulSet` rotates on `spec.template` object changes, which can happen during Helm Chart upgrades (labels, or image tag) or pod resource requests or limits updates.
This section focuses on common pod disruption cases and how to address them. This section focuses on common pod disruption cases and how to address them.

View File

@ -177,7 +177,7 @@ staggered across Gitaly nodes so the scheduled housekeeping is not running
simultaneously on multiple nodes. simultaneously on multiple nodes.
If a scheduled housekeeping run reaches the `duration` specified, the running tasks are If a scheduled housekeeping run reaches the `duration` specified, the running tasks are
gracefully cancelled. On subsequent scheduled housekeeping runs, Gitaly randomly shuffles gracefully canceled. On subsequent scheduled housekeeping runs, Gitaly randomly shuffles
the repository list to process. the repository list to process.
The following snippet enables daily background repository maintenance starting at The following snippet enables daily background repository maintenance starting at

View File

@ -178,7 +178,7 @@ For more information, see [user deactivation emails](../administration/settings/
To deactivate users with the GitLab API, see [deactivate user](../api/user_moderation.md#deactivate-a-user). For information about permanent user restrictions, see [block and unblock users](#block-and-unblock-users). To deactivate users with the GitLab API, see [deactivate user](../api/user_moderation.md#deactivate-a-user). For information about permanent user restrictions, see [block and unblock users](#block-and-unblock-users).
To remove a user from a GitLab.com subscription, see To remove a user from a GitLab.com subscription, see
[Remove users from your subscription](../subscriptions/gitlab_com/index.md#remove-users-from-your-subscription). [Remove users from your subscription](../subscriptions/gitlab_com/index.md#remove-users-from-subscription).
### Automatically deactivate dormant users ### Automatically deactivate dormant users

View File

@ -30,9 +30,13 @@ to the UI. This is properly balanced and scheduled, and therefore is
a better indicator of effective uptime. You can also monitor the sign-in a better indicator of effective uptime. You can also monitor the sign-in
page `/users/sign_in` endpoint. page `/users/sign_in` endpoint.
<!-- vale gitlab_base.Spelling = NO -->
On GitLab.com, tools such as [Pingdom](https://www.pingdom.com/) and On GitLab.com, tools such as [Pingdom](https://www.pingdom.com/) and
Apdex measurements are used to determine uptime. Apdex measurements are used to determine uptime.
<!-- vale gitlab_base.Spelling = YES -->
## IP allowlist ## IP allowlist
To access monitoring resources, the requesting client IP needs to be included in the allowlist. To access monitoring resources, the requesting client IP needs to be included in the allowlist.

View File

@ -561,7 +561,7 @@ To enable it:
1. On the left sidebar, at the bottom, select **Admin**. 1. On the left sidebar, at the bottom, select **Admin**.
1. Select **Settings > Preferences**. 1. Select **Settings > Preferences**.
1. Expand **Pages**. 1. Expand **Pages**.
1. Enter the email address for receiving notifications and accept Let's Encrypt's Terms of Service. 1. Enter the email address for receiving notifications and accept the Terms of Service for Let's Encrypt.
1. Select **Save changes**. 1. Select **Save changes**.
### Access control ### Access control

View File

@ -1526,8 +1526,8 @@ input/output operations per second (IOPS) for read operations and 2,000 IOPS for
write operations. If you're running the environment on a Cloud provider, write operations. If you're running the environment on a Cloud provider,
refer to their documentation about how to configure IOPS correctly. refer to their documentation about how to configure IOPS correctly.
Gitaly servers must not be exposed to the public internet, as Gitaly's network Gitaly servers must not be exposed to the public internet, as network traffic
traffic is unencrypted by default. The use of a firewall is highly recommended on Gitaly is unencrypted by default. The use of a firewall is highly recommended
to restrict access to the Gitaly server. Another option is to to restrict access to the Gitaly server. Another option is to
[use TLS](#gitaly-cluster-tls-support). [use TLS](#gitaly-cluster-tls-support).

View File

@ -1532,8 +1532,8 @@ input/output operations per second (IOPS) for read operations and 2,000 IOPS for
write operations. If you're running the environment on a Cloud provider, write operations. If you're running the environment on a Cloud provider,
refer to their documentation about how to configure IOPS correctly. refer to their documentation about how to configure IOPS correctly.
Gitaly servers must not be exposed to the public internet, as Gitaly's network Gitaly servers must not be exposed to the public internet, as network traffic
traffic is unencrypted by default. The use of a firewall is highly recommended on Gitaly is unencrypted by default. The use of a firewall is highly recommended
to restrict access to the Gitaly server. Another option is to to restrict access to the Gitaly server. Another option is to
[use TLS](#gitaly-cluster-tls-support). [use TLS](#gitaly-cluster-tls-support).

View File

@ -444,8 +444,8 @@ Be sure to note the following items:
- A GitLab server can use one or more Gitaly server nodes. - A GitLab server can use one or more Gitaly server nodes.
- Gitaly addresses must be specified to be correctly resolvable for *all* - Gitaly addresses must be specified to be correctly resolvable for *all*
Gitaly clients. Gitaly clients.
- Gitaly servers must not be exposed to the public internet, as Gitaly's network - Gitaly servers must not be exposed to the public internet, as network traffic
traffic is unencrypted by default. The use of a firewall is highly recommended on Gitaly is unencrypted by default. The use of a firewall is highly recommended
to restrict access to the Gitaly server. Another option is to to restrict access to the Gitaly server. Another option is to
[use TLS](#gitaly-tls-support). [use TLS](#gitaly-tls-support).

View File

@ -1362,8 +1362,8 @@ input/output operations per second (IOPS) for read operations and 2,000 IOPS for
write operations. If you're running the environment on a Cloud provider, write operations. If you're running the environment on a Cloud provider,
refer to their documentation about how to configure IOPS correctly. refer to their documentation about how to configure IOPS correctly.
Gitaly servers must not be exposed to the public internet, as Gitaly's network Gitaly servers must not be exposed to the public internet, as network traffic
traffic is unencrypted by default. The use of a firewall is highly recommended on Gitaly is unencrypted by default. The use of a firewall is highly recommended
to restrict access to the Gitaly server. Another option is to to restrict access to the Gitaly server. Another option is to
[use TLS](#gitaly-cluster-tls-support). [use TLS](#gitaly-cluster-tls-support).
@ -2127,13 +2127,13 @@ If not stated below, no other modifications are supported for lower use counts.
- Combining select nodes: The following specific components are supported to be combined onto the same nodes to reduce complexity at the cost of some performance: - Combining select nodes: The following specific components are supported to be combined onto the same nodes to reduce complexity at the cost of some performance:
- GitLab Rails and Sidekiq: Sidekiq nodes can be removed, and the component instead enabled on the GitLab Rails nodes. - GitLab Rails and Sidekiq: Sidekiq nodes can be removed, and the component instead enabled on the GitLab Rails nodes.
- PostgreSQL and PgBouncer: PgBouncer nodes could be removed and instead be enabled on PostgreSQL nodes with the Internal Load Balancer pointing to them. However, to enable [Database Load Balancing](../postgresql/database_load_balancing.md), a separate PgBouncer array is still required. - PostgreSQL and PgBouncer: PgBouncer nodes could be removed and instead be enabled on PostgreSQL nodes with the Internal Load Balancer pointing to them. However, to enable [Database Load Balancing](../postgresql/database_load_balancing.md), a separate PgBouncer array is still required.
- Reducing the node counts: Some node types do not need consensus and can run with fewer nodes (but more than one for redundancy). This will also lead to reduced performance. - Reducing the node counts: Some node types do not need consensus and can run with fewer nodes (but more than one for redundancy):
- GitLab Rails and Sidekiq: Stateless services don't have a minimum node count. Two are enough for redundancy. - GitLab Rails and Sidekiq: Stateless services don't have a minimum node count. Two are enough for redundancy.
- PostgreSQL and PgBouncer: A quorum is not strictly necessary. Two PostgreSQL nodes and two PgBouncer nodes are enough for redundancy. - PostgreSQL and PgBouncer: A quorum is not strictly necessary. Two PostgreSQL nodes and two PgBouncer nodes are enough for redundancy.
- Consul, Redis Sentinel, and Praefect: Require an odd number, and a minimum of three nodes, for a voting quorum.
- Running select components in reputable Cloud PaaS solutions: The following specific components are supported to be run on reputable Cloud Provider PaaS solutions. By doing this, additional dependent components can also be removed: - Running select components in reputable Cloud PaaS solutions: The following specific components are supported to be run on reputable Cloud Provider PaaS solutions. By doing this, additional dependent components can also be removed:
- PostgreSQL: Can be run on reputable Cloud PaaS solutions such as Google Cloud SQL or Amazon RDS. In this setup, the PgBouncer and Consul nodes are no longer required: - PostgreSQL: Can be run on reputable Cloud PaaS solutions such as Google Cloud SQL or Amazon RDS. In this setup, the PgBouncer and Consul nodes are no longer required:
- Consul may still be desired if [Prometheus](../monitoring/prometheus/index.md) auto discovery is a requirement, otherwise you would need to [manually add scrape configurations](../monitoring/prometheus/index.md#adding-custom-scrape-configurations) for all nodes. - Consul may still be desired if [Prometheus](../monitoring/prometheus/index.md) auto discovery is a requirement, otherwise you would need to [manually add scrape configurations](../monitoring/prometheus/index.md#adding-custom-scrape-configurations) for all nodes.
- As Redis Sentinel runs on the same box as Consul in this architecture, it may need to be run on a separate box if Redis is still being run using the Linux package.
- Redis: Can be run on reputable Cloud PaaS solutions such as Google Memorystore and AWS ElastiCache. In this setup, the Redis Sentinel is no longer required. - Redis: Can be run on reputable Cloud PaaS solutions such as Google Memorystore and AWS ElastiCache. In this setup, the Redis Sentinel is no longer required.
## Cloud Native Hybrid reference architecture with Helm Charts (alternative) ## Cloud Native Hybrid reference architecture with Helm Charts (alternative)

Some files were not shown because too many files have changed in this diff Show More