diff options
56 files changed, 768 insertions, 145 deletions
diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml index 004630a5c88..e73dcaa177d 100644 --- a/.gitlab/ci/rails/shared.gitlab-ci.yml +++ b/.gitlab/ci/rails/shared.gitlab-ci.yml @@ -67,7 +67,7 @@ include: # spec/lib, yet background migration tests are also sitting there, # and they should run on their own jobs so we don't need to run them # in unit tests again. - - rspec_paralellized_job "--tag ~quarantine --tag ~level:background_migration" + - rspec_paralellized_job "--tag ~quarantine --tag ~zoekt --tag ~level:background_migration" allow_failure: exit_codes: !reference [.rspec-base, variables, SUCCESSFULLY_RETRIED_TEST_EXIT_CODE] diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 58a394e160a..1db98e8532e 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -121,6 +121,9 @@ .if-security-schedule: &if-security-schedule if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_PIPELINE_SOURCE == "schedule"' +.if-foss-schedule: &if-foss-schedule + if: '$CI_PROJECT_PATH == "gitlab-org/gitlab-foss" && $CI_PIPELINE_SOURCE == "schedule"' + .if-dot-com-gitlab-org-schedule: &if-dot-com-gitlab-org-schedule if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_PIPELINE_SOURCE == "schedule"' @@ -682,6 +685,7 @@ rules: - <<: *if-schedule-maintenance - <<: *if-security-schedule + - <<: *if-foss-schedule - <<: *if-merge-request-labels-update-caches .shared:rules:update-gitaly-binaries-cache: @@ -1736,6 +1740,24 @@ - <<: *if-merge-request changes: *static-analysis-patterns +.semgrep-appsec-custom-rules:rules: + rules: + - <<: *if-not-ee + when: never + - <<: *if-merge-request + changes: *code-backstage-qa-patterns + +.ping-appsec-for-sast-findings:rules: + rules: + # Requiring $CUSTOM_SAST_RULES_BOT_PAT prevents the bot from running on forks or CE + # Without it the script would fail too. + - if: "$CUSTOM_SAST_RULES_BOT_PAT == null" + when: never + - <<: *if-not-ee + when: never + - <<: *if-merge-request + changes: *code-backstage-qa-patterns + ####################### # Vendored gems rules # ####################### @@ -2060,7 +2082,7 @@ .review:rules:review-k8s-resources-count-checks: rules: - - <<: *if-dot-com-gitlab-org-schedule + - <<: *if-dot-com-ee-schedule-default-branch-maintenance allow_failure: true - <<: *if-dot-com-gitlab-org-merge-request changes: @@ -2069,7 +2091,7 @@ .review:rules:review-gcp-quotas-checks: rules: - - <<: *if-dot-com-gitlab-org-schedule + - <<: *if-dot-com-ee-schedule-default-branch-maintenance allow_failure: true - <<: *if-dot-com-gitlab-org-merge-request changes: diff --git a/.gitlab/ci/static-analysis.gitlab-ci.yml b/.gitlab/ci/static-analysis.gitlab-ci.yml index 13013d9a9db..d546c79aab9 100644 --- a/.gitlab/ci/static-analysis.gitlab-ci.yml +++ b/.gitlab/ci/static-analysis.gitlab-ci.yml @@ -183,3 +183,37 @@ feature-flags-usage: when: always paths: - tmp/feature_flags/ + +semgrep-appsec-custom-rules: + stage: lint + extends: + - .semgrep-appsec-custom-rules:rules + image: returntocorp/semgrep + needs: [] + script: + # Required to avoid a timeout https://github.com/returntocorp/semgrep/issues/5395 + - git fetch origin master + # Include/exclude list isn't ideal https://github.com/returntocorp/semgrep/issues/5399 + - | + semgrep ci --gitlab-sast --metrics off --config $CUSTOM_RULES_URL \ + --include app --include lib --include workhorse \ + --exclude '*_test.go' --exclude spec --exclude qa > gl-sast-report.json || true + variables: + CUSTOM_RULES_URL: https://gitlab.com/gitlab-com/gl-security/appsec/sast-custom-rules/-/raw/main/appsec-pings/rules.yml + artifacts: + paths: + - gl-sast-report.json + +ping-appsec-for-sast-findings: + stage: lint + image: alpine:latest + extends: + - .ping-appsec-for-sast-findings:rules + variables: + # Project Access Token bot ID for /gitlab-com/gl-security/appsec/sast-custom-rules + BOT_USER_ID: 13559989 + needs: + - semgrep-appsec-custom-rules + script: + - apk add jq curl + - scripts/process_custom_semgrep_results.sh diff --git a/.rubocop.yml b/.rubocop.yml index f7d08c191aa..77c54053db3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -948,3 +948,7 @@ Cop/FeatureFlagUsage: # See https://gitlab.com/gitlab-org/gitlab/-/issues/386618 Layout/HashAlignment: Enabled: false + +Style/ArgumentsForwarding: + Enabled: true + AllowOnlyRestArgument: false diff --git a/.rubocop_todo/gitlab/doc_url.yml b/.rubocop_todo/gitlab/doc_url.yml index bbb477d791c..9c781ca64e9 100644 --- a/.rubocop_todo/gitlab/doc_url.yml +++ b/.rubocop_todo/gitlab/doc_url.yml @@ -4,6 +4,7 @@ Gitlab/DocUrl: Exclude: - 'app/controllers/jira_connect/app_descriptor_controller.rb' - 'app/graphql/types/concerns/gitlab_style_deprecations.rb' + - 'app/graphql/types/merge_request_type.rb' - 'app/graphql/types/notes/diff_position_input_type.rb' - 'app/graphql/types/query_complexity_type.rb' - 'app/helpers/learn_gitlab_helper.rb' diff --git a/.rubocop_todo/style/arguments_forwarding.yml b/.rubocop_todo/style/arguments_forwarding.yml new file mode 100644 index 00000000000..3f3bc958a7d --- /dev/null +++ b/.rubocop_todo/style/arguments_forwarding.yml @@ -0,0 +1,67 @@ +--- +# Cop supports --autocorrect. +Style/ArgumentsForwarding: + Details: grace period + Exclude: + - 'app/finders/access_requests_finder.rb' + - 'app/finders/concerns/finder_methods.rb' + - 'app/finders/members_finder.rb' + - 'app/graphql/resolvers/concerns/resolves_groups.rb' + - 'app/helpers/json_helper.rb' + - 'app/helpers/routing/snippets_helper.rb' + - 'app/helpers/tree_helper.rb' + - 'app/models/commit.rb' + - 'app/models/compare.rb' + - 'app/models/concerns/integrations/has_web_hook.rb' + - 'app/models/concerns/noteable.rb' + - 'app/models/concerns/reactive_caching.rb' + - 'app/models/legacy_diff_discussion.rb' + - 'app/models/repository.rb' + - 'app/presenters/merge_request_presenter.rb' + - 'app/presenters/project_presenter.rb' + - 'app/presenters/user_presenter.rb' + - 'app/serializers/analytics/cycle_analytics/stage_entity.rb' + - 'app/services/event_create_service.rb' + - 'app/services/notification_recipients/build_service.rb' + - 'app/services/notification_service.rb' + - 'app/services/system_notes/base_service.rb' + - 'app/workers/ci/schedule_delete_objects_cron_worker.rb' + - 'app/workers/concerns/gitlab/github_import/rescheduling_methods.rb' + - 'app/workers/concerns/limited_capacity/worker.rb' + - 'ee/app/models/concerns/geo/repository_replicator_strategy.rb' + - 'ee/app/serializers/security/vulnerability_report_data_entity.rb' + - 'ee/app/services/status_page/publish_base_service.rb' + - 'ee/app/workers/ee/project_cache_worker.rb' + - 'ee/lib/audit/details.rb' + - 'ee/lib/ee/api/geo.rb' + - 'ee/lib/ee/gitlab/auth/ldap/adapter.rb' + - 'ee/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules.rb' + - 'ee/spec/features/projects/mirror_spec.rb' + - 'ee/spec/lib/gitlab/status_page/storage/s3_multipart_upload_spec.rb' + - 'lib/atlassian/jira_issue_key_extractor.rb' + - 'lib/bulk_imports/clients/graphql.rb' + - 'lib/gitlab/allowable.rb' + - 'lib/gitlab/auth/ldap/adapter.rb' + - 'lib/gitlab/ci/config/external/mapper/base.rb' + - 'lib/gitlab/database/migrations/background_migration_helpers.rb' + - 'lib/gitlab/dependency_linker/base_linker.rb' + - 'lib/gitlab/git/repository.rb' + - 'lib/gitlab/github_import/markdown_text.rb' + - 'lib/gitlab/graphql/authorize/authorize_resource.rb' + - 'lib/gitlab/import_export/base/object_builder.rb' + - 'lib/gitlab/metrics/dashboard/url.rb' + - 'lib/gitlab/nav/top_nav_view_model_builder.rb' + - 'lib/gitlab/omniauth_initializer.rb' + - 'lib/gitlab/push_options.rb' + - 'lib/tasks/import.rake' + - 'qa/qa/runtime/application_settings.rb' + - 'spec/features/dashboard/issues_filter_spec.rb' + - 'spec/features/merge_request/batch_comments_spec.rb' + - 'spec/lib/gitlab/asciidoc_spec.rb' + - 'spec/lib/gitlab/other_markup_spec.rb' + - 'spec/lib/gitlab/pagination/offset_pagination_spec.rb' + - 'spec/models/integrations/mock_ci_spec.rb' + - 'spec/support/graphql/fake_tracer.rb' + - 'spec/support/graphql/resolver_factories.rb' + - 'spec/support/helpers/kubernetes_helpers.rb' + - 'spec/support/helpers/reload_helpers.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 0def1fbf392..2d734a19249 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -4a014f5227b3defe785d9cec776abe2b1a944826 +b1841c6d79f62066335ad4d44efa21f3c5f77c03 @@ -231,7 +231,7 @@ gem 'redis-namespace', '~> 1.9.0' gem 'gitlab-sidekiq-fetcher', '0.9.0', require: 'sidekiq-reliable-fetch' # Cron Parser -gem 'fugit', '~> 1.2.1' +gem 'fugit', '~> 1.8.1' # HTTP requests gem 'httparty', '~> 0.20.0' diff --git a/Gemfile.checksum b/Gemfile.checksum index e3f4b2d7b81..e19a9ab3ab6 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -189,7 +189,7 @@ {"name":"fog-rackspace","version":"0.1.1","platform":"ruby","checksum":"4a8c7a2432dd32321958c869f3b1b8190cf4eac292024e6ea267bc6040a44b78"}, {"name":"fog-xml","version":"0.1.3","platform":"ruby","checksum":"5604c42649ebb0d8a31bd973aa000c2dd0127f1c1c4c174b69266a2e78e37410"}, {"name":"formatador","version":"0.2.5","platform":"ruby","checksum":"80821869ddacb79e72870ff4bb1531efacd278c04f2df26bc6b4529ee13582bd"}, -{"name":"fugit","version":"1.2.3","platform":"ruby","checksum":"a0ba58211f4e69103531cc26d6d5c18f03a2d3989e7b8aa03d2d83f2181cb745"}, +{"name":"fugit","version":"1.8.1","platform":"ruby","checksum":"18ffb26813869610f71bb0b7d568c3624d2b3025aeebb6600a18df0c77a6a2b2"}, {"name":"fuubar","version":"2.2.0","platform":"ruby","checksum":"9b0263c4074f39c68b37f1e4e69a7d3cfc7523c41bea43601235daa723179b4a"}, {"name":"fuzzyurl","version":"0.9.0","platform":"ruby","checksum":"542efa80f2bcaadbdc402c2f0b572f2e335a1d53e375aecad68bbb3d86860c0f"}, {"name":"gemoji","version":"3.0.1","platform":"ruby","checksum":"80553f2f4932a7a95fb1b3c7c63f7dd937e7c8c610164bbdea28fd06eba5f36d"}, diff --git a/Gemfile.lock b/Gemfile.lock index 7b651712899..e5e54e4b654 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -543,9 +543,9 @@ GEM fog-core nokogiri (>= 1.5.11, < 2.0.0) formatador (0.2.5) - fugit (1.2.3) - et-orbi (~> 1.1, >= 1.1.8) - raabro (~> 1.1) + fugit (1.8.1) + et-orbi (~> 1, >= 1.2.7) + raabro (~> 1.4) fuubar (2.2.0) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) @@ -1662,7 +1662,7 @@ DEPENDENCIES fog-local (~> 0.8) fog-openstack (~> 1.0) fog-rackspace (~> 0.1.1) - fugit (~> 1.2.1) + fugit (~> 1.8.1) fuubar (~> 2.2.0) gettext (~> 3.3) gettext_i18n_rails (~> 1.8.0) diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index cf04669ba43..c597ffb18ea 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -46,9 +46,7 @@ export function formatListIssues(listIssues) { const boardItems = {}; const listData = listIssues.nodes.reduce((map, list) => { - let sortedIssues = list.issues.edges.map((issueNode) => ({ - ...issueNode.node, - })); + let sortedIssues = list.issues.nodes; if (list.listType !== ListType.closed) { sortedIssues = sortBy(sortedIssues, 'relativePosition'); } diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 0c64cbad5b1..3071c1f334e 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -9,7 +9,7 @@ export default { BoardCardInner, }, mixins: [Tracking.mixin()], - inject: ['disabled'], + inject: ['disabled', 'isApolloBoard'], props: { list: { type: Object, @@ -63,6 +63,15 @@ export default { colorClass() { return this.isColorful ? 'gl-pl-4 gl-border-l-solid gl-border-4' : ''; }, + formattedItem() { + return this.isApolloBoard + ? { + ...this.item, + assignees: this.item.assignees?.nodes || [], + labels: this.item.labels?.nodes || [], + } + : this.item; + }, }, methods: { ...mapActions(['toggleBoardItemMultiSelection', 'toggleBoardItem']), @@ -106,7 +115,7 @@ export default { > <board-card-inner :list="list" - :item="item" + :item="formattedItem" :update-filters="true" :index="index" :show-work-item-type-icon="showWorkItemTypeIcon" diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue index b728b8dd22a..708e1539c6e 100644 --- a/app/assets/javascripts/boards/components/board_column.vue +++ b/app/assets/javascripts/boards/components/board_column.vue @@ -9,17 +9,17 @@ export default { BoardListHeader, BoardList, }, - inject: { - boardId: { - default: '', - }, - }, + inject: ['isApolloBoard'], props: { list: { type: Object, default: () => ({}), required: false, }, + boardId: { + type: String, + required: true, + }, }, computed: { ...mapState(['filterParams', 'highlightedLists']), @@ -28,7 +28,7 @@ export default { return this.highlightedLists.includes(this.list.id); }, listItems() { - return this.getBoardItemsByList(this.list.id); + return this.isApolloBoard ? [] : this.getBoardItemsByList(this.list.id); }, isListDraggable() { return isListDraggable(this.list); @@ -84,7 +84,13 @@ export default { :class="{ 'board-column-highlighted': highlighted }" > <board-list-header :list="list" /> - <board-list ref="board-list" :board-items="listItems" :list="list" /> + <board-list + ref="board-list" + :board-id="boardId" + :board-items="listItems" + :list="list" + :filter-params="filterParams" + /> </div> </div> </template> diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 92f79e61f14..66fd2af5aa3 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -172,6 +172,7 @@ export default { v-for="(list, index) in boardListsToUse" :key="index" ref="board" + :board-id="boardId" :list="list" :data-draggable-item-type="$options.draggableItemTypes.list" :class="{ 'gl-xs-display-none!': addColumnFormVisible }" diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 060a708a22f..d05cd8075d1 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -8,7 +8,12 @@ import { sortableStart, sortableEnd } from '~/sortable/utils'; import Tracking from '~/tracking'; import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue'; -import { toggleFormEventPrefix, DraggableItemTypes } from '../constants'; +import { + DEFAULT_BOARD_LIST_ITEMS_SIZE, + toggleFormEventPrefix, + DraggableItemTypes, + listIssuablesQueries, +} from 'ee_else_ce/boards/constants'; import eventHub from '../eventhub'; import BoardCard from './board_card.vue'; import BoardNewIssue from './board_new_issue.vue'; @@ -31,12 +36,24 @@ export default { BoardCardMoveToPosition, }, mixins: [Tracking.mixin()], - inject: ['isEpicBoard', 'disabled'], + inject: [ + 'isEpicBoard', + 'isGroupBoard', + 'disabled', + 'fullPath', + 'boardType', + 'issuableType', + 'isApolloBoard', + ], props: { list: { type: Object, required: true, }, + boardId: { + type: String, + required: true, + }, boardItems: { type: Array, required: true, @@ -48,6 +65,8 @@ export default { showCount: false, showIssueForm: false, showEpicForm: false, + currentList: null, + isLoadingMore: false, }; }, apollo: { @@ -66,15 +85,50 @@ export default { return this.isEpicBoard; }, }, + currentList: { + query() { + return listIssuablesQueries[this.issuableType].query; + }, + variables() { + return { + id: this.list.id, + ...this.listQueryVariables, + }; + }, + skip() { + return !this.isApolloBoard || this.list.collapsed; + }, + update(data) { + return data[this.boardType].board.lists.nodes[0]; + }, + context: { + isSingleRequest: true, + }, + }, }, computed: { ...mapState(['pageInfoByListId', 'listsFlags', 'filterParams', 'isUpdateIssueOrderInProgress']), + boardListItems() { + return this.isApolloBoard + ? this.currentList?.[`${this.issuableType}s`].nodes || [] + : this.boardItems; + }, + listQueryVariables() { + return { + fullPath: this.fullPath, + boardId: this.boardId, + filters: this.filterParams, + isGroup: this.isGroupBoard, + isProject: !this.isGroupBoard, + first: DEFAULT_BOARD_LIST_ITEMS_SIZE, + }; + }, listItemsCount() { return this.isEpicBoard ? this.list.epicsCount : this.boardList?.issuesCount; }, paginatedIssueText() { return sprintf(__('Showing %{pageSize} of %{total} %{issuableType}'), { - pageSize: this.boardItems.length, + pageSize: this.boardListItems.length, total: this.listItemsCount, issuableType: this.isEpicBoard ? 'epics' : 'issues', }); @@ -86,13 +140,17 @@ export default { return this.list.maxIssueCount > 0 && this.listItemsCount > this.list.maxIssueCount; }, hasNextPage() { - return this.pageInfoByListId[this.list.id]?.hasNextPage; + return this.isApolloBoard + ? this.currentList?.[`${this.issuableType}s`].pageInfo?.hasNextPage + : this.pageInfoByListId[this.list.id]?.hasNextPage; }, loading() { - return this.listsFlags[this.list.id]?.isLoading; + return this.isApolloBoard + ? this.$apollo.queries.currentList.loading && !this.isLoadingMore + : this.listsFlags[this.list.id]?.isLoading; }, loadingMore() { - return this.listsFlags[this.list.id]?.isLoadingMore; + return this.isApolloBoard ? this.isLoadingMore : this.listsFlags[this.list.id]?.isLoadingMore; }, epicCreateFormVisible() { return this.isEpicBoard && this.list.listType !== 'closed' && this.showEpicForm; @@ -105,7 +163,7 @@ export default { return this.canMoveIssue ? this.$refs.list.$el : this.$refs.list; }, showingAllItems() { - return this.boardItems.length === this.listItemsCount; + return this.boardListItems.length === this.listItemsCount; }, showingAllItemsText() { return this.isEpicBoard @@ -128,7 +186,7 @@ export default { tag: 'ul', 'ghost-class': 'board-card-drag-active', 'data-list-id': this.list.id, - value: this.boardItems, + value: this.boardListItems, delay: 100, delayOnTouchOnly: true, }; @@ -140,7 +198,7 @@ export default { }, }, watch: { - boardItems() { + boardListItems() { this.$nextTick(() => { this.showCount = this.scrollHeight() > Math.ceil(this.listHeight()); }); @@ -165,10 +223,10 @@ export default { methods: { ...mapActions(['fetchItemsForList', 'moveItem']), listHeight() { - return this.listRef.getBoundingClientRect().height; + return this.listRef?.getBoundingClientRect()?.height || 0; }, scrollHeight() { - return this.listRef.scrollHeight; + return this.listRef?.scrollHeight || 0; }, scrollTop() { return this.listRef.scrollTop + this.listHeight(); @@ -176,8 +234,20 @@ export default { scrollToTop() { this.listRef.scrollTop = 0; }, - loadNextPage() { - this.fetchItemsForList({ listId: this.list.id, fetchNext: true }); + async loadNextPage() { + if (this.isApolloBoard) { + this.isLoadingMore = true; + await this.$apollo.queries.currentList.fetchMore({ + variables: { + ...this.listQueryVariables, + id: this.list.id, + after: this.currentList?.[`${this.issuableType}s`].pageInfo.endCursor, + }, + }); + this.isLoadingMore = false; + } else { + this.fetchItemsForList({ listId: this.list.id, fetchNext: true }); + } }, toggleForm() { if (this.isEpicBoard) { @@ -303,7 +373,7 @@ export default { @end="handleDragOnEnd" > <board-card - v-for="(item, index) in boardItems" + v-for="(item, index) in boardListItems" ref="issue" :key="item.id" :index="index" @@ -318,7 +388,7 @@ export default { :item="item" :index="index" :list="list" - :list-items-length="boardItems.length" + :list-items-length="boardListItems.length" /> </board-card> <gl-intersection-observer @appear="onReachingListBottom"> diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js index 5941e237298..055298e66f1 100644 --- a/app/assets/javascripts/boards/constants.js +++ b/app/assets/javascripts/boards/constants.js @@ -9,6 +9,7 @@ import issueSetSubscriptionMutation from './graphql/issue_set_subscription.mutat import issueSetTitleMutation from './graphql/issue_set_title.mutation.graphql'; import groupBoardQuery from './graphql/group_board.query.graphql'; import projectBoardQuery from './graphql/project_board.query.graphql'; +import listIssuesQuery from './graphql/lists_issues.query.graphql'; /* eslint-disable-next-line @gitlab/require-i18n-strings */ export const AssigneeIdParamValues = ['Any', 'None']; @@ -106,6 +107,12 @@ export const subscriptionQueries = { }, }; +export const listIssuablesQueries = { + [issuableTypes.issue]: { + query: listIssuesQuery, + }, +}; + export const FilterFields = { [issuableTypes.issue]: [ 'assigneeUsername', diff --git a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql index ae6394f9a2f..0b9e416d408 100644 --- a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql +++ b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql @@ -19,10 +19,8 @@ query BoardListsEE( id listType issues(first: $first, filters: $filters, after: $after) { - edges { - node { - ...Issue - } + nodes { + ...Issue } pageInfo { endCursor @@ -42,10 +40,8 @@ query BoardListsEE( id listType issues(first: $first, filters: $filters, after: $after) { - edges { - node { - ...Issue - } + nodes { + ...Issue } pageInfo { endCursor diff --git a/app/assets/javascripts/graphql_shared/issuable_client.js b/app/assets/javascripts/graphql_shared/issuable_client.js index 01cc2fc3018..130a8785250 100644 --- a/app/assets/javascripts/graphql_shared/issuable_client.js +++ b/app/assets/javascripts/graphql_shared/issuable_client.js @@ -13,7 +13,9 @@ export const config = { // included temporarily until Vuex is removed from boards app dataIdFromObject: (object) => { // eslint-disable-next-line no-underscore-dangle - return object.__typename === 'BoardList' ? object.iid : defaultDataIdFromObject(object); + return object.__typename === 'BoardList' && !window.gon?.features?.apolloBoards + ? object.iid + : defaultDataIdFromObject(object); }, typePolicies: { Project: { @@ -83,6 +85,57 @@ export const config = { nodes: concatPagination(), }, }, + ...(window.gon?.features?.apolloBoards + ? { + BoardList: { + fields: { + issues: { + keyArgs: ['filters'], + }, + }, + }, + IssueConnection: { + merge(existing = { nodes: [] }, incoming, { args }) { + if (!args.after) { + return incoming; + } + return { + ...incoming, + nodes: [...existing.nodes, ...incoming.nodes], + }; + }, + }, + EpicList: { + fields: { + epics: { + keyArgs: ['filters'], + }, + }, + }, + EpicConnection: { + merge(existing = { nodes: [] }, incoming, { args }) { + if (!args.after) { + return incoming; + } + return { + ...incoming, + nodes: [...existing.nodes, ...incoming.nodes], + }; + }, + }, + BoardEpicConnection: { + merge(existing = { nodes: [] }, incoming, { args }) { + if (!args.after) { + return incoming; + } + return { + ...incoming, + nodes: [...existing.nodes, ...incoming.nodes], + }; + }, + }, + } + : {}), }, }, }; diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index a622b342c0a..4a5536986bd 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -156,6 +156,7 @@ "WorkItemWidgetRequirementLegacy", "WorkItemWidgetStartAndDueDate", "WorkItemWidgetStatus", + "WorkItemWidgetTestReports", "WorkItemWidgetWeight" ] -} +}
\ No newline at end of file diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index 77216408c39..c87dcef6a93 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -9,7 +9,6 @@ import { REPORT_TYPE_SECRET_DETECTION, REPORT_TYPE_DEPENDENCY_SCANNING, REPORT_TYPE_CONTAINER_SCANNING, - REPORT_TYPE_CLUSTER_IMAGE_SCANNING, REPORT_TYPE_COVERAGE_FUZZING, REPORT_TYPE_CORPUS_MANAGEMENT, REPORT_TYPE_API_FUZZING, @@ -105,18 +104,6 @@ export const CONTAINER_SCANNING_CONFIG_HELP_PATH = helpPagePath( { anchor: 'configuration' }, ); -export const CLUSTER_IMAGE_SCANNING_NAME = s__('ciReport|Cluster Image Scanning'); -export const CLUSTER_IMAGE_SCANNING_DESCRIPTION = __( - 'Check your Kubernetes cluster images for known vulnerabilities.', -); -export const CLUSTER_IMAGE_SCANNING_HELP_PATH = helpPagePath( - 'user/application_security/cluster_image_scanning/index', -); -export const CLUSTER_IMAGE_SCANNING_CONFIG_HELP_PATH = helpPagePath( - 'user/application_security/cluster_image_scanning/index', - { anchor: 'configuration' }, -); - export const COVERAGE_FUZZING_NAME = __('Coverage Fuzzing'); export const COVERAGE_FUZZING_DESCRIPTION = __( 'Find bugs in your code with coverage-guided fuzzing.', @@ -153,7 +140,6 @@ export const SCANNER_NAMES_MAP = { DAST: DAST_SHORT_NAME, API_FUZZING: API_FUZZING_NAME, CONTAINER_SCANNING: CONTAINER_SCANNING_NAME, - CLUSTER_IMAGE_SCANNING: CLUSTER_IMAGE_SCANNING_NAME, COVERAGE_FUZZING: COVERAGE_FUZZING_NAME, SECRET_DETECTION: SECRET_DETECTION_NAME, DEPENDENCY_SCANNING: DEPENDENCY_SCANNING_NAME, @@ -213,13 +199,6 @@ export const securityFeatures = [ type: REPORT_TYPE_CONTAINER_SCANNING, }, { - name: CLUSTER_IMAGE_SCANNING_NAME, - description: CLUSTER_IMAGE_SCANNING_DESCRIPTION, - helpPath: CLUSTER_IMAGE_SCANNING_HELP_PATH, - configurationHelpPath: CLUSTER_IMAGE_SCANNING_CONFIG_HELP_PATH, - type: REPORT_TYPE_CLUSTER_IMAGE_SCANNING, - }, - { name: SECRET_DETECTION_NAME, description: SECRET_DETECTION_DESCRIPTION, helpPath: SECRET_DETECTION_HELP_PATH, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue index 7343c98938c..c52d5ee6e08 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/widget/widget.vue @@ -2,6 +2,7 @@ import { GlButton, GlLink, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; import { normalizeHeaders } from '~/lib/utils/common_utils'; +import { logError } from '~/lib/logger'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { sprintf, __ } from '~/locale'; import Poll from '~/lib/utils/poll'; @@ -17,8 +18,12 @@ import ActionButtons from './action_buttons.vue'; const FETCH_TYPE_COLLAPSED = 'collapsed'; const FETCH_TYPE_EXPANDED = 'expanded'; const WIDGET_PREFIX = 'Widget'; +const MISSING_RESPONSE_HEADERS = + 'MR Widget: raesponse object should contain status and headers object. Make sure to include that in your `fetchCollapsedData` and `fetchExpandedData` functions.'; export default { + MISSING_RESPONSE_HEADERS, + components: { ActionButtons, StatusIcon, @@ -225,6 +230,14 @@ export default { }, method: 'fetchData', successCallback: (response) => { + if ( + typeof response.status === 'undefined' || + typeof response.headers === 'undefined' + ) { + logError(MISSING_RESPONSE_HEADERS); + throw new Error(MISSING_RESPONSE_HEADERS); + } + const headers = normalizeHeaders(response.headers); if (headers['POLL-INTERVAL']) { diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 64e585bae14..333a176b8f3 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -108,6 +108,8 @@ class CommitStatus < Ci::ApplicationRecord # These are pages deployments and external statuses. # before_create unless: :importing? do + next if Feature.enabled?(:ci_remove_ensure_stage_service, project) + # rubocop: disable CodeReuse/ServiceClass Ci::EnsureStageService.new(project, user).execute(self) do |stage| self.run_after_commit { StageUpdateWorker.perform_async(stage.id) } diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb index cc9003423be..8741a341ad3 100644 --- a/app/models/project_ci_cd_setting.rb +++ b/app/models/project_ci_cd_setting.rb @@ -20,6 +20,10 @@ class ProjectCiCdSetting < ApplicationRecord attribute :forward_deployment_enabled, default: true attribute :separated_caches, default: true + default_value_for :inbound_job_token_scope_enabled do |settings| + Feature.enabled?(:ci_inbound_job_token_scope, settings.project) + end + chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval def keep_latest_artifacts_available? diff --git a/config/feature_flags/development/ci_remove_ensure_stage_service.yml b/config/feature_flags/development/ci_remove_ensure_stage_service.yml new file mode 100644 index 00000000000..e584a1316b6 --- /dev/null +++ b/config/feature_flags/development/ci_remove_ensure_stage_service.yml @@ -0,0 +1,8 @@ +--- +name: ci_remove_ensure_stage_service +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107389 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/389076 +milestone: '15.9' +type: development +group: group::pipeline authoring +default_enabled: false diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index de6e159921c..067c4730f0a 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -543,3 +543,5 @@ - 1 - - x509_certificate_revoke - 1 +- - zoekt_indexer + - 1 diff --git a/db/docs/zoekt_indexed_namespaces.yml b/db/docs/zoekt_indexed_namespaces.yml new file mode 100644 index 00000000000..1ab748ac154 --- /dev/null +++ b/db/docs/zoekt_indexed_namespaces.yml @@ -0,0 +1,10 @@ +--- +table_name: zoekt_indexed_namespaces +classes: +- Zoekt::IndexedNamespace +feature_categories: +- global_search +description: Describes a namespace that is configured to use a specific Zoekt shard for code search +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105049 +milestone: '15.9' +gitlab_schema: gitlab_main diff --git a/db/docs/zoekt_shards.yml b/db/docs/zoekt_shards.yml new file mode 100644 index 00000000000..5fe3b469b19 --- /dev/null +++ b/db/docs/zoekt_shards.yml @@ -0,0 +1,10 @@ +--- +table_name: zoekt_shards +classes: +- Zoekt::Shard +feature_categories: +- global_search +description: Describes a Zoekt server that will be used for indexing and search for some configured namespaces +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105049 +milestone: '15.9' +gitlab_schema: gitlab_main diff --git a/db/migrate/20230104201524_add_zoekt_shards_and_indexed_namespaces.rb b/db/migrate/20230104201524_add_zoekt_shards_and_indexed_namespaces.rb new file mode 100644 index 00000000000..c9d7bc51041 --- /dev/null +++ b/db/migrate/20230104201524_add_zoekt_shards_and_indexed_namespaces.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddZoektShardsAndIndexedNamespaces < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def change + create_table :zoekt_shards do |t| + t.text :index_base_url, limit: 1024, index: { unique: true }, null: false + t.text :search_base_url, limit: 1024, index: { unique: true }, null: false + t.timestamps_with_timezone + end + + create_table :zoekt_indexed_namespaces do |t| + t.references :zoekt_shard, null: false, index: false, foreign_key: { on_delete: :cascade } + t.bigint :namespace_id, null: false, index: true + t.timestamps_with_timezone + t.index [:zoekt_shard_id, :namespace_id], unique: true, name: 'index_zoekt_shard_and_namespace' + end + end +end diff --git a/db/migrate/20230107125328_add_zoekt_indexed_namespaces_foreign_key.rb b/db/migrate/20230107125328_add_zoekt_indexed_namespaces_foreign_key.rb new file mode 100644 index 00000000000..db995d6603e --- /dev/null +++ b/db/migrate/20230107125328_add_zoekt_indexed_namespaces_foreign_key.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class AddZoektIndexedNamespacesForeignKey < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :zoekt_indexed_namespaces, :namespaces, column: :namespace_id, on_delete: :cascade + end + + def down + with_lock_retries do + remove_foreign_key :zoekt_indexed_namespaces, column: :namespace_id + end + end +end diff --git a/db/schema_migrations/20230104201524 b/db/schema_migrations/20230104201524 new file mode 100644 index 00000000000..e98bb08fe2f --- /dev/null +++ b/db/schema_migrations/20230104201524 @@ -0,0 +1 @@ +e27a0a61f6807352c02ddf7c0bd44a86e3c244051fa3977f597cc92e83fcb0d1
\ No newline at end of file diff --git a/db/schema_migrations/20230107125328 b/db/schema_migrations/20230107125328 new file mode 100644 index 00000000000..94ba5596a06 --- /dev/null +++ b/db/schema_migrations/20230107125328 @@ -0,0 +1 @@ +741599316bd51b0d454e49c43a06b834d8d172f3fd1dcd28996494da8fdf5d8b
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 9c16aa0dad2..8d187d6798e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -23837,6 +23837,42 @@ CREATE SEQUENCE zentao_tracker_data_id_seq ALTER SEQUENCE zentao_tracker_data_id_seq OWNED BY zentao_tracker_data.id; +CREATE TABLE zoekt_indexed_namespaces ( + id bigint NOT NULL, + zoekt_shard_id bigint NOT NULL, + namespace_id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL +); + +CREATE SEQUENCE zoekt_indexed_namespaces_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE zoekt_indexed_namespaces_id_seq OWNED BY zoekt_indexed_namespaces.id; + +CREATE TABLE zoekt_shards ( + id bigint NOT NULL, + index_base_url text NOT NULL, + search_base_url text NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + CONSTRAINT check_61794bac26 CHECK ((char_length(search_base_url) <= 1024)), + CONSTRAINT check_c65bb85a32 CHECK ((char_length(index_base_url) <= 1024)) +); + +CREATE SEQUENCE zoekt_shards_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER SEQUENCE zoekt_shards_id_seq OWNED BY zoekt_shards.id; + CREATE TABLE zoom_meetings ( id bigint NOT NULL, project_id bigint NOT NULL, @@ -24926,6 +24962,10 @@ ALTER TABLE ONLY x509_issuers ALTER COLUMN id SET DEFAULT nextval('x509_issuers_ ALTER TABLE ONLY zentao_tracker_data ALTER COLUMN id SET DEFAULT nextval('zentao_tracker_data_id_seq'::regclass); +ALTER TABLE ONLY zoekt_indexed_namespaces ALTER COLUMN id SET DEFAULT nextval('zoekt_indexed_namespaces_id_seq'::regclass); + +ALTER TABLE ONLY zoekt_shards ALTER COLUMN id SET DEFAULT nextval('zoekt_shards_id_seq'::regclass); + ALTER TABLE ONLY zoom_meetings ALTER COLUMN id SET DEFAULT nextval('zoom_meetings_id_seq'::regclass); ALTER TABLE ONLY analytics_cycle_analytics_issue_stage_events @@ -27388,6 +27428,12 @@ ALTER TABLE ONLY x509_issuers ALTER TABLE ONLY zentao_tracker_data ADD CONSTRAINT zentao_tracker_data_pkey PRIMARY KEY (id); +ALTER TABLE ONLY zoekt_indexed_namespaces + ADD CONSTRAINT zoekt_indexed_namespaces_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY zoekt_shards + ADD CONSTRAINT zoekt_shards_pkey PRIMARY KEY (id); + ALTER TABLE ONLY zoom_meetings ADD CONSTRAINT zoom_meetings_pkey PRIMARY KEY (id); @@ -31821,6 +31867,14 @@ CREATE INDEX index_x509_issuers_on_subject_key_identifier ON x509_issuers USING CREATE INDEX index_zentao_tracker_data_on_integration_id ON zentao_tracker_data USING btree (integration_id); +CREATE INDEX index_zoekt_indexed_namespaces_on_namespace_id ON zoekt_indexed_namespaces USING btree (namespace_id); + +CREATE UNIQUE INDEX index_zoekt_shard_and_namespace ON zoekt_indexed_namespaces USING btree (zoekt_shard_id, namespace_id); + +CREATE UNIQUE INDEX index_zoekt_shards_on_index_base_url ON zoekt_shards USING btree (index_base_url); + +CREATE UNIQUE INDEX index_zoekt_shards_on_search_base_url ON zoekt_shards USING btree (search_base_url); + CREATE INDEX index_zoom_meetings_on_issue_id ON zoom_meetings USING btree (issue_id); CREATE UNIQUE INDEX index_zoom_meetings_on_issue_id_and_issue_status ON zoom_meetings USING btree (issue_id, issue_status) WHERE (issue_status = 1); @@ -33558,6 +33612,9 @@ ALTER TABLE ONLY agent_activity_events ALTER TABLE ONLY issues ADD CONSTRAINT fk_3b8c72ea56 FOREIGN KEY (sprint_id) REFERENCES sprints(id) ON DELETE SET NULL; +ALTER TABLE ONLY zoekt_indexed_namespaces + ADD CONSTRAINT fk_3bebdb4efc FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY epics ADD CONSTRAINT fk_3c1fd1cccc FOREIGN KEY (due_date_sourcing_milestone_id) REFERENCES milestones(id) ON DELETE SET NULL; @@ -34794,6 +34851,9 @@ ALTER TABLE ONLY geo_repository_renamed_events ALTER TABLE ONLY aws_roles ADD CONSTRAINT fk_rails_4ed56f4720 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY zoekt_indexed_namespaces + ADD CONSTRAINT fk_rails_4f6006e94c FOREIGN KEY (zoekt_shard_id) REFERENCES zoekt_shards(id) ON DELETE CASCADE; + ALTER TABLE ONLY packages_debian_publications ADD CONSTRAINT fk_rails_4fc8ebd03e FOREIGN KEY (distribution_id) REFERENCES packages_debian_project_distributions(id) ON DELETE CASCADE; diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 80c4b3ac016..d01e8e3e7c3 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -21405,6 +21405,17 @@ Represents a status widget. | <a id="workitemwidgetstatusstatus"></a>`status` | [`String`](#string) | Status of the work item. | | <a id="workitemwidgetstatustype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | +### `WorkItemWidgetTestReports` + +Represents a test reports widget. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="workitemwidgettestreportstestreports"></a>`testReports` | [`TestReportConnection`](#testreportconnection) | Test reports of the work item. (see [Connections](#connections)) | +| <a id="workitemwidgettestreportstype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | + ### `WorkItemWidgetWeight` Represents a weight widget. @@ -23619,6 +23630,7 @@ Type of a work item widget. | <a id="workitemwidgettyperequirement_legacy"></a>`REQUIREMENT_LEGACY` | Requirement Legacy widget. | | <a id="workitemwidgettypestart_and_due_date"></a>`START_AND_DUE_DATE` | Start And Due Date widget. | | <a id="workitemwidgettypestatus"></a>`STATUS` | Status widget. | +| <a id="workitemwidgettypetest_reports"></a>`TEST_REPORTS` | Test Reports widget. | | <a id="workitemwidgettypeweight"></a>`WEIGHT` | Weight widget. | ## Scalar types @@ -24948,6 +24960,7 @@ Implementations: - [`WorkItemWidgetRequirementLegacy`](#workitemwidgetrequirementlegacy) - [`WorkItemWidgetStartAndDueDate`](#workitemwidgetstartandduedate) - [`WorkItemWidgetStatus`](#workitemwidgetstatus) +- [`WorkItemWidgetTestReports`](#workitemwidgettestreports) - [`WorkItemWidgetWeight`](#workitemwidgetweight) ##### Fields diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md index a800c6fc93f..72263699a4c 100644 --- a/doc/development/documentation/styleguide/word_list.md +++ b/doc/development/documentation/styleguide/word_list.md @@ -351,6 +351,12 @@ Use **inactive** or **off** instead. ([Vale](../testing.md#vale) rule: [`Inclusi Use **prevent** instead of **disallow**. ([Vale](../testing.md#vale) rule: [`Substitutions.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/Substitutions.yml)) +## Docker-in-Docker, `dind` + +Use **Docker-in-Docker** when you are describing running a Docker container by using the Docker executor. + +Use `dind` in backticks to describe the container name: `docker:dind`. Otherwise, spell it out. + ## downgrade To be more upbeat and precise, do not use **downgrade**. Focus instead on the action the user is taking. diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md index 806e275489f..41fbe8fe83c 100644 --- a/doc/operations/incident_management/integrations.md +++ b/doc/operations/incident_management/integrations.md @@ -89,7 +89,7 @@ GitLab fields when you [create an HTTP endpoint](#http-endpoints): ## Customize the alert payload outside of GitLab For HTTP Endpoints without [custom mappings](#map-fields-in-custom-alerts), you can customize the payload by sending the following -parameters. All fields are optional. If the incoming alert does not contain a value for the `Title` field, a default value of `New: Alert` will be applied. +parameters. All fields are optional. If the incoming alert does not contain a value for the `Title` field, a default value of `New: Alert` is applied. | Property | Type | Description | | ------------------------- | --------------- | ----------- | @@ -413,7 +413,7 @@ If the existing alert is already `resolved`, GitLab creates a new alert instead. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13402) in GitLab 13.4. -The alert in GitLab will be automatically resolved when an HTTP Endpoint +The alert in GitLab is automatically resolved when an HTTP Endpoint receives a payload with the end time of the alert set. For HTTP Endpoints without [custom mappings](#map-fields-in-custom-alerts), the expected field is `end_time`. With custom mappings, you can select the expected field. diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md index 3b1c800856a..58db404f809 100644 --- a/doc/user/application_security/index.md +++ b/doc/user/application_security/index.md @@ -242,7 +242,7 @@ reports are available to download. To download a report, select A merge request contains a security widget which displays a summary of the new results. New results are determined by comparing the findings of the merge request against the findings of the most recent completed pipeline (`success`, `failed`, `canceled` or `skipped`) for the latest commit in the target branch. -If security scans have not run for the most recent completed pipeline in the target branch there is no base for comparison. The vulnerabilities from the merge request findings will be listed as new in the merge request security widget. We recommend you run a scan of the `default` (target) branch before enabling feature branch scans for your developers. +If security scans have not run for the most recent completed pipeline in the target branch there is no base for comparison. The vulnerabilities from the merge request findings are listed as new in the merge request security widget. We recommend you run a scan of the `default` (target) branch before enabling feature branch scans for your developers. The merge request security widget displays only a subset of the vulnerabilities in the generated JSON artifact because it contains both new and existing findings. @@ -516,7 +516,7 @@ Additional details about the differences between the two solutions are outlined | ------ | ------ | ------ | | **Flexibility** | Supports anything that can be done in a CI file. | Limited to only the items for which GitLab has explicitly added support. DAST, SAST, Secret Detection, Dependency Scanning, and Container Scanning scans are supported. | | **Usability** | Requires knowledge of CI YAML. | Follows a `rules` and `actions`-based YAML structure. | -| **Inclusion in CI pipeline** | The compliance pipeline is executed instead of the project's `.gitlab-ci.yml` file. To include the project's `.gitlab-ci.yml` file, use an `include` statement. Defined variables aren't allowed to be overwritten by the included project's YAML file. | Forced inclusion of a new job into the CI pipeline. DAST jobs that must be customized on a per-project basis can have project-level Site Profiles and Scan Profiles defined. To ensure separation of duties, these profiles are immutable when referenced in a scan execution policy. All jobs can be customized as part of the security policy itself with the same variables that are normally available to the CI job. | +| **Inclusion in CI pipeline** | The compliance pipeline is executed instead of the project's `.gitlab-ci.yml` file. To include the project's `.gitlab-ci.yml` file, use an `include` statement. Defined variables aren't allowed to be overwritten by the included project's YAML file. | Forced inclusion of a new job into the CI pipeline. DAST jobs that must be customized on a per-project basis can have project-level Site Profiles and Scan Profiles defined. To ensure separation of duties, these profiles are immutable when referenced in a scan execution policy. All jobs can be customized as part of the security policy itself with the same variables that are usually available to the CI job. | | **Schedulable** | Can be scheduled through a scheduled pipeline on the group. | Can be scheduled natively through the policy configuration itself. | | **Separation of Duties** | Only group owners can create compliance framework labels. Only project owners can apply compliance framework labels to projects. The ability to make or approve changes to the compliance pipeline definition is limited to individuals who are explicitly given access to the project that contains the compliance pipeline. | Only project owners can define a linked security policy project. The ability to make or approve changes to security policies is limited to individuals who are explicitly given access to the security policy project. | | **Ability to apply one standard to multiple projects** | The same compliance framework label can be applied to multiple projects inside a group. | The same security policy project can be used for multiple projects across GitLab with no requirement of being located in the same group. | @@ -702,7 +702,7 @@ The `sast` or `dependency_scanning` stanzas can be used to make changes to all S such as changing `variables` or the `stage`, but they cannot be used to define shared `rules`. There [is an issue open to improve extendability](https://gitlab.com/gitlab-org/gitlab/-/issues/218444). -Please upvote the issue to help with prioritization, and +You can upvote the issue to help with prioritization, and [contributions are welcomed](https://about.gitlab.com/community/contribute/). ### Empty Vulnerability Report, Dependency List, License list pages @@ -710,9 +710,9 @@ Please upvote the issue to help with prioritization, and If the pipeline has manual steps with a job that has the `allow_failure: false` option, and this job is not finished, GitLab can't populate listed pages with the data from security reports. In this case, [the Vulnerability Report](vulnerability_report/index.md), [the Dependency List](dependency_list/index.md), -and [the License list](../compliance/license_compliance/index.md#license-list) pages will be empty. +and [the License list](../compliance/license_compliance/index.md#license-list) pages are empty. These security pages can be populated by running the jobs from the manual step of the pipeline. There is [an issue open to handle this scenario](https://gitlab.com/gitlab-org/gitlab/-/issues/346843). -Please upvote the issue to help with prioritization, and +You can upvote the issue to help with prioritization, and [contributions are welcomed](https://about.gitlab.com/community/contribute/). diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md index 394afb4bac2..0a0295459e4 100644 --- a/doc/user/profile/personal_access_tokens.md +++ b/doc/user/profile/personal_access_tokens.md @@ -198,6 +198,29 @@ This code can be shortened into a single-line shell command using the sudo gitlab-rails runner "PersonalAccessToken.find_by_token('token-string-here123').revoke!" ``` +## Clone repository using personal access token **(FREE SELF)** + +To clone a repository when SSH is disabled, clone it using a personal access token by running the following command: + +```shell +git clone https://<username>:<personal_token>@gitlab.com/gitlab-org/gitlab.git +``` + +This method saves your personal access token in your bash history. To avoid this, run the following command: + +```shell +git clone https://<username>@gitlab.com/gitlab-org/gitlab.git +``` + +When asked for your password for `https://gitlab.com`, enter your personal access token. + +The `username` in the `clone` command: + +- Can be any string value. +- Must not be an empty string. + +Remember this if you set up an automation pipeline that depends on authentication. + ## Troubleshooting ### Unrevoke a personal access token **(FREE SELF)** diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md index bbe4aadc50d..5f9a2961df5 100644 --- a/doc/user/project/milestones/index.md +++ b/doc/user/project/milestones/index.md @@ -48,8 +48,7 @@ In a group, GitLab displays milestones that belong to the group and all projects If a project has issue tracking [turned off](../settings/index.md#configure-project-visibility-features-and-permissions), -you can get to the milestones page -by going to its URL. +to get to the milestones page, enter its URL. To do so: diff --git a/doc/user/search/exact_code_search.md b/doc/user/search/exact_code_search.md new file mode 100644 index 00000000000..97f58b973cb --- /dev/null +++ b/doc/user/search/exact_code_search.md @@ -0,0 +1,36 @@ +--- +stage: Data Stores +group: Global Search +info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments" +type: reference +--- + +# Exact Code Search **(PREMIUM)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105049) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) named `index_code_with_zoekt` and `search_code_with_zoekt` which enables indexing and searching respectively. Both are disabled by default. + +WARNING: +Exact code search is in [**Alpha**](../../policy/alpha-beta-support.md#alpha-features). +For the Exact code search feature roadmap, see [epic 9404](https://gitlab.com/groups/gitlab-org/-/epics/9404). + +This feature will initially only be rolled out to +specific customers on GitLab.com that request +access. + +On self-managed GitLab it should be possible to enable this, but no +documentation is provided as it requires executing commands from the Rails +console as well advanced configuration of +[Zoekt](https://github.com/sourcegraph/zoekt) servers. + +## Usage + +When performing any Code search in GitLab it will choose to use "Exact Code +Search" powered by [Zoekt](https://github.com/sourcegraph/zoekt) if the project +is part of an enabled Group. + +The main differences between Zoekt and [Advanced Search](advanced_search.md) +are that Zoekt provides exact substring matching as well as allows you to +search for regular expressions. Since it allows searching for regular +expressions, certain special characters will require escaping. Backslash can +escape special characters and wrapping in double quotes can be used for phrase +searches. diff --git a/lib/gitlab/sql/set_operator.rb b/lib/gitlab/sql/set_operator.rb index 18275da3ef0..8b4c43786f7 100644 --- a/lib/gitlab/sql/set_operator.rb +++ b/lib/gitlab/sql/set_operator.rb @@ -35,9 +35,12 @@ module Gitlab # By using "unprepared_statements" we remove the usage of placeholders # (thus fixing this problem), at a slight performance cost. fragments = ApplicationRecord.connection.unprepared_statement do - relations.map do |rel| - remove_order ? rel.reorder(nil).to_sql : rel.to_sql - end.reject(&:blank?) + relations.filter_map do |rel| + next if rel.is_a?(ActiveRecord::NullRelation) + + sql = remove_order ? rel.reorder(nil).to_sql : rel.to_sql + sql.presence + end end if fragments.any? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2c39f85ed7a..f55eddb3eaf 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8284,9 +8284,6 @@ msgstr "" msgid "Check your Docker images for known vulnerabilities." msgstr "" -msgid "Check your Kubernetes cluster images for known vulnerabilities." -msgstr "" - msgid "Check your sign-up restrictions" msgstr "" @@ -8471,16 +8468,16 @@ msgstr "" msgid "Checkout|Payment method" msgstr "" -msgid "Checkout|Please select a country" +msgid "Checkout|Purchase details" msgstr "" -msgid "Checkout|Please select a state" +msgid "Checkout|Select" msgstr "" -msgid "Checkout|Purchase details" +msgid "Checkout|Select a country" msgstr "" -msgid "Checkout|Select" +msgid "Checkout|Select a state" msgstr "" msgid "Checkout|State" diff --git a/scripts/process_custom_semgrep_results.sh b/scripts/process_custom_semgrep_results.sh new file mode 100755 index 00000000000..1fdd8e486f3 --- /dev/null +++ b/scripts/process_custom_semgrep_results.sh @@ -0,0 +1,55 @@ +# This script requires BOT_USER_ID, CUSTOM_SAST_RULES_BOT_PAT and CI_MERGE_REQUEST_IID variables to be set + +echo "Processing vuln report" + +# Preparing the message for the comment that will be posted by the bot +# Empty string if there are no findings +jq -crM '.vulnerabilities | + map( select( .identifiers[0].name | test( "glappsec_" ) ) | + "- `" + .location.file + "` line " + ( .location.start_line | tostring ) + + ( + if .location.start_line = .location.end_line then "" + else ( " to " + ( .location.end_line | tostring ) ) end + ) + ": " + .message + ) | + sort | + if length > 0 then + { body: ("The findings below have been detected based on the AppSec custom SAST rules. For more information about this bot and what to do with this comment head over to the [README](https://gitlab.com/gitlab-com/gl-security/appsec/sast-custom-rules/-/tree/main/appsec-pings). The following lines of code possibly need attention:\n\n" + join("\n") + "\n\n/cc @gitlab-com/gl-security/appsec") } + else + empty + end' gl-sast-report.json >findings.txt + +echo "Resulting file:" +cat findings.txt + +EXISTING_COMMENT_ID=$(curl "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \ + --header "Private-Token: $CUSTOM_SAST_RULES_BOT_PAT" | + jq -crM 'map( select( .author.id == (env.BOT_USER_ID | tonumber) ) | .id ) | first') + +echo "EXISTING_COMMENT_ID: $EXISTING_COMMENT_ID" + +if [ "$EXISTING_COMMENT_ID" == "null" ]; then + if [ -s findings.txt ]; then + echo "No existing comment and there are findings: a new comment will be posted" + curl "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \ + --header "Private-Token: $CUSTOM_SAST_RULES_BOT_PAT" \ + --header 'Content-Type: application/json' \ + --data '@findings.txt' + else + echo "No existing comment and no findings: nothing to do" + fi +else + if [ -s findings.txt ]; then + echo "There is an existing comment and there are findings: the existing comment will be updated" + curl --request PUT "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes/$EXISTING_COMMENT_ID" \ + --header "Private-Token: $CUSTOM_SAST_RULES_BOT_PAT" \ + --header 'Content-Type: application/json' \ + --data '@findings.txt' + else + echo "There is an existing comment but no findings: the existing comment will be updated to mention everything is resolved" + curl --request PUT "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes/$EXISTING_COMMENT_ID" \ + --header "Private-Token: $CUSTOM_SAST_RULES_BOT_PAT" \ + --header 'Content-Type: application/json' \ + --data '{"body":"All findings based on the [AppSec custom Semgrep rules](https://gitlab.com/gitlab-com/gl-security/appsec/sast-custom-rules/) have been resolved! :tada:"}' + fi +fi diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index cfb4711be19..b08cf9ac832 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -130,7 +130,7 @@ function disable_sign_ups() { # Create the root token + Disable sign-ups local disable_signup_rb="token = User.find_by_username('root').personal_access_tokens.create(scopes: [:api], name: 'Token to disable sign-ups'); token.set_token('${REVIEW_APPS_ROOT_TOKEN}'); begin; token.save!; rescue(ActiveRecord::RecordNotUnique); end; Gitlab::CurrentSettings.current_application_settings.update!(signup_enabled: false)" - if (retry "run_task \"${disable_signup_rb}\""); then + if (retry_exponential "run_task \"${disable_signup_rb}\""); then echoinfo "Sign-ups have been disabled successfully." else echoerr "Sign-ups are still enabled!" diff --git a/scripts/utils.sh b/scripts/utils.sh index c71de666ac6..44bbabb4c99 100644 --- a/scripts/utils.sh +++ b/scripts/utils.sh @@ -10,6 +10,28 @@ function retry() { return 0 fi done + + return 1 +} + +# Retry after 2s, 4s, 8s, 16s, 32, 64s, 128s +function retry_exponential() { + if eval "$@"; then + return 0 + fi + + local sleep_time=0 + # The last try will be after 2**7 = 128 seconds (2min8s) + for i in 1 2 3 4 5 6 7; do + sleep_time=$((2 ** i)) + + echo "Sleep for $sleep_time seconds..." + sleep $sleep_time + echo "[$(date '+%H:%M:%S')] Attempt #$i..." + if eval "$@"; then + return 0 + fi + done return 1 } diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index eef5c593e0f..d68562c0aa5 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -237,7 +237,9 @@ FactoryBot.define do trait :with_job do after(:build) do |pipeline, evaluator| - pipeline.builds << build(:ci_build, pipeline: pipeline, project: pipeline.project) + stage = build(:ci_stage, pipeline: pipeline) + + pipeline.builds << build(:ci_build, pipeline: pipeline, project: pipeline.project, ci_stage: stage) end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index e4d4375a138..eafe74f4b0b 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -24,7 +24,8 @@ RSpec.describe 'Commits', feature_category: :source_code_management do end context 'commit status is Generic Commit Status' do - let!(:status) { create(:generic_commit_status, pipeline: pipeline, ref: pipeline.ref) } + let(:stage) { create(:ci_stage, pipeline: pipeline, name: 'external') } + let!(:status) { create(:generic_commit_status, pipeline: pipeline, ref: pipeline.ref, ci_stage: stage) } before do project.add_reporter(user) diff --git a/spec/frontend/boards/board_list_helper.js b/spec/frontend/boards/board_list_helper.js index 1ba546f24a8..d882ff071b7 100644 --- a/spec/frontend/boards/board_list_helper.js +++ b/spec/frontend/boards/board_list_helper.js @@ -22,6 +22,7 @@ export default function createComponent({ listIssueProps = {}, componentProps = {}, listProps = {}, + apolloQueryHandlers = [], actions = {}, getters = {}, provide = {}, @@ -39,6 +40,7 @@ export default function createComponent({ const fakeApollo = createMockApollo([ [listQuery, jest.fn().mockResolvedValue(boardListQueryResponse(issuesCount))], + ...apolloQueryHandlers, ]); const store = new Vuex.Store({ @@ -89,6 +91,7 @@ export default function createComponent({ list, boardItems: [issue], canAdminList: true, + boardId: 'gid://gitlab/Board/1', ...componentProps, }, provide: { @@ -104,6 +107,9 @@ export default function createComponent({ isGroupBoard: false, isProjectBoard: true, disabled: false, + boardType: 'group', + issuableType: 'issue', + isApolloBoard: false, ...provide, }, stubs, diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js index f8ad7c468c1..84e6318d98e 100644 --- a/spec/frontend/boards/components/board_card_spec.js +++ b/spec/frontend/boards/components/board_card_spec.js @@ -61,6 +61,7 @@ describe('Board card', () => { isProjectBoard: false, isGroupBoard: true, disabled: false, + isApolloBoard: false, ...provide, }, }); diff --git a/spec/frontend/boards/components/board_column_spec.js b/spec/frontend/boards/components/board_column_spec.js index d34e228a2d7..c0bb51620f2 100644 --- a/spec/frontend/boards/components/board_column_spec.js +++ b/spec/frontend/boards/components/board_column_spec.js @@ -35,6 +35,10 @@ describe('Board Column Component', () => { store, propsData: { list: listMock, + boardId: 'gid://gitlab/Board/1', + }, + provide: { + isApolloBoard: false, }, }); }; diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js index 658b2cc4be3..c3817323c56 100644 --- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js +++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js @@ -12,11 +12,7 @@ describe('SignInPage', () => { const findSignInGitlabCom = () => wrapper.findComponent(SignInGitlabCom); const findSignInGitabMultiversion = () => wrapper.findComponent(SignInGitlabMultiversion); - const createComponent = ({ - props = {}, - jiraConnectOauthEnabled, - jiraConnectOauthSelfManagedEnabled, - } = {}) => { + const createComponent = ({ props = {}, jiraConnectOauthEnabled } = {}) => { store = createStore(); wrapper = shallowMount(SignInPage, { @@ -24,7 +20,6 @@ describe('SignInPage', () => { provide: { glFeatures: { jiraConnectOauth: jiraConnectOauthEnabled, - jiraConnectOauthSelfManaged: jiraConnectOauthSelfManagedEnabled, }, }, propsData: { hasSubscriptions: false, ...props }, diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js index 7e941c5ceaa..170c28c3624 100644 --- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js @@ -7,6 +7,7 @@ import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_ import ActionButtons from '~/vue_merge_request_widget/components/widget/action_buttons.vue'; import Widget from '~/vue_merge_request_widget/components/widget/widget.vue'; import WidgetContentRow from '~/vue_merge_request_widget/components/widget/widget_content_row.vue'; +import * as logger from '~/lib/logger'; jest.mock('~/vue_merge_request_widget/components/extensions/telemetry', () => ({ createTelemetryHub: jest.fn().mockReturnValue({ @@ -32,7 +33,7 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { isCollapsible: false, loadingText: 'Loading widget', widgetName: 'WidgetTest', - fetchCollapsedData: () => Promise.resolve([]), + fetchCollapsedData: () => Promise.resolve({ headers: {}, status: 200 }), value: { collapsed: null, expanded: null, @@ -150,6 +151,21 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => { }); }); + it('throws an error when the handler does not include headers or status objects', async () => { + const error = new Error(Widget.MISSING_RESPONSE_HEADERS); + jest.spyOn(Sentry, 'captureException').mockImplementation(); + jest.spyOn(logger, 'logError').mockImplementation(); + createComponent({ + propsData: { + fetchCollapsedData: () => Promise.resolve({}), + }, + }); + await waitForPromises(); + expect(wrapper.emitted('input')).toBeUndefined(); + expect(Sentry.captureException).toHaveBeenCalledWith(error); + expect(logger.logError).toHaveBeenCalledWith(error.message); + }); + it('calls sentry when failed', async () => { const error = new Error('Something went wrong'); jest.spyOn(Sentry, 'captureException').mockImplementation(); diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index 4b750cf3bcf..2c07e4d2224 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -37,7 +37,7 @@ RSpec.describe Gitlab::Ci::CronParser do end end - context 'when slash used' do + context 'when */ used' do let(:cron) { '*/10 */6 */10 */10 *' } let(:cron_timezone) { 'UTC' } @@ -63,7 +63,7 @@ RSpec.describe Gitlab::Ci::CronParser do end end - context 'when range and slash used' do + context 'when range and / are used' do let(:cron) { '3-59/10 * * * *' } let(:cron_timezone) { 'UTC' } @@ -74,6 +74,17 @@ RSpec.describe Gitlab::Ci::CronParser do end end + context 'when / is used' do + let(:cron) { '3/10 * * * *' } + let(:cron_timezone) { 'UTC' } + + it_behaves_like returns_time_for_epoch + + it 'returns specific time' do + expect(subject.min).to be_in([3, 13, 23, 33, 43, 53]) + end + end + context 'when cron_timezone is TZInfo format' do before do allow(Time).to receive(:zone) diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index d08a60af50f..4ff451af9de 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -802,64 +802,70 @@ RSpec.describe CommitStatus do end describe 'ensure stage assignment' do - context 'when commit status has a stage_id assigned' do - let!(:stage) do - create(:ci_stage, project: project, pipeline: pipeline) - end + before do + stub_feature_flags(ci_remove_ensure_stage_service: false) + end - let(:commit_status) do - create(:commit_status, stage_id: stage.id, name: 'rspec', stage: 'test') - end + context 'when the feature flag ci_remove_ensure_stage_service is disabled' do + context 'when commit status has a stage_id assigned' do + let!(:stage) do + create(:ci_stage, project: project, pipeline: pipeline) + end - it 'does not create a new stage' do - expect { commit_status }.not_to change { Ci::Stage.count } - expect(commit_status.stage_id).to eq stage.id - end - end + let(:commit_status) do + create(:commit_status, stage_id: stage.id, name: 'rspec', stage: 'test') + end - context 'when commit status does not have a stage_id assigned' do - let(:commit_status) do - create(:commit_status, name: 'rspec', stage: 'test', status: :success) + it 'does not create a new stage' do + expect { commit_status }.not_to change { Ci::Stage.count } + expect(commit_status.stage_id).to eq stage.id + end end - let(:stage) { Ci::Stage.first } + context 'when commit status does not have a stage_id assigned' do + let(:commit_status) do + create(:commit_status, name: 'rspec', stage: 'test', status: :success) + end - it 'creates a new stage', :sidekiq_might_not_need_inline do - expect { commit_status }.to change { Ci::Stage.count }.by(1) + let(:stage) { Ci::Stage.first } - expect(stage.name).to eq 'test' - expect(stage.project).to eq commit_status.project - expect(stage.pipeline).to eq commit_status.pipeline - expect(stage.status).to eq commit_status.status - expect(commit_status.stage_id).to eq stage.id - end - end + it 'creates a new stage', :sidekiq_might_not_need_inline do + expect { commit_status }.to change { Ci::Stage.count }.by(1) - context 'when commit status does not have stage but it exists' do - let!(:stage) do - create(:ci_stage, project: project, pipeline: pipeline, name: 'test') + expect(stage.name).to eq 'test' + expect(stage.project).to eq commit_status.project + expect(stage.pipeline).to eq commit_status.pipeline + expect(stage.status).to eq commit_status.status + expect(commit_status.stage_id).to eq stage.id + end end - let(:commit_status) do - create(:commit_status, project: project, pipeline: pipeline, name: 'rspec', stage: 'test', status: :success) - end + context 'when commit status does not have stage but it exists' do + let!(:stage) do + create(:ci_stage, project: project, pipeline: pipeline, name: 'test') + end - it 'uses existing stage', :sidekiq_might_not_need_inline do - expect { commit_status }.not_to change { Ci::Stage.count } + let(:commit_status) do + create(:commit_status, project: project, pipeline: pipeline, name: 'rspec', stage: 'test', status: :success) + end - expect(commit_status.stage_id).to eq stage.id - expect(stage.reload.status).to eq commit_status.status - end - end + it 'uses existing stage', :sidekiq_might_not_need_inline do + expect { commit_status }.not_to change { Ci::Stage.count } - context 'when commit status is being imported' do - let(:commit_status) do - create(:commit_status, name: 'rspec', stage: 'test', importing: true) + expect(commit_status.stage_id).to eq stage.id + expect(stage.reload.status).to eq commit_status.status + end end - it 'does not create a new stage' do - expect { commit_status }.not_to change { Ci::Stage.count } - expect(commit_status.stage_id).not_to be_present + context 'when commit status is being imported' do + let(:commit_status) do + create(:commit_status, name: 'rspec', stage: 'test', importing: true) + end + + it 'does not create a new stage' do + expect { commit_status }.not_to change { Ci::Stage.count } + expect(commit_status.stage_id).not_to be_present + end end end end @@ -1007,6 +1013,10 @@ RSpec.describe CommitStatus do describe '.stage_name' do subject(:stage_name) { commit_status.stage_name } + before do + commit_status.ci_stage = build(:ci_stage) + end + it 'returns the stage name' do expect(stage_name).to eq('test') end diff --git a/spec/models/project_ci_cd_setting_spec.rb b/spec/models/project_ci_cd_setting_spec.rb index 5a32e103e0f..47e0ef56844 100644 --- a/spec/models/project_ci_cd_setting_spec.rb +++ b/spec/models/project_ci_cd_setting_spec.rb @@ -27,6 +27,34 @@ RSpec.describe ProjectCiCdSetting do end end + describe '#set_default_for_inbound_job_token_scope_enabled' do + context 'when feature flag ci_inbound_job_token_scope is enabled' do + before do + stub_feature_flags(ci_inbound_job_token_scope: true) + end + + it 'sets inbound_job_token_scope_enabled to true' do + project = build(:project) + setting = project.build_ci_cd_settings + + expect(setting.inbound_job_token_scope_enabled).to eq(true) + end + end + + context 'when feature flag ci_inbound_job_token_scope is disabled' do + before do + stub_feature_flags(ci_inbound_job_token_scope: false) + end + + it 'sets inbound_job_token_scope_enabled to false' do + project = build(:project) + setting = project.build_ci_cd_settings + + expect(setting.inbound_job_token_scope_enabled).to eq(false) + end + end + end + describe '#default_git_depth' do let(:default_value) { described_class::DEFAULT_GIT_DEPTH } diff --git a/spec/requests/api/graphql/ci/groups_spec.rb b/spec/requests/api/graphql/ci/groups_spec.rb index d1588833d8f..1874e1d35dd 100644 --- a/spec/requests/api/graphql/ci/groups_spec.rb +++ b/spec/requests/api/graphql/ci/groups_spec.rb @@ -10,8 +10,9 @@ RSpec.describe 'Query.project.pipeline.stages.groups', feature_category: :contin let(:group_graphql_data) { graphql_data_at(:project, :pipeline, :stages, :nodes, 0, :groups, :nodes) } let_it_be(:ref) { 'master' } - let_it_be(:job_a) { create(:commit_status, pipeline: pipeline, name: 'rspec 0 2', ref: ref) } - let_it_be(:job_b) { create(:ci_build, pipeline: pipeline, name: 'rspec 0 1', ref: ref) } + let_it_be(:stage) { create(:ci_stage, pipeline: pipeline) } + let_it_be(:job_a) { create(:commit_status, pipeline: pipeline, name: 'rspec 0 2', ref: ref, ci_stage: stage) } + let_it_be(:job_b) { create(:ci_build, pipeline: pipeline, name: 'rspec 0 1', ref: ref, ci_stage: stage) } let_it_be(:job_c) { create(:ci_bridge, pipeline: pipeline, name: 'spinach 0 1', ref: ref) } let(:params) { {} } diff --git a/spec/serializers/pipeline_details_entity_spec.rb b/spec/serializers/pipeline_details_entity_spec.rb index 764d55e8b4a..52a01787711 100644 --- a/spec/serializers/pipeline_details_entity_spec.rb +++ b/spec/serializers/pipeline_details_entity_spec.rb @@ -104,7 +104,7 @@ RSpec.describe PipelineDetailsEntity do let(:pipeline) { create(:ci_empty_pipeline) } before do - create(:commit_status, pipeline: pipeline) + create(:ci_build, pipeline: pipeline) end it 'contains stages' do |