Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									e7e44c0e4c
								
							
						
					
					
						commit
						524e972622
					
				|  | @ -1,10 +1,51 @@ | ||||||
|  | cloud-native-image-env: | ||||||
|  |   extends: | ||||||
|  |     - .default-retry | ||||||
|  |     - .cng:rules | ||||||
|  |   image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine3.13 | ||||||
|  |   stage: post-test | ||||||
|  |   before_script: | ||||||
|  |     - source ./scripts/utils.sh | ||||||
|  |     - install_gitlab_gem | ||||||
|  |   script: | ||||||
|  |     - 'ruby -r./scripts/trigger-build.rb -e "puts Trigger.variables_for_env_file(Trigger::CNG.new.variables)" > build.env' | ||||||
|  |     - cat build.env | ||||||
|  |   artifacts: | ||||||
|  |     reports: | ||||||
|  |       dotenv: build.env | ||||||
|  |     paths: | ||||||
|  |       - build.env | ||||||
|  |     expire_in: 7 days | ||||||
|  |     when: always | ||||||
|  | 
 | ||||||
| cloud-native-image: | cloud-native-image: | ||||||
|   extends: .cng:rules |   extends: .cng:rules | ||||||
|   image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine |  | ||||||
|   dependencies: [] |  | ||||||
|   stage: post-test |   stage: post-test | ||||||
|  |   needs: ["cloud-native-image-env"] | ||||||
|  |   inherit: | ||||||
|  |     variables: false | ||||||
|   variables: |   variables: | ||||||
|     GIT_DEPTH: "1" |     TOP_UPSTREAM_SOURCE_PROJECT: "${TOP_UPSTREAM_SOURCE_PROJECT}" | ||||||
|   script: |     TOP_UPSTREAM_SOURCE_REF: "${TOP_UPSTREAM_SOURCE_REF}" | ||||||
|     - install_gitlab_gem |     TOP_UPSTREAM_SOURCE_JOB: "${TOP_UPSTREAM_SOURCE_JOB}" | ||||||
|     - ./scripts/trigger-build cng |     TOP_UPSTREAM_SOURCE_SHA: "${TOP_UPSTREAM_SOURCE_SHA}" | ||||||
|  |     TOP_UPSTREAM_MERGE_REQUEST_PROJECT_ID: "${TOP_UPSTREAM_MERGE_REQUEST_PROJECT_ID}" | ||||||
|  |     TOP_UPSTREAM_MERGE_REQUEST_IID: "${TOP_UPSTREAM_MERGE_REQUEST_IID}" | ||||||
|  |     GITLAB_REF_SLUG: "${GITLAB_REF_SLUG}" | ||||||
|  |     # CNG pipeline specific variables | ||||||
|  |     GITLAB_VERSION: "${GITLAB_VERSION}" | ||||||
|  |     GITLAB_TAG: "${GITLAB_TAG}" | ||||||
|  |     GITLAB_ASSETS_TAG: "${GITLAB_ASSETS_TAG}" | ||||||
|  |     FORCE_RAILS_IMAGE_BUILDS: "${FORCE_RAILS_IMAGE_BUILDS}" | ||||||
|  |     CE_PIPELINE: "${CE_PIPELINE}"  # Based on https://docs.gitlab.com/ee/ci/jobs/job_control.html#check-if-a-variable-exists, `if: '$CE_PIPELINE'` will evaluate to `false` when this variable is empty | ||||||
|  |     EE_PIPELINE: "${EE_PIPELINE}"  # Based on https://docs.gitlab.com/ee/ci/jobs/job_control.html#check-if-a-variable-exists, `if: '$EE_PIPELINE'` will evaluate to `false` when this variable is empty | ||||||
|  |     GITLAB_SHELL_VERSION: "${GITLAB_SHELL_VERSION}" | ||||||
|  |     GITLAB_ELASTICSEARCH_INDEXER_VERSION: "${GITLAB_ELASTICSEARCH_INDEXER_VERSION}" | ||||||
|  |     GITLAB_KAS_VERSION: "${GITLAB_KAS_VERSION}" | ||||||
|  |     GITLAB_WORKHORSE_VERSION: "${GITLAB_WORKHORSE_VERSION}" | ||||||
|  |     GITLAB_PAGES_VERSION: "${GITLAB_PAGES_VERSION}" | ||||||
|  |     GITALY_SERVER_VERSION: "${GITALY_SERVER_VERSION}" | ||||||
|  |   trigger: | ||||||
|  |     project: gitlab-org/build/CNG | ||||||
|  |     branch: $TRIGGER_BRANCH | ||||||
|  |     strategy: depend | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ | ||||||
| review-docs-deploy: | review-docs-deploy: | ||||||
|   extends: .review-docs |   extends: .review-docs | ||||||
|   script: |   script: | ||||||
|     - ./scripts/trigger-build docs deploy |     - ./scripts/trigger-build.rb docs deploy | ||||||
| 
 | 
 | ||||||
| # Cleanup remote environment of gitlab-docs | # Cleanup remote environment of gitlab-docs | ||||||
| review-docs-cleanup: | review-docs-cleanup: | ||||||
|  | @ -37,7 +37,7 @@ review-docs-cleanup: | ||||||
|     name: review-docs/mr-${CI_MERGE_REQUEST_IID} |     name: review-docs/mr-${CI_MERGE_REQUEST_IID} | ||||||
|     action: stop |     action: stop | ||||||
|   script: |   script: | ||||||
|     - ./scripts/trigger-build docs cleanup |     - ./scripts/trigger-build.rb docs cleanup | ||||||
| 
 | 
 | ||||||
| docs-lint markdown: | docs-lint markdown: | ||||||
|   extends: |   extends: | ||||||
|  |  | ||||||
|  | @ -73,7 +73,7 @@ update-qa-cache: | ||||||
|     - echo $exit_code |     - echo $exit_code | ||||||
|     - | |     - | | ||||||
|       if [ $exit_code -eq 0 ]; then |       if [ $exit_code -eq 0 ]; then | ||||||
|         ./scripts/trigger-build omnibus |         ./scripts/trigger-build.rb omnibus | ||||||
|       elif [ $exit_code -eq 1 ]; then |       elif [ $exit_code -eq 1 ]; then | ||||||
|         exit 1 |         exit 1 | ||||||
|       else |       else | ||||||
|  | @ -108,7 +108,7 @@ update-qa-cache: | ||||||
|       if [[ $feature_flags ]]; then |       if [[ $feature_flags ]]; then | ||||||
|         export GITLAB_QA_OPTIONS="--set-feature-flags $feature_flags" |         export GITLAB_QA_OPTIONS="--set-feature-flags $feature_flags" | ||||||
|         echo $GITLAB_QA_OPTIONS |         echo $GITLAB_QA_OPTIONS | ||||||
|         ./scripts/trigger-build omnibus |         ./scripts/trigger-build.rb omnibus | ||||||
|       else |       else | ||||||
|         echo "No changed feature flag found to test. The tests are skipped if the flag was removed." |         echo "No changed feature flag found to test. The tests are skipped if the flag was removed." | ||||||
|       fi |       fi | ||||||
|  |  | ||||||
|  | @ -438,7 +438,7 @@ db:gitlabcom-database-testing: | ||||||
|   script: |   script: | ||||||
|     - source scripts/utils.sh |     - source scripts/utils.sh | ||||||
|     - install_gitlab_gem |     - install_gitlab_gem | ||||||
|     - ./scripts/trigger-build gitlab-com-database-testing |     - ./scripts/trigger-build.rb gitlab-com-database-testing | ||||||
| 
 | 
 | ||||||
| gitlab:setup: | gitlab:setup: | ||||||
|   extends: .db-job-base |   extends: .db-job-base | ||||||
|  |  | ||||||
|  | @ -16,20 +16,58 @@ include: | ||||||
|   - source ./scripts/review_apps/review-apps.sh |   - source ./scripts/review_apps/review-apps.sh | ||||||
|   - install_api_client_dependencies_with_apk |   - install_api_client_dependencies_with_apk | ||||||
| 
 | 
 | ||||||
| review-build-cng: | review-build-cng-env: | ||||||
|   extends: |   extends: | ||||||
|     - .default-retry |     - .default-retry | ||||||
|     - .review:rules:review-build-cng |     - .review:rules:review-build-cng | ||||||
|   image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine3.13 |   image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine3.13 | ||||||
|   stage: prepare |   stage: prepare | ||||||
|   variables: |   needs: [] | ||||||
|     CNG_PROJECT_ACCESS_TOKEN: "${CNG_MIRROR_PROJECT_ACCESS_TOKEN}"  # "Multi-pipeline (from 'gitlab-org/gitlab' 'review-build-cng' job)" at https://gitlab.com/gitlab-org/build/CNG-mirror/-/settings/access_tokens |  | ||||||
|     CNG_PROJECT_PATH: "gitlab-org/build/CNG-mirror" |  | ||||||
|   before_script: |   before_script: | ||||||
|     - source ./scripts/utils.sh |     - source ./scripts/utils.sh | ||||||
|     - install_gitlab_gem |     - install_gitlab_gem | ||||||
|   script: |   script: | ||||||
|     - ./scripts/trigger-build cng |     - 'ruby -r./scripts/trigger-build.rb -e "puts Trigger.variables_for_env_file(Trigger::CNG.new.variables)" > build.env' | ||||||
|  |     - cat build.env | ||||||
|  |   artifacts: | ||||||
|  |     reports: | ||||||
|  |       dotenv: build.env | ||||||
|  |     paths: | ||||||
|  |       - build.env | ||||||
|  |     expire_in: 7 days | ||||||
|  |     when: always | ||||||
|  | 
 | ||||||
|  | review-build-cng: | ||||||
|  |   extends: .review:rules:review-build-cng | ||||||
|  |   stage: prepare | ||||||
|  |   needs: ["review-build-cng-env"] | ||||||
|  |   inherit: | ||||||
|  |     variables: false | ||||||
|  |   variables: | ||||||
|  |     TOP_UPSTREAM_SOURCE_PROJECT: "${TOP_UPSTREAM_SOURCE_PROJECT}" | ||||||
|  |     TOP_UPSTREAM_SOURCE_REF: "${TOP_UPSTREAM_SOURCE_REF}" | ||||||
|  |     TOP_UPSTREAM_SOURCE_JOB: "${TOP_UPSTREAM_SOURCE_JOB}" | ||||||
|  |     TOP_UPSTREAM_SOURCE_SHA: "${TOP_UPSTREAM_SOURCE_SHA}" | ||||||
|  |     TOP_UPSTREAM_MERGE_REQUEST_PROJECT_ID: "${TOP_UPSTREAM_MERGE_REQUEST_PROJECT_ID}" | ||||||
|  |     TOP_UPSTREAM_MERGE_REQUEST_IID: "${TOP_UPSTREAM_MERGE_REQUEST_IID}" | ||||||
|  |     GITLAB_REF_SLUG: "${GITLAB_REF_SLUG}" | ||||||
|  |     # CNG pipeline specific variables | ||||||
|  |     GITLAB_VERSION: "${GITLAB_VERSION}" | ||||||
|  |     GITLAB_TAG: "${GITLAB_TAG}" | ||||||
|  |     GITLAB_ASSETS_TAG: "${GITLAB_ASSETS_TAG}" | ||||||
|  |     FORCE_RAILS_IMAGE_BUILDS: "${FORCE_RAILS_IMAGE_BUILDS}" | ||||||
|  |     CE_PIPELINE: "${CE_PIPELINE}"  # Based on https://docs.gitlab.com/ee/ci/jobs/job_control.html#check-if-a-variable-exists, `if: '$CE_PIPELINE'` will evaluate to `false` when this variable is empty | ||||||
|  |     EE_PIPELINE: "${EE_PIPELINE}"  # Based on https://docs.gitlab.com/ee/ci/jobs/job_control.html#check-if-a-variable-exists, `if: '$EE_PIPELINE'` will evaluate to `false` when this variable is empty | ||||||
|  |     GITLAB_SHELL_VERSION: "${GITLAB_SHELL_VERSION}" | ||||||
|  |     GITLAB_ELASTICSEARCH_INDEXER_VERSION: "${GITLAB_ELASTICSEARCH_INDEXER_VERSION}" | ||||||
|  |     GITLAB_KAS_VERSION: "${GITLAB_KAS_VERSION}" | ||||||
|  |     GITLAB_WORKHORSE_VERSION: "${GITLAB_WORKHORSE_VERSION}" | ||||||
|  |     GITLAB_PAGES_VERSION: "${GITLAB_PAGES_VERSION}" | ||||||
|  |     GITALY_SERVER_VERSION: "${GITALY_SERVER_VERSION}" | ||||||
|  |   trigger: | ||||||
|  |     project: gitlab-org/build/CNG-mirror | ||||||
|  |     branch: $TRIGGER_BRANCH | ||||||
|  |     strategy: depend | ||||||
| 
 | 
 | ||||||
| .review-workflow-base: | .review-workflow-base: | ||||||
|   extends: |   extends: | ||||||
|  |  | ||||||
|  | @ -141,7 +141,7 @@ | ||||||
|   - ".gitlab/ci/review-apps/**/*" |   - ".gitlab/ci/review-apps/**/*" | ||||||
|   - "scripts/review_apps/base-config.yaml" |   - "scripts/review_apps/base-config.yaml" | ||||||
|   - "scripts/review_apps/review-apps.sh" |   - "scripts/review_apps/review-apps.sh" | ||||||
|   - "scripts/trigger-build" |   - "scripts/trigger-build.rb" | ||||||
|   - "{,ee/,jh/}{bin,config}/**/*.rb" |   - "{,ee/,jh/}{bin,config}/**/*.rb" | ||||||
| 
 | 
 | ||||||
| .ci-qa-patterns: &ci-qa-patterns | .ci-qa-patterns: &ci-qa-patterns | ||||||
|  |  | ||||||
|  | @ -24,7 +24,6 @@ Database/MultipleDatabases: | ||||||
|   - lib/gitlab/import_export/group/relation_tree_restorer.rb |   - lib/gitlab/import_export/group/relation_tree_restorer.rb | ||||||
|   - lib/gitlab/legacy_github_import/importer.rb |   - lib/gitlab/legacy_github_import/importer.rb | ||||||
|   - lib/gitlab/seeder.rb |   - lib/gitlab/seeder.rb | ||||||
|   - lib/system_check/orphans/repository_check.rb |  | ||||||
|   - spec/db/schema_spec.rb |   - spec/db/schema_spec.rb | ||||||
|   - spec/initializers/database_config_spec.rb |   - spec/initializers/database_config_spec.rb | ||||||
|   - spec/lib/backup/manager_spec.rb |   - spec/lib/backup/manager_spec.rb | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						
									
										11
									
								
								CHANGELOG.md
								
								
								
								
							|  | @ -2,6 +2,17 @@ | ||||||
| documentation](doc/development/changelog.md) for instructions on adding your own | documentation](doc/development/changelog.md) for instructions on adding your own | ||||||
| entry. | entry. | ||||||
| 
 | 
 | ||||||
|  | ## 14.7.3 (2022-02-15) | ||||||
|  | 
 | ||||||
|  | ### Fixed (2 changes) | ||||||
|  | 
 | ||||||
|  | - [Update GitHub PRs Importer to force update repository](gitlab-org/gitlab@33f12736b070362cb89e9bbb4b3aa7d86fc373c3) ([merge request](gitlab-org/gitlab!80595)) | ||||||
|  | - [Fix Geo checksummable check failing when file is nil](gitlab-org/gitlab@f49e3ea3e4d4ca7a64607687f9aaa974801b6bf9) ([merge request](gitlab-org/gitlab!80595)) **GitLab Enterprise Edition** | ||||||
|  | 
 | ||||||
|  | ### Changed (1 change) | ||||||
|  | 
 | ||||||
|  | - [Properly exclude pending_destruction packages when creating one](gitlab-org/gitlab@9fb9f1ca8a2342225b7017c211f85175a4ef56dd) ([merge request](gitlab-org/gitlab!80595)) | ||||||
|  | 
 | ||||||
| ## 14.7.2 (2022-02-08) | ## 14.7.2 (2022-02-08) | ||||||
| 
 | 
 | ||||||
| ### Added (1 change) | ### Added (1 change) | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| d3ab199f7923a9d75516b8d1f1ea2f84b03190b1 | a67a6fdd96ba690d57c919f9a042dceebab2832e | ||||||
|  |  | ||||||
|  | @ -44,6 +44,9 @@ export const typePolicies = { | ||||||
|   PipelinePermissions: { |   PipelinePermissions: { | ||||||
|     merge: true, |     merge: true, | ||||||
|   }, |   }, | ||||||
|  |   DesignCollection: { | ||||||
|  |     merge: true, | ||||||
|  |   }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const stripWhitespaceFromQuery = (url, path) => { | export const stripWhitespaceFromQuery = (url, path) => { | ||||||
|  |  | ||||||
|  | @ -2,31 +2,14 @@ | ||||||
| import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui'; | import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui'; | ||||||
| import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; | import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; | ||||||
| import { getIdFromGraphQLId } from '~/graphql_shared/utils'; | import { getIdFromGraphQLId } from '~/graphql_shared/utils'; | ||||||
| import { formatNumber, __, s__ } from '~/locale'; | import { __, s__ } from '~/locale'; | ||||||
| import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; | import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; | ||||||
| import { RUNNER_JOB_COUNT_LIMIT } from '../constants'; | import { formatJobCount, tableField } from '../utils'; | ||||||
| import RunnerActionsCell from './cells/runner_actions_cell.vue'; | import RunnerActionsCell from './cells/runner_actions_cell.vue'; | ||||||
| import RunnerSummaryCell from './cells/runner_summary_cell.vue'; | import RunnerSummaryCell from './cells/runner_summary_cell.vue'; | ||||||
| import RunnerStatusCell from './cells/runner_status_cell.vue'; | import RunnerStatusCell from './cells/runner_status_cell.vue'; | ||||||
| import RunnerTags from './runner_tags.vue'; | import RunnerTags from './runner_tags.vue'; | ||||||
| 
 | 
 | ||||||
| const tableField = ({ key, label = '', thClasses = [] }) => { |  | ||||||
|   return { |  | ||||||
|     key, |  | ||||||
|     label, |  | ||||||
|     thClass: [ |  | ||||||
|       'gl-bg-transparent!', |  | ||||||
|       'gl-border-b-solid!', |  | ||||||
|       'gl-border-b-gray-100!', |  | ||||||
|       'gl-border-b-1!', |  | ||||||
|       ...thClasses, |  | ||||||
|     ], |  | ||||||
|     tdAttr: { |  | ||||||
|       'data-testid': `td-${key}`, |  | ||||||
|     }, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|     GlTable, |     GlTable, | ||||||
|  | @ -54,10 +37,7 @@ export default { | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     formatJobCount(jobCount) { |     formatJobCount(jobCount) { | ||||||
|       if (jobCount > RUNNER_JOB_COUNT_LIMIT) { |       return formatJobCount(jobCount); | ||||||
|         return `${formatNumber(RUNNER_JOB_COUNT_LIMIT)}+`; |  | ||||||
|       } |  | ||||||
|       return formatNumber(jobCount); |  | ||||||
|     }, |     }, | ||||||
|     runnerTrAttr(runner) { |     runnerTrAttr(runner) { | ||||||
|       if (runner) { |       if (runner) { | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import { | ||||||
|   I18N_FETCH_ERROR, |   I18N_FETCH_ERROR, | ||||||
|   RUNNER_DETAILS_PROJECTS_PAGE_SIZE, |   RUNNER_DETAILS_PROJECTS_PAGE_SIZE, | ||||||
| } from '../constants'; | } from '../constants'; | ||||||
|  | import { getPaginationVariables } from '../utils'; | ||||||
| import { captureException } from '../sentry_utils'; | import { captureException } from '../sentry_utils'; | ||||||
| import RunnerAssignedItem from './runner_assigned_item.vue'; | import RunnerAssignedItem from './runner_assigned_item.vue'; | ||||||
| import RunnerPagination from './runner_pagination.vue'; | import RunnerPagination from './runner_pagination.vue'; | ||||||
|  | @ -62,19 +63,9 @@ export default { | ||||||
|   computed: { |   computed: { | ||||||
|     variables() { |     variables() { | ||||||
|       const { id } = this.runner; |       const { id } = this.runner; | ||||||
|       const { before, after } = this.pagination; |  | ||||||
| 
 |  | ||||||
|       if (before) { |  | ||||||
|         return { |  | ||||||
|           id, |  | ||||||
|           before, |  | ||||||
|           last: RUNNER_DETAILS_PROJECTS_PAGE_SIZE, |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
|       return { |       return { | ||||||
|         id, |         id, | ||||||
|         after, |         ...getPaginationVariables(this.pagination, RUNNER_DETAILS_PROJECTS_PAGE_SIZE), | ||||||
|         first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE, |  | ||||||
|       }; |       }; | ||||||
|     }, |     }, | ||||||
|     loading() { |     loading() { | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ import { | ||||||
|   RUNNER_PAGE_SIZE, |   RUNNER_PAGE_SIZE, | ||||||
|   STATUS_NEVER_CONTACTED, |   STATUS_NEVER_CONTACTED, | ||||||
| } from './constants'; | } from './constants'; | ||||||
|  | import { getPaginationVariables } from './utils'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The filters and sorting of the runners are built around |  * The filters and sorting of the runners are built around | ||||||
|  | @ -184,30 +185,27 @@ export const fromSearchToVariables = ({ | ||||||
|   sort = null, |   sort = null, | ||||||
|   pagination = {}, |   pagination = {}, | ||||||
| } = {}) => { | } = {}) => { | ||||||
|   const variables = {}; |   const filterVariables = {}; | ||||||
| 
 | 
 | ||||||
|   const queryObj = filterToQueryObject(processFilters(filters), { |   const queryObj = filterToQueryObject(processFilters(filters), { | ||||||
|     filteredSearchTermKey: PARAM_KEY_SEARCH, |     filteredSearchTermKey: PARAM_KEY_SEARCH, | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   [variables.status] = queryObj[PARAM_KEY_STATUS] || []; |   [filterVariables.status] = queryObj[PARAM_KEY_STATUS] || []; | ||||||
|   variables.search = queryObj[PARAM_KEY_SEARCH]; |   filterVariables.search = queryObj[PARAM_KEY_SEARCH]; | ||||||
|   variables.tagList = queryObj[PARAM_KEY_TAG]; |   filterVariables.tagList = queryObj[PARAM_KEY_TAG]; | ||||||
| 
 | 
 | ||||||
|   if (runnerType) { |   if (runnerType) { | ||||||
|     variables.type = runnerType; |     filterVariables.type = runnerType; | ||||||
|   } |   } | ||||||
|   if (sort) { |   if (sort) { | ||||||
|     variables.sort = sort; |     filterVariables.sort = sort; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (pagination.before) { |   const paginationVariables = getPaginationVariables(pagination, RUNNER_PAGE_SIZE); | ||||||
|     variables.before = pagination.before; |  | ||||||
|     variables.last = RUNNER_PAGE_SIZE; |  | ||||||
|   } else { |  | ||||||
|     variables.after = pagination.after; |  | ||||||
|     variables.first = RUNNER_PAGE_SIZE; |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   return variables; |   return { | ||||||
|  |     ...filterVariables, | ||||||
|  |     ...paginationVariables, | ||||||
|  |   }; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,72 @@ | ||||||
|  | import { formatNumber } from '~/locale'; | ||||||
|  | import { DEFAULT_TH_CLASSES } from '~/lib/utils/constants'; | ||||||
|  | import { RUNNER_JOB_COUNT_LIMIT } from './constants'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Formats a job count, limited to a max number | ||||||
|  |  * | ||||||
|  |  * @param {Number} jobCount | ||||||
|  |  * @returns Formatted string | ||||||
|  |  */ | ||||||
|  | export const formatJobCount = (jobCount) => { | ||||||
|  |   if (typeof jobCount !== 'number') { | ||||||
|  |     return ''; | ||||||
|  |   } | ||||||
|  |   if (jobCount > RUNNER_JOB_COUNT_LIMIT) { | ||||||
|  |     return `${formatNumber(RUNNER_JOB_COUNT_LIMIT)}+`; | ||||||
|  |   } | ||||||
|  |   return formatNumber(jobCount); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Returns a GlTable fields with a given key and label | ||||||
|  |  * | ||||||
|  |  * @param {Object} options | ||||||
|  |  * @returns Field object to add to GlTable fields | ||||||
|  |  */ | ||||||
|  | export const tableField = ({ key, label = '', thClasses = [] }) => { | ||||||
|  |   return { | ||||||
|  |     key, | ||||||
|  |     label, | ||||||
|  |     thClass: [DEFAULT_TH_CLASSES, ...thClasses], | ||||||
|  |     tdAttr: { | ||||||
|  |       'data-testid': `td-${key}`, | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Returns variables for a GraphQL query that uses keyset | ||||||
|  |  * pagination. | ||||||
|  |  * | ||||||
|  |  * https://docs.gitlab.com/ee/development/graphql_guide/pagination.html#keyset-pagination
 | ||||||
|  |  * | ||||||
|  |  * @param {Object} pagination - Contains before, after, page | ||||||
|  |  * @param {Number} pageSize | ||||||
|  |  * @returns Variables | ||||||
|  |  */ | ||||||
|  | export const getPaginationVariables = (pagination, pageSize = 10) => { | ||||||
|  |   const { before, after } = pagination; | ||||||
|  | 
 | ||||||
|  |   // first + after: Next page
 | ||||||
|  |   // Get the first N items after item X
 | ||||||
|  |   if (after) { | ||||||
|  |     return { | ||||||
|  |       after, | ||||||
|  |       first: pageSize, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // last + before: Prev page
 | ||||||
|  |   // Get the first N items before item X, when you click on Prev
 | ||||||
|  |   if (before) { | ||||||
|  |     return { | ||||||
|  |       before, | ||||||
|  |       last: pageSize, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // first page
 | ||||||
|  |   // Get the first N items
 | ||||||
|  |   return { first: pageSize }; | ||||||
|  | }; | ||||||
|  | @ -10,6 +10,7 @@ import { | ||||||
|   GlIcon, |   GlIcon, | ||||||
|   GlTooltipDirective, |   GlTooltipDirective, | ||||||
| } from '@gitlab/ui'; | } from '@gitlab/ui'; | ||||||
|  | import { kebabCase, snakeCase } from 'lodash'; | ||||||
| import createFlash from '~/flash'; | import createFlash from '~/flash'; | ||||||
| import { getIdFromGraphQLId } from '~/graphql_shared/utils'; | import { getIdFromGraphQLId } from '~/graphql_shared/utils'; | ||||||
| import { IssuableType } from '~/issues/constants'; | import { IssuableType } from '~/issues/constants'; | ||||||
|  | @ -221,6 +222,12 @@ export default { | ||||||
|       // MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311 |       // MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311 | ||||||
|       return this.issuableAttribute === IssuableType.Epic; |       return this.issuableAttribute === IssuableType.Epic; | ||||||
|     }, |     }, | ||||||
|  |     formatIssuableAttribute() { | ||||||
|  |       return { | ||||||
|  |         kebab: kebabCase(this.issuableAttribute), | ||||||
|  |         snake: snakeCase(this.issuableAttribute), | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     updateAttribute(attributeId) { |     updateAttribute(attributeId) { | ||||||
|  | @ -300,26 +307,28 @@ export default { | ||||||
|   <sidebar-editable-item |   <sidebar-editable-item | ||||||
|     ref="editable" |     ref="editable" | ||||||
|     :title="attributeTypeTitle" |     :title="attributeTypeTitle" | ||||||
|     :data-testid="`${issuableAttribute}-edit`" |     :data-testid="`${formatIssuableAttribute.kebab}-edit`" | ||||||
|     :tracking="tracking" |     :tracking="tracking" | ||||||
|     :loading="updating || loading" |     :loading="updating || loading" | ||||||
|     @open="handleOpen" |     @open="handleOpen" | ||||||
|     @close="handleClose" |     @close="handleClose" | ||||||
|   > |   > | ||||||
|     <template #collapsed> |     <template #collapsed> | ||||||
|  |       <slot name="value-collapsed" :current-attribute="currentAttribute"> | ||||||
|  |         <div | ||||||
|  |           v-if="isClassicSidebar" | ||||||
|  |           v-gl-tooltip.left.viewport | ||||||
|  |           :title="attributeTypeTitle" | ||||||
|  |           class="sidebar-collapsed-icon" | ||||||
|  |         > | ||||||
|  |           <gl-icon :aria-label="attributeTypeTitle" :name="attributeTypeIcon" /> | ||||||
|  |           <span class="collapse-truncated-title"> | ||||||
|  |             {{ attributeTitle }} | ||||||
|  |           </span> | ||||||
|  |         </div> | ||||||
|  |       </slot> | ||||||
|       <div |       <div | ||||||
|         v-if="isClassicSidebar" |         :data-testid="`select-${formatIssuableAttribute.kebab}`" | ||||||
|         v-gl-tooltip.left.viewport |  | ||||||
|         :title="attributeTypeTitle" |  | ||||||
|         class="sidebar-collapsed-icon" |  | ||||||
|       > |  | ||||||
|         <gl-icon :size="16" :aria-label="attributeTypeTitle" :name="attributeTypeIcon" /> |  | ||||||
|         <span class="collapse-truncated-title"> |  | ||||||
|           {{ attributeTitle }} |  | ||||||
|         </span> |  | ||||||
|       </div> |  | ||||||
|       <div |  | ||||||
|         :data-testid="`select-${issuableAttribute}`" |  | ||||||
|         :class="isClassicSidebar ? 'hide-collapsed' : 'gl-mt-3'" |         :class="isClassicSidebar ? 'hide-collapsed' : 'gl-mt-3'" | ||||||
|       > |       > | ||||||
|         <span v-if="updating" class="gl-font-weight-bold">{{ selectedTitle }}</span> |         <span v-if="updating" class="gl-font-weight-bold">{{ selectedTitle }}</span> | ||||||
|  | @ -337,7 +346,7 @@ export default { | ||||||
|             v-gl-tooltip="tooltipText" |             v-gl-tooltip="tooltipText" | ||||||
|             class="gl-text-gray-900! gl-font-weight-bold" |             class="gl-text-gray-900! gl-font-weight-bold" | ||||||
|             :href="attributeUrl" |             :href="attributeUrl" | ||||||
|             :data-qa-selector="`${issuableAttribute}_link`" |             :data-qa-selector="`${formatIssuableAttribute.snake}_link`" | ||||||
|           > |           > | ||||||
|             {{ attributeTitle }} |             {{ attributeTitle }} | ||||||
|             <span v-if="isAttributeOverdue(currentAttribute)">{{ $options.i18n.expired }}</span> |             <span v-if="isAttributeOverdue(currentAttribute)">{{ $options.i18n.expired }}</span> | ||||||
|  | @ -359,7 +368,7 @@ export default { | ||||||
|       > |       > | ||||||
|         <gl-search-box-by-type ref="search" v-model="searchTerm" /> |         <gl-search-box-by-type ref="search" v-model="searchTerm" /> | ||||||
|         <gl-dropdown-item |         <gl-dropdown-item | ||||||
|           :data-testid="`no-${issuableAttribute}-item`" |           :data-testid="`no-${formatIssuableAttribute.kebab}-item`" | ||||||
|           :is-check-item="true" |           :is-check-item="true" | ||||||
|           :is-checked="isAttributeChecked($options.noAttributeId)" |           :is-checked="isAttributeChecked($options.noAttributeId)" | ||||||
|           @click="updateAttribute($options.noAttributeId)" |           @click="updateAttribute($options.noAttributeId)" | ||||||
|  | @ -389,7 +398,7 @@ export default { | ||||||
|               :key="attrItem.id" |               :key="attrItem.id" | ||||||
|               :is-check-item="true" |               :is-check-item="true" | ||||||
|               :is-checked="isAttributeChecked(attrItem.id)" |               :is-checked="isAttributeChecked(attrItem.id)" | ||||||
|               :data-testid="`${issuableAttribute}-items`" |               :data-testid="`${formatIssuableAttribute.kebab}-items`" | ||||||
|               @click="updateAttribute(attrItem.id)" |               @click="updateAttribute(attrItem.id)" | ||||||
|             > |             > | ||||||
|               {{ attrItem.title }} |               {{ attrItem.title }} | ||||||
|  |  | ||||||
|  | @ -38,7 +38,10 @@ export default { | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <div data-testid="helpPane" class="time-tracking-help-state"> |   <div | ||||||
|  |     data-testid="helpPane" | ||||||
|  |     class="sidebar-help-state gl-bg-white gl-border-gray-100 gl-border-t-solid gl-border-b-solid gl-border-1" | ||||||
|  |   > | ||||||
|     <div class="time-tracking-info"> |     <div class="time-tracking-info"> | ||||||
|       <h4>{{ __('Track time with quick actions') }}</h4> |       <h4>{{ __('Track time with quick actions') }}</h4> | ||||||
|       <p>{{ __('Quick actions can be used in description and comment boxes.') }}</p> |       <p>{{ __('Quick actions can be used in description and comment boxes.') }}</p> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <script> | <script> | ||||||
| import { GlIcon, GlLink, GlModal, GlModalDirective, GlLoadingIcon } from '@gitlab/ui'; | import { GlIcon, GlLink, GlModal, GlButton, GlModalDirective, GlLoadingIcon } from '@gitlab/ui'; | ||||||
| import { IssuableType } from '~/issues/constants'; | import { IssuableType } from '~/issues/constants'; | ||||||
| import { s__, __ } from '~/locale'; | import { s__, __ } from '~/locale'; | ||||||
| import { timeTrackingQueries } from '~/sidebar/constants'; | import { timeTrackingQueries } from '~/sidebar/constants'; | ||||||
|  | @ -21,6 +21,7 @@ export default { | ||||||
|     GlIcon, |     GlIcon, | ||||||
|     GlLink, |     GlLink, | ||||||
|     GlModal, |     GlModal, | ||||||
|  |     GlButton, | ||||||
|     GlLoadingIcon, |     GlLoadingIcon, | ||||||
|     TimeTrackingCollapsedState, |     TimeTrackingCollapsedState, | ||||||
|     TimeTrackingSpentOnlyPane, |     TimeTrackingSpentOnlyPane, | ||||||
|  | @ -187,7 +188,11 @@ export default { | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <div v-cloak class="time-tracker time-tracking-component-wrap" data-testid="time-tracker"> |   <div | ||||||
|  |     v-cloak | ||||||
|  |     class="time-tracker time-tracking-component-wrap sidebar-help-wrap" | ||||||
|  |     data-testid="time-tracker" | ||||||
|  |   > | ||||||
|     <time-tracking-collapsed-state |     <time-tracking-collapsed-state | ||||||
|       v-if="showCollapsed" |       v-if="showCollapsed" | ||||||
|       :show-comparison-state="showComparisonState" |       :show-comparison-state="showComparisonState" | ||||||
|  | @ -198,25 +203,21 @@ export default { | ||||||
|       :time-spent-human-readable="humanTotalTimeSpent" |       :time-spent-human-readable="humanTotalTimeSpent" | ||||||
|       :time-estimate-human-readable="humanTimeEstimate" |       :time-estimate-human-readable="humanTimeEstimate" | ||||||
|     /> |     /> | ||||||
|     <div class="hide-collapsed gl-line-height-20 gl-text-gray-900"> |     <div | ||||||
|  |       class="hide-collapsed gl-line-height-20 gl-text-gray-900 gl-display-flex gl-align-items-center" | ||||||
|  |     > | ||||||
|       {{ __('Time tracking') }} |       {{ __('Time tracking') }} | ||||||
|       <gl-loading-icon v-if="isTimeTrackingInfoLoading" size="sm" inline /> |       <gl-loading-icon v-if="isTimeTrackingInfoLoading" size="sm" inline /> | ||||||
|       <div |       <gl-button | ||||||
|         v-if="!showHelpState" |         :data-testid="showHelpState ? 'closeHelpButton' : 'helpButton'" | ||||||
|         data-testid="helpButton" |         category="tertiary" | ||||||
|         class="help-button float-right" |         size="small" | ||||||
|         @click="toggleHelpState(true)" |         variant="link" | ||||||
|  |         class="gl-ml-auto" | ||||||
|  |         @click="toggleHelpState(!showHelpState)" | ||||||
|       > |       > | ||||||
|         <gl-icon name="question-o" /> |         <gl-icon :name="showHelpState ? 'close' : 'question-o'" class="gl-text-gray-900!" /> | ||||||
|       </div> |       </gl-button> | ||||||
|       <div |  | ||||||
|         v-else |  | ||||||
|         data-testid="closeHelpButton" |  | ||||||
|         class="close-help-button float-right" |  | ||||||
|         @click="toggleHelpState(false)" |  | ||||||
|       > |  | ||||||
|         <gl-icon name="close" /> |  | ||||||
|       </div> |  | ||||||
|     </div> |     </div> | ||||||
|     <div v-if="!isTimeTrackingInfoLoading" class="hide-collapsed"> |     <div v-if="!isTimeTrackingInfoLoading" class="hide-collapsed"> | ||||||
|       <div v-if="showEstimateOnlyState" data-testid="estimateOnlyPane"> |       <div v-if="showEstimateOnlyState" data-testid="estimateOnlyPane"> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,65 @@ | ||||||
|  | import { kebabCase } from 'lodash'; | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import { GlToggle } from '@gitlab/ui'; | ||||||
|  | import { parseBoolean } from '~/lib/utils/common_utils'; | ||||||
|  | 
 | ||||||
|  | export const initToggle = (el) => { | ||||||
|  |   if (!el) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const { | ||||||
|  |     name, | ||||||
|  |     isChecked, | ||||||
|  |     disabled, | ||||||
|  |     isLoading, | ||||||
|  |     label, | ||||||
|  |     help, | ||||||
|  |     labelPosition, | ||||||
|  |     ...dataset | ||||||
|  |   } = el.dataset; | ||||||
|  | 
 | ||||||
|  |   return new Vue({ | ||||||
|  |     el, | ||||||
|  |     props: { | ||||||
|  |       disabled: { | ||||||
|  |         type: Boolean, | ||||||
|  |         required: false, | ||||||
|  |         default: parseBoolean(disabled), | ||||||
|  |       }, | ||||||
|  |       isLoading: { | ||||||
|  |         type: Boolean, | ||||||
|  |         required: false, | ||||||
|  |         default: parseBoolean(isLoading), | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     data() { | ||||||
|  |       return { | ||||||
|  |         value: parseBoolean(isChecked), | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |     render(h) { | ||||||
|  |       return h(GlToggle, { | ||||||
|  |         props: { | ||||||
|  |           name, | ||||||
|  |           value: this.value, | ||||||
|  |           disabled: this.disabled, | ||||||
|  |           isLoading: this.isLoading, | ||||||
|  |           label, | ||||||
|  |           help, | ||||||
|  |           labelPosition, | ||||||
|  |         }, | ||||||
|  |         class: el.className, | ||||||
|  |         attrs: Object.fromEntries( | ||||||
|  |           Object.entries(dataset).map(([key, value]) => [`data-${kebabCase(key)}`, value]), | ||||||
|  |         ), | ||||||
|  |         on: { | ||||||
|  |           change: (newValue) => { | ||||||
|  |             this.value = newValue; | ||||||
|  |             this.$emit('change', newValue); | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  | @ -742,6 +742,26 @@ | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .sidebar-help-wrap { | ||||||
|  |   .sidebar-help-state { | ||||||
|  |     margin: 16px -20px -20px; | ||||||
|  |     padding: 16px 20px; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .help-state-toggle-enter-active { | ||||||
|  |     transition: all 0.8s ease; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .help-state-toggle-leave-active { | ||||||
|  |     transition: all 0.5s ease; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .help-state-toggle-enter, | ||||||
|  |   .help-state-toggle-leave-active { | ||||||
|  |     opacity: 0; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .time-tracker { | .time-tracker { | ||||||
|   .sidebar-collapsed-icon { |   .sidebar-collapsed-icon { | ||||||
|     > .stopwatch-svg { |     > .stopwatch-svg { | ||||||
|  | @ -759,11 +779,6 @@ | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .help-button, |  | ||||||
|   .close-help-button { |  | ||||||
|     cursor: pointer; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .compare-meter { |   .compare-meter { | ||||||
|     &.over_estimate { |     &.over_estimate { | ||||||
|       .time-remaining, |       .time-remaining, | ||||||
|  | @ -776,31 +791,6 @@ | ||||||
|   .compare-display-container { |   .compare-display-container { | ||||||
|     font-size: 13px; |     font-size: 13px; | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   .time-tracking-help-state { |  | ||||||
|     background: $white; |  | ||||||
|     margin: 16px -20px -20px; |  | ||||||
|     padding: 16px 20px; |  | ||||||
|     border-top: 1px solid $border-gray-light; |  | ||||||
|     border-bottom: 1px solid $border-gray-light; |  | ||||||
| 
 |  | ||||||
|     a:hover { |  | ||||||
|       color: $btn-white-active; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .help-state-toggle-enter-active { |  | ||||||
|     transition: all 0.8s ease; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .help-state-toggle-leave-active { |  | ||||||
|     transition: all 0.5s ease; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .help-state-toggle-enter, |  | ||||||
|   .help-state-toggle-leave-active { |  | ||||||
|     opacity: 0; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .issuable-todo-btn { | .issuable-todo-btn { | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module AlertManagement |   module AlertManagement | ||||||
|     module HttpIntegration |     module HttpIntegration | ||||||
|       class Create < HttpIntegrationBase |       class Create < HttpIntegrationBase | ||||||
|         include FindsProject |  | ||||||
| 
 |  | ||||||
|         graphql_name 'HttpIntegrationCreate' |         graphql_name 'HttpIntegrationCreate' | ||||||
| 
 | 
 | ||||||
|  |         include FindsProject | ||||||
|  | 
 | ||||||
|         argument :project_path, GraphQL::Types::ID, |         argument :project_path, GraphQL::Types::ID, | ||||||
|                  required: true, |                  required: true, | ||||||
|                  description: 'Project to create the integration in.' |                  description: 'Project to create the integration in.' | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module AlertManagement |   module AlertManagement | ||||||
|     module PrometheusIntegration |     module PrometheusIntegration | ||||||
|       class Create < PrometheusIntegrationBase |       class Create < PrometheusIntegrationBase | ||||||
|         include FindsProject |  | ||||||
| 
 |  | ||||||
|         graphql_name 'PrometheusIntegrationCreate' |         graphql_name 'PrometheusIntegrationCreate' | ||||||
| 
 | 
 | ||||||
|  |         include FindsProject | ||||||
|  | 
 | ||||||
|         argument :project_path, GraphQL::Types::ID, |         argument :project_path, GraphQL::Types::ID, | ||||||
|                  required: true, |                  required: true, | ||||||
|                  description: 'Project to create the integration in.' |                  description: 'Project to create the integration in.' | ||||||
|  |  | ||||||
|  | @ -3,10 +3,9 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Boards |   module Boards | ||||||
|     class Create < ::Mutations::BaseMutation |     class Create < ::Mutations::BaseMutation | ||||||
|       include Mutations::ResolvesResourceParent |  | ||||||
| 
 |  | ||||||
|       graphql_name 'CreateBoard' |       graphql_name 'CreateBoard' | ||||||
| 
 | 
 | ||||||
|  |       include Mutations::ResolvesResourceParent | ||||||
|       include Mutations::Boards::CommonMutationArguments |       include Mutations::Boards::CommonMutationArguments | ||||||
| 
 | 
 | ||||||
|       field :board, |       field :board, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Branches |   module Branches | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|       include FindsProject |  | ||||||
| 
 |  | ||||||
|       graphql_name 'CreateBranch' |       graphql_name 'CreateBranch' | ||||||
| 
 | 
 | ||||||
|  |       include FindsProject | ||||||
|  | 
 | ||||||
|       argument :project_path, GraphQL::Types::ID, |       argument :project_path, GraphQL::Types::ID, | ||||||
|                required: true, |                required: true, | ||||||
|                description: 'Project full path the branch is associated with.' |                description: 'Project full path the branch is associated with.' | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Ci |   module Ci | ||||||
|     class CiCdSettingsUpdate < BaseMutation |     class CiCdSettingsUpdate < BaseMutation | ||||||
|       include FindsProject |  | ||||||
| 
 |  | ||||||
|       graphql_name 'CiCdSettingsUpdate' |       graphql_name 'CiCdSettingsUpdate' | ||||||
| 
 | 
 | ||||||
|  |       include FindsProject | ||||||
|  | 
 | ||||||
|       authorize :admin_project |       authorize :admin_project | ||||||
| 
 | 
 | ||||||
|       argument :full_path, GraphQL::Types::ID, |       argument :full_path, GraphQL::Types::ID, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module Ci |   module Ci | ||||||
|     module JobTokenScope |     module JobTokenScope | ||||||
|       class AddProject < BaseMutation |       class AddProject < BaseMutation | ||||||
|         include FindsProject |  | ||||||
| 
 |  | ||||||
|         graphql_name 'CiJobTokenScopeAddProject' |         graphql_name 'CiJobTokenScopeAddProject' | ||||||
| 
 | 
 | ||||||
|  |         include FindsProject | ||||||
|  | 
 | ||||||
|         authorize :admin_project |         authorize :admin_project | ||||||
| 
 | 
 | ||||||
|         argument :project_path, GraphQL::Types::ID, |         argument :project_path, GraphQL::Types::ID, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module Ci |   module Ci | ||||||
|     module JobTokenScope |     module JobTokenScope | ||||||
|       class RemoveProject < BaseMutation |       class RemoveProject < BaseMutation | ||||||
|         include FindsProject |  | ||||||
| 
 |  | ||||||
|         graphql_name 'CiJobTokenScopeRemoveProject' |         graphql_name 'CiJobTokenScopeRemoveProject' | ||||||
| 
 | 
 | ||||||
|  |         include FindsProject | ||||||
|  | 
 | ||||||
|         authorize :admin_project |         authorize :admin_project | ||||||
| 
 | 
 | ||||||
|         argument :project_path, GraphQL::Types::ID, |         argument :project_path, GraphQL::Types::ID, | ||||||
|  |  | ||||||
|  | @ -4,12 +4,12 @@ module Mutations | ||||||
|   module Clusters |   module Clusters | ||||||
|     module Agents |     module Agents | ||||||
|       class Create < BaseMutation |       class Create < BaseMutation | ||||||
|  |         graphql_name 'CreateClusterAgent' | ||||||
|  | 
 | ||||||
|         include FindsProject |         include FindsProject | ||||||
| 
 | 
 | ||||||
|         authorize :create_cluster |         authorize :create_cluster | ||||||
| 
 | 
 | ||||||
|         graphql_name 'CreateClusterAgent' |  | ||||||
| 
 |  | ||||||
|         argument :project_path, GraphQL::Types::ID, |         argument :project_path, GraphQL::Types::ID, | ||||||
|                  required: true, |                  required: true, | ||||||
|                  description: 'Full path of the associated project for this cluster agent.' |                  description: 'Full path of the associated project for this cluster agent.' | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Commits |   module Commits | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|  |       graphql_name 'CommitCreate' | ||||||
|  | 
 | ||||||
|       include FindsProject |       include FindsProject | ||||||
| 
 | 
 | ||||||
|       class UrlHelpers |       class UrlHelpers | ||||||
|  | @ -10,8 +12,6 @@ module Mutations | ||||||
|         include Gitlab::Routing |         include Gitlab::Routing | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       graphql_name 'CommitCreate' |  | ||||||
| 
 |  | ||||||
|       argument :project_path, GraphQL::Types::ID, |       argument :project_path, GraphQL::Types::ID, | ||||||
|                required: true, |                required: true, | ||||||
|                description: 'Project full path the branch is associated with.' |                description: 'Project full path the branch is associated with.' | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module ContainerExpirationPolicies |   module ContainerExpirationPolicies | ||||||
|     class Update < Mutations::BaseMutation |     class Update < Mutations::BaseMutation | ||||||
|       include FindsProject |  | ||||||
| 
 |  | ||||||
|       graphql_name 'UpdateContainerExpirationPolicy' |       graphql_name 'UpdateContainerExpirationPolicy' | ||||||
| 
 | 
 | ||||||
|  |       include FindsProject | ||||||
|  | 
 | ||||||
|       authorize :destroy_container_image |       authorize :destroy_container_image | ||||||
| 
 | 
 | ||||||
|       argument :project_path, |       argument :project_path, | ||||||
|  |  | ||||||
|  | @ -3,12 +3,11 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module ContainerRepositories |   module ContainerRepositories | ||||||
|     class DestroyTags < ::Mutations::ContainerRepositories::DestroyBase |     class DestroyTags < ::Mutations::ContainerRepositories::DestroyBase | ||||||
|       LIMIT = 20 |  | ||||||
| 
 |  | ||||||
|       TOO_MANY_TAGS_ERROR_MESSAGE = "Number of tags is greater than #{LIMIT}" |  | ||||||
| 
 |  | ||||||
|       graphql_name 'DestroyContainerRepositoryTags' |       graphql_name 'DestroyContainerRepositoryTags' | ||||||
| 
 | 
 | ||||||
|  |       LIMIT = 20 | ||||||
|  |       TOO_MANY_TAGS_ERROR_MESSAGE = "Number of tags is greater than #{LIMIT}" | ||||||
|  | 
 | ||||||
|       authorize :destroy_container_image |       authorize :destroy_container_image | ||||||
| 
 | 
 | ||||||
|       argument :id, |       argument :id, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module CustomEmoji |   module CustomEmoji | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|       include Mutations::ResolvesGroup |  | ||||||
| 
 |  | ||||||
|       graphql_name 'CreateCustomEmoji' |       graphql_name 'CreateCustomEmoji' | ||||||
| 
 | 
 | ||||||
|  |       include Mutations::ResolvesGroup | ||||||
|  | 
 | ||||||
|       authorize :create_custom_emoji |       authorize :create_custom_emoji | ||||||
| 
 | 
 | ||||||
|       field :custom_emoji, |       field :custom_emoji, | ||||||
|  |  | ||||||
|  | @ -4,11 +4,11 @@ module Mutations | ||||||
|   module CustomerRelations |   module CustomerRelations | ||||||
|     module Contacts |     module Contacts | ||||||
|       class Create < BaseMutation |       class Create < BaseMutation | ||||||
|  |         graphql_name 'CustomerRelationsContactCreate' | ||||||
|  | 
 | ||||||
|         include ResolvesIds |         include ResolvesIds | ||||||
|         include Gitlab::Graphql::Authorize::AuthorizeResource |         include Gitlab::Graphql::Authorize::AuthorizeResource | ||||||
| 
 | 
 | ||||||
|         graphql_name 'CustomerRelationsContactCreate' |  | ||||||
| 
 |  | ||||||
|         field :contact, |         field :contact, | ||||||
|               Types::CustomerRelations::ContactType, |               Types::CustomerRelations::ContactType, | ||||||
|               null: true, |               null: true, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module CustomerRelations |   module CustomerRelations | ||||||
|     module Contacts |     module Contacts | ||||||
|       class Update < Mutations::BaseMutation |       class Update < Mutations::BaseMutation | ||||||
|         include ResolvesIds |  | ||||||
| 
 |  | ||||||
|         graphql_name 'CustomerRelationsContactUpdate' |         graphql_name 'CustomerRelationsContactUpdate' | ||||||
| 
 | 
 | ||||||
|  |         include ResolvesIds | ||||||
|  | 
 | ||||||
|         authorize :admin_crm_contact |         authorize :admin_crm_contact | ||||||
| 
 | 
 | ||||||
|         field :contact, |         field :contact, | ||||||
|  |  | ||||||
|  | @ -4,11 +4,11 @@ module Mutations | ||||||
|   module CustomerRelations |   module CustomerRelations | ||||||
|     module Organizations |     module Organizations | ||||||
|       class Create < BaseMutation |       class Create < BaseMutation | ||||||
|  |         graphql_name 'CustomerRelationsOrganizationCreate' | ||||||
|  | 
 | ||||||
|         include ResolvesIds |         include ResolvesIds | ||||||
|         include Gitlab::Graphql::Authorize::AuthorizeResource |         include Gitlab::Graphql::Authorize::AuthorizeResource | ||||||
| 
 | 
 | ||||||
|         graphql_name 'CustomerRelationsOrganizationCreate' |  | ||||||
| 
 |  | ||||||
|         field :organization, |         field :organization, | ||||||
|               Types::CustomerRelations::OrganizationType, |               Types::CustomerRelations::OrganizationType, | ||||||
|               null: true, |               null: true, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module CustomerRelations |   module CustomerRelations | ||||||
|     module Organizations |     module Organizations | ||||||
|       class Update < Mutations::BaseMutation |       class Update < Mutations::BaseMutation | ||||||
|         include ResolvesIds |  | ||||||
| 
 |  | ||||||
|         graphql_name 'CustomerRelationsOrganizationUpdate' |         graphql_name 'CustomerRelationsOrganizationUpdate' | ||||||
| 
 | 
 | ||||||
|  |         include ResolvesIds | ||||||
|  | 
 | ||||||
|         authorize :admin_crm_organization |         authorize :admin_crm_organization | ||||||
| 
 | 
 | ||||||
|         field :organization, |         field :organization, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module DependencyProxy |   module DependencyProxy | ||||||
|     module GroupSettings |     module GroupSettings | ||||||
|       class Update < Mutations::BaseMutation |       class Update < Mutations::BaseMutation | ||||||
|         include Mutations::ResolvesGroup |  | ||||||
| 
 |  | ||||||
|         graphql_name 'UpdateDependencyProxySettings' |         graphql_name 'UpdateDependencyProxySettings' | ||||||
| 
 | 
 | ||||||
|  |         include Mutations::ResolvesGroup | ||||||
|  | 
 | ||||||
|         authorize :admin_dependency_proxy |         authorize :admin_dependency_proxy | ||||||
| 
 | 
 | ||||||
|         argument :group_path, |         argument :group_path, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module DependencyProxy |   module DependencyProxy | ||||||
|     module ImageTtlGroupPolicy |     module ImageTtlGroupPolicy | ||||||
|       class Update < Mutations::BaseMutation |       class Update < Mutations::BaseMutation | ||||||
|         include Mutations::ResolvesGroup |  | ||||||
| 
 |  | ||||||
|         graphql_name 'UpdateDependencyProxyImageTtlGroupPolicy' |         graphql_name 'UpdateDependencyProxyImageTtlGroupPolicy' | ||||||
| 
 | 
 | ||||||
|  |         include Mutations::ResolvesGroup | ||||||
|  | 
 | ||||||
|         authorize :admin_dependency_proxy |         authorize :admin_dependency_proxy | ||||||
| 
 | 
 | ||||||
|         argument :group_path, |         argument :group_path, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module DesignManagement |   module DesignManagement | ||||||
|     class Delete < Base |     class Delete < Base | ||||||
|       Errors = ::Gitlab::Graphql::Errors |  | ||||||
| 
 |  | ||||||
|       graphql_name "DesignManagementDelete" |       graphql_name "DesignManagementDelete" | ||||||
| 
 | 
 | ||||||
|  |       Errors = ::Gitlab::Graphql::Errors | ||||||
|  | 
 | ||||||
|       argument :filenames, [GraphQL::Types::String], |       argument :filenames, [GraphQL::Types::String], | ||||||
|                required: true, |                required: true, | ||||||
|                description: "Filenames of the designs to delete.", |                description: "Filenames of the designs to delete.", | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Groups |   module Groups | ||||||
|     class Update < Mutations::BaseMutation |     class Update < Mutations::BaseMutation | ||||||
|       include Mutations::ResolvesGroup |  | ||||||
| 
 |  | ||||||
|       graphql_name 'GroupUpdate' |       graphql_name 'GroupUpdate' | ||||||
| 
 | 
 | ||||||
|  |       include Mutations::ResolvesGroup | ||||||
|  | 
 | ||||||
|       authorize :admin_group |       authorize :admin_group | ||||||
| 
 | 
 | ||||||
|       field :group, Types::GroupType, |       field :group, Types::GroupType, | ||||||
|  |  | ||||||
|  | @ -3,12 +3,12 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Issues |   module Issues | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|  |       graphql_name 'CreateIssue' | ||||||
|  | 
 | ||||||
|       include Mutations::SpamProtection |       include Mutations::SpamProtection | ||||||
|       include FindsProject |       include FindsProject | ||||||
|       include CommonMutationArguments |       include CommonMutationArguments | ||||||
| 
 | 
 | ||||||
|       graphql_name 'CreateIssue' |  | ||||||
| 
 |  | ||||||
|       authorize :create_issue |       authorize :create_issue | ||||||
| 
 | 
 | ||||||
|       argument :project_path, GraphQL::Types::ID, |       argument :project_path, GraphQL::Types::ID, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Issues |   module Issues | ||||||
|     class SetConfidential < Base |     class SetConfidential < Base | ||||||
|       include Mutations::SpamProtection |  | ||||||
| 
 |  | ||||||
|       graphql_name 'IssueSetConfidential' |       graphql_name 'IssueSetConfidential' | ||||||
| 
 | 
 | ||||||
|  |       include Mutations::SpamProtection | ||||||
|  | 
 | ||||||
|       argument :confidential, |       argument :confidential, | ||||||
|                GraphQL::Types::Boolean, |                GraphQL::Types::Boolean, | ||||||
|                required: true, |                required: true, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module JiraImport |   module JiraImport | ||||||
|     class ImportUsers < BaseMutation |     class ImportUsers < BaseMutation | ||||||
|       include FindsProject |  | ||||||
| 
 |  | ||||||
|       graphql_name 'JiraImportUsers' |       graphql_name 'JiraImportUsers' | ||||||
| 
 | 
 | ||||||
|  |       include FindsProject | ||||||
|  | 
 | ||||||
|       authorize :admin_project |       authorize :admin_project | ||||||
| 
 | 
 | ||||||
|       field :jira_users, |       field :jira_users, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module JiraImport |   module JiraImport | ||||||
|     class Start < BaseMutation |     class Start < BaseMutation | ||||||
|       include FindsProject |  | ||||||
| 
 |  | ||||||
|       graphql_name 'JiraImportStart' |       graphql_name 'JiraImportStart' | ||||||
| 
 | 
 | ||||||
|  |       include FindsProject | ||||||
|  | 
 | ||||||
|       authorize :admin_project |       authorize :admin_project | ||||||
| 
 | 
 | ||||||
|       field :jira_import, |       field :jira_import, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Labels |   module Labels | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|       include Mutations::ResolvesResourceParent |  | ||||||
| 
 |  | ||||||
|       graphql_name 'LabelCreate' |       graphql_name 'LabelCreate' | ||||||
| 
 | 
 | ||||||
|  |       include Mutations::ResolvesResourceParent | ||||||
|  | 
 | ||||||
|       field :label, |       field :label, | ||||||
|             Types::LabelType, |             Types::LabelType, | ||||||
|             null: true, |             null: true, | ||||||
|  |  | ||||||
|  | @ -3,12 +3,6 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module MergeRequests |   module MergeRequests | ||||||
|     class Accept < Base |     class Accept < Base | ||||||
|       NOT_MERGEABLE = 'This branch cannot be merged' |  | ||||||
|       HOOKS_VALIDATION_ERROR = 'Pre-merge hooks failed' |  | ||||||
|       SHA_MISMATCH = 'The merge-head is not at the anticipated SHA' |  | ||||||
|       MERGE_FAILED = 'The merge failed' |  | ||||||
|       ALREADY_SCHEDULED = 'The merge request is already scheduled to be merged' |  | ||||||
| 
 |  | ||||||
|       graphql_name 'MergeRequestAccept' |       graphql_name 'MergeRequestAccept' | ||||||
|       authorize :accept_merge_request |       authorize :accept_merge_request | ||||||
|       description <<~DESC |       description <<~DESC | ||||||
|  | @ -17,6 +11,12 @@ module Mutations | ||||||
|         immediately if possible, or using one of the automatic merge strategies. |         immediately if possible, or using one of the automatic merge strategies. | ||||||
|       DESC |       DESC | ||||||
| 
 | 
 | ||||||
|  |       NOT_MERGEABLE = 'This branch cannot be merged' | ||||||
|  |       HOOKS_VALIDATION_ERROR = 'Pre-merge hooks failed' | ||||||
|  |       SHA_MISMATCH = 'The merge-head is not at the anticipated SHA' | ||||||
|  |       MERGE_FAILED = 'The merge failed' | ||||||
|  |       ALREADY_SCHEDULED = 'The merge request is already scheduled to be merged' | ||||||
|  | 
 | ||||||
|       argument :strategy, |       argument :strategy, | ||||||
|                ::Types::MergeStrategyEnum, |                ::Types::MergeStrategyEnum, | ||||||
|                required: false, |                required: false, | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module MergeRequests |   module MergeRequests | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|       include FindsProject |  | ||||||
| 
 |  | ||||||
|       graphql_name 'MergeRequestCreate' |       graphql_name 'MergeRequestCreate' | ||||||
| 
 | 
 | ||||||
|  |       include FindsProject | ||||||
|  | 
 | ||||||
|       argument :project_path, GraphQL::Types::ID, |       argument :project_path, GraphQL::Types::ID, | ||||||
|                required: true, |                required: true, | ||||||
|                description: 'Project full path the merge request is associated with.' |                description: 'Project full path the merge request is associated with.' | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Mutations | ||||||
|   module Namespace |   module Namespace | ||||||
|     module PackageSettings |     module PackageSettings | ||||||
|       class Update < Mutations::BaseMutation |       class Update < Mutations::BaseMutation | ||||||
|         include Mutations::ResolvesNamespace |  | ||||||
| 
 |  | ||||||
|         graphql_name 'UpdateNamespacePackageSettings' |         graphql_name 'UpdateNamespacePackageSettings' | ||||||
| 
 | 
 | ||||||
|  |         include Mutations::ResolvesNamespace | ||||||
|  | 
 | ||||||
|         authorize :create_package_settings |         authorize :create_package_settings | ||||||
| 
 | 
 | ||||||
|         argument :namespace_path, |         argument :namespace_path, | ||||||
|  |  | ||||||
|  | @ -3,14 +3,13 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module ReleaseAssetLinks |   module ReleaseAssetLinks | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|       include FindsProject |  | ||||||
| 
 |  | ||||||
|       graphql_name 'ReleaseAssetLinkCreate' |       graphql_name 'ReleaseAssetLinkCreate' | ||||||
| 
 | 
 | ||||||
|       authorize :create_release |       include FindsProject | ||||||
| 
 |  | ||||||
|       include Types::ReleaseAssetLinkSharedInputArguments |       include Types::ReleaseAssetLinkSharedInputArguments | ||||||
| 
 | 
 | ||||||
|  |       authorize :create_release | ||||||
|  | 
 | ||||||
|       argument :project_path, GraphQL::Types::ID, |       argument :project_path, GraphQL::Types::ID, | ||||||
|                required: true, |                required: true, | ||||||
|                description: 'Full path of the project the asset link is associated with.' |                description: 'Full path of the project the asset link is associated with.' | ||||||
|  |  | ||||||
|  | @ -3,14 +3,14 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Snippets |   module Snippets | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|  |       graphql_name 'CreateSnippet' | ||||||
|  | 
 | ||||||
|       include ServiceCompatibility |       include ServiceCompatibility | ||||||
|       include CanMutateSpammable |       include CanMutateSpammable | ||||||
|       include Mutations::SpamProtection |       include Mutations::SpamProtection | ||||||
| 
 | 
 | ||||||
|       authorize :create_snippet |       authorize :create_snippet | ||||||
| 
 | 
 | ||||||
|       graphql_name 'CreateSnippet' |  | ||||||
| 
 |  | ||||||
|       field :snippet, |       field :snippet, | ||||||
|             Types::SnippetType, |             Types::SnippetType, | ||||||
|             null: true, |             null: true, | ||||||
|  |  | ||||||
|  | @ -3,12 +3,12 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module Snippets |   module Snippets | ||||||
|     class Update < Base |     class Update < Base | ||||||
|  |       graphql_name 'UpdateSnippet' | ||||||
|  | 
 | ||||||
|       include ServiceCompatibility |       include ServiceCompatibility | ||||||
|       include CanMutateSpammable |       include CanMutateSpammable | ||||||
|       include Mutations::SpamProtection |       include Mutations::SpamProtection | ||||||
| 
 | 
 | ||||||
|       graphql_name 'UpdateSnippet' |  | ||||||
| 
 |  | ||||||
|       argument :id, ::Types::GlobalIDType[::Snippet], |       argument :id, ::Types::GlobalIDType[::Snippet], | ||||||
|                required: true, |                required: true, | ||||||
|                description: 'Global ID of the snippet to update.' |                description: 'Global ID of the snippet to update.' | ||||||
|  |  | ||||||
|  | @ -3,11 +3,11 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module WorkItems |   module WorkItems | ||||||
|     class Create < BaseMutation |     class Create < BaseMutation | ||||||
|  |       graphql_name 'WorkItemCreate' | ||||||
|  | 
 | ||||||
|       include Mutations::SpamProtection |       include Mutations::SpamProtection | ||||||
|       include FindsProject |       include FindsProject | ||||||
| 
 | 
 | ||||||
|       graphql_name 'WorkItemCreate' |  | ||||||
| 
 |  | ||||||
|       authorize :create_work_item |       authorize :create_work_item | ||||||
| 
 | 
 | ||||||
|       argument :description, GraphQL::Types::String, |       argument :description, GraphQL::Types::String, | ||||||
|  |  | ||||||
|  | @ -3,11 +3,10 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module WorkItems |   module WorkItems | ||||||
|     class Delete < BaseMutation |     class Delete < BaseMutation | ||||||
|  |       graphql_name 'WorkItemDelete' | ||||||
|       description "Deletes a work item." \ |       description "Deletes a work item." \ | ||||||
|                   " Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice." |                   " Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice." | ||||||
| 
 | 
 | ||||||
|       graphql_name 'WorkItemDelete' |  | ||||||
| 
 |  | ||||||
|       authorize :delete_work_item |       authorize :delete_work_item | ||||||
| 
 | 
 | ||||||
|       argument :id, ::Types::GlobalIDType[::WorkItem], |       argument :id, ::Types::GlobalIDType[::WorkItem], | ||||||
|  |  | ||||||
|  | @ -3,12 +3,11 @@ | ||||||
| module Mutations | module Mutations | ||||||
|   module WorkItems |   module WorkItems | ||||||
|     class Update < BaseMutation |     class Update < BaseMutation | ||||||
|       include Mutations::SpamProtection |       graphql_name 'WorkItemUpdate' | ||||||
| 
 |  | ||||||
|       description "Updates a work item by Global ID." \ |       description "Updates a work item by Global ID." \ | ||||||
|                   " Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice." |                   " Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice." | ||||||
| 
 | 
 | ||||||
|       graphql_name 'WorkItemUpdate' |       include Mutations::SpamProtection | ||||||
| 
 | 
 | ||||||
|       authorize :update_work_item |       authorize :update_work_item | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,10 +5,11 @@ module Types | ||||||
|     module Analytics |     module Analytics | ||||||
|       module UsageTrends |       module UsageTrends | ||||||
|         class MeasurementType < BaseObject |         class MeasurementType < BaseObject | ||||||
|           include Gitlab::Graphql::Authorize::AuthorizeResource |  | ||||||
|           graphql_name 'UsageTrendsMeasurement' |           graphql_name 'UsageTrendsMeasurement' | ||||||
|           description 'Represents a recorded measurement (object count) for the Admins' |           description 'Represents a recorded measurement (object count) for the Admins' | ||||||
| 
 | 
 | ||||||
|  |           include Gitlab::Graphql::Authorize::AuthorizeResource | ||||||
|  | 
 | ||||||
|           authorize :read_usage_trends_measurement |           authorize :read_usage_trends_measurement | ||||||
| 
 | 
 | ||||||
|           field :recorded_at, Types::TimeType, null: true, |           field :recorded_at, Types::TimeType, null: true, | ||||||
|  |  | ||||||
|  | @ -3,11 +3,11 @@ | ||||||
| module Types | module Types | ||||||
|   module AlertManagement |   module AlertManagement | ||||||
|     class PrometheusIntegrationType < ::Types::BaseObject |     class PrometheusIntegrationType < ::Types::BaseObject | ||||||
|       include ::Gitlab::Routing |  | ||||||
| 
 |  | ||||||
|       graphql_name 'AlertManagementPrometheusIntegration' |       graphql_name 'AlertManagementPrometheusIntegration' | ||||||
|       description 'An endpoint and credentials used to accept Prometheus alerts for a project' |       description 'An endpoint and credentials used to accept Prometheus alerts for a project' | ||||||
| 
 | 
 | ||||||
|  |       include ::Gitlab::Routing | ||||||
|  | 
 | ||||||
|       implements(Types::AlertManagement::IntegrationType) |       implements(Types::AlertManagement::IntegrationType) | ||||||
| 
 | 
 | ||||||
|       authorize :admin_project |       authorize :admin_project | ||||||
|  |  | ||||||
|  | @ -3,11 +3,11 @@ | ||||||
| module Types | module Types | ||||||
|   # rubocop: disable Graphql/AuthorizeTypes |   # rubocop: disable Graphql/AuthorizeTypes | ||||||
|   class BoardListType < BaseObject |   class BoardListType < BaseObject | ||||||
|     include Gitlab::Utils::StrongMemoize |  | ||||||
| 
 |  | ||||||
|     graphql_name 'BoardList' |     graphql_name 'BoardList' | ||||||
|     description 'Represents a list for an issue board' |     description 'Represents a list for an issue board' | ||||||
| 
 | 
 | ||||||
|  |     include Gitlab::Utils::StrongMemoize | ||||||
|  | 
 | ||||||
|     alias_method :list, :object |     alias_method :list, :object | ||||||
| 
 | 
 | ||||||
|     field :id, GraphQL::Types::ID, |     field :id, GraphQL::Types::ID, | ||||||
|  |  | ||||||
|  | @ -3,12 +3,13 @@ | ||||||
| module Types | module Types | ||||||
|   module Ci |   module Ci | ||||||
|     class RunnerType < BaseObject |     class RunnerType < BaseObject | ||||||
|  |       graphql_name 'CiRunner' | ||||||
|  | 
 | ||||||
|       edge_type_class(RunnerWebUrlEdge) |       edge_type_class(RunnerWebUrlEdge) | ||||||
|       connection_type_class(Types::CountableConnectionType) |       connection_type_class(Types::CountableConnectionType) | ||||||
|       graphql_name 'CiRunner' | 
 | ||||||
|       authorize :read_runner |       authorize :read_runner | ||||||
|       present_using ::Ci::RunnerPresenter |       present_using ::Ci::RunnerPresenter | ||||||
| 
 |  | ||||||
|       expose_permissions Types::PermissionTypes::Ci::Runner |       expose_permissions Types::PermissionTypes::Ci::Runner | ||||||
| 
 | 
 | ||||||
|       JOB_COUNT_LIMIT = 1000 |       JOB_COUNT_LIMIT = 1000 | ||||||
|  |  | ||||||
|  | @ -2,14 +2,14 @@ | ||||||
| 
 | 
 | ||||||
| module Types | module Types | ||||||
|   class GroupInvitationType < BaseObject |   class GroupInvitationType < BaseObject | ||||||
|  |     graphql_name 'GroupInvitation' | ||||||
|  |     description 'Represents a Group Invitation' | ||||||
|  | 
 | ||||||
|     expose_permissions Types::PermissionTypes::Group |     expose_permissions Types::PermissionTypes::Group | ||||||
|     authorize :admin_group |     authorize :admin_group | ||||||
| 
 | 
 | ||||||
|     implements InvitationInterface |     implements InvitationInterface | ||||||
| 
 | 
 | ||||||
|     graphql_name 'GroupInvitation' |  | ||||||
|     description 'Represents a Group Invitation' |  | ||||||
| 
 |  | ||||||
|     field :group, Types::GroupType, null: true, |     field :group, Types::GroupType, null: true, | ||||||
|           description: 'Group that a User is invited to.' |           description: 'Group that a User is invited to.' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,14 +2,14 @@ | ||||||
| 
 | 
 | ||||||
| module Types | module Types | ||||||
|   class GroupMemberType < BaseObject |   class GroupMemberType < BaseObject | ||||||
|  |     graphql_name 'GroupMember' | ||||||
|  |     description 'Represents a Group Membership' | ||||||
|  | 
 | ||||||
|     expose_permissions Types::PermissionTypes::Group |     expose_permissions Types::PermissionTypes::Group | ||||||
|     authorize :read_group |     authorize :read_group | ||||||
| 
 | 
 | ||||||
|     implements MemberInterface |     implements MemberInterface | ||||||
| 
 | 
 | ||||||
|     graphql_name 'GroupMember' |  | ||||||
|     description 'Represents a Group Membership' |  | ||||||
| 
 |  | ||||||
|     field :group, Types::GroupType, null: true, |     field :group, Types::GroupType, null: true, | ||||||
|           description: 'Group that a User is a member of.' |           description: 'Group that a User is a member of.' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,11 +3,12 @@ | ||||||
| module Types | module Types | ||||||
|   module MergeRequests |   module MergeRequests | ||||||
|     class AssigneeType < ::Types::UserType |     class AssigneeType < ::Types::UserType | ||||||
|  |       graphql_name 'MergeRequestAssignee' | ||||||
|  |       description 'A user assigned to a merge request.' | ||||||
|  | 
 | ||||||
|       include FindClosest |       include FindClosest | ||||||
|       include ::Types::MergeRequests::InteractsWithMergeRequest |       include ::Types::MergeRequests::InteractsWithMergeRequest | ||||||
| 
 | 
 | ||||||
|       graphql_name 'MergeRequestAssignee' |  | ||||||
|       description 'A user assigned to a merge request.' |  | ||||||
|       authorize :read_user |       authorize :read_user | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -3,11 +3,12 @@ | ||||||
| module Types | module Types | ||||||
|   module MergeRequests |   module MergeRequests | ||||||
|     class ReviewerType < ::Types::UserType |     class ReviewerType < ::Types::UserType | ||||||
|  |       graphql_name 'MergeRequestReviewer' | ||||||
|  |       description 'A user assigned to a merge request as a reviewer.' | ||||||
|  | 
 | ||||||
|       include FindClosest |       include FindClosest | ||||||
|       include ::Types::MergeRequests::InteractsWithMergeRequest |       include ::Types::MergeRequests::InteractsWithMergeRequest | ||||||
| 
 | 
 | ||||||
|       graphql_name 'MergeRequestReviewer' |  | ||||||
|       description 'A user assigned to a merge request as a reviewer.' |  | ||||||
|       authorize :read_user |       authorize :read_user | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -4,8 +4,8 @@ module Types | ||||||
|   module Metrics |   module Metrics | ||||||
|     module Dashboards |     module Dashboards | ||||||
|       class AnnotationType < ::Types::BaseObject |       class AnnotationType < ::Types::BaseObject | ||||||
|         authorize :read_metrics_dashboard_annotation |  | ||||||
|         graphql_name 'MetricsDashboardAnnotation' |         graphql_name 'MetricsDashboardAnnotation' | ||||||
|  |         authorize :read_metrics_dashboard_annotation | ||||||
| 
 | 
 | ||||||
|         field :description, GraphQL::Types::String, null: true, |         field :description, GraphQL::Types::String, null: true, | ||||||
|               description: 'Description of the annotation.' |               description: 'Description of the annotation.' | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ | ||||||
| 
 | 
 | ||||||
| module Types | module Types | ||||||
|   class MutationType < BaseObject |   class MutationType < BaseObject | ||||||
|     include Gitlab::Graphql::MountMutation |  | ||||||
| 
 |  | ||||||
|     graphql_name 'Mutation' |     graphql_name 'Mutation' | ||||||
| 
 | 
 | ||||||
|  |     include Gitlab::Graphql::MountMutation | ||||||
|  | 
 | ||||||
|     mount_mutation Mutations::Admin::SidekiqQueues::DeleteJobs |     mount_mutation Mutations::Admin::SidekiqQueues::DeleteJobs | ||||||
|     mount_mutation Mutations::AlertManagement::CreateAlertIssue |     mount_mutation Mutations::AlertManagement::CreateAlertIssue | ||||||
|     mount_mutation Mutations::AlertManagement::UpdateAlertStatus |     mount_mutation Mutations::AlertManagement::UpdateAlertStatus | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Types | module Types | ||||||
|   module Notes |   module Notes | ||||||
|     class DiscussionType < BaseObject |     class DiscussionType < BaseObject | ||||||
|       DiscussionID = ::Types::GlobalIDType[::Discussion] |  | ||||||
| 
 |  | ||||||
|       graphql_name 'Discussion' |       graphql_name 'Discussion' | ||||||
| 
 | 
 | ||||||
|  |       DiscussionID = ::Types::GlobalIDType[::Discussion] | ||||||
|  | 
 | ||||||
|       authorize :read_note |       authorize :read_note | ||||||
| 
 | 
 | ||||||
|       implements(Types::ResolvableInterface) |       implements(Types::ResolvableInterface) | ||||||
|  |  | ||||||
|  | @ -3,10 +3,11 @@ | ||||||
| module Types | module Types | ||||||
|   module Packages |   module Packages | ||||||
|     class PackageDetailsType < PackageType |     class PackageDetailsType < PackageType | ||||||
|       include ::PackagesHelper |  | ||||||
| 
 |  | ||||||
|       graphql_name 'PackageDetailsType' |       graphql_name 'PackageDetailsType' | ||||||
|       description 'Represents a package details in the Package Registry. Note that this type is in beta and susceptible to changes' |       description 'Represents a package details in the Package Registry. Note that this type is in beta and susceptible to changes' | ||||||
|  | 
 | ||||||
|  |       include ::PackagesHelper | ||||||
|  | 
 | ||||||
|       authorize :read_package |       authorize :read_package | ||||||
| 
 | 
 | ||||||
|       field :versions, ::Types::Packages::PackageType.connection_type, null: true, |       field :versions, ::Types::Packages::PackageType.connection_type, null: true, | ||||||
|  |  | ||||||
|  | @ -3,8 +3,8 @@ | ||||||
| module Types | module Types | ||||||
|   module PermissionTypes |   module PermissionTypes | ||||||
|     class Issue < BasePermissionType |     class Issue < BasePermissionType | ||||||
|       description 'Check permissions for the current user on a issue' |  | ||||||
|       graphql_name 'IssuePermissions' |       graphql_name 'IssuePermissions' | ||||||
|  |       description 'Check permissions for the current user on a issue' | ||||||
| 
 | 
 | ||||||
|       abilities :read_issue, :admin_issue, :update_issue, :reopen_issue, |       abilities :read_issue, :admin_issue, :update_issue, :reopen_issue, | ||||||
|                 :read_design, :create_design, :destroy_design, |                 :read_design, :create_design, :destroy_design, | ||||||
|  |  | ||||||
|  | @ -3,15 +3,16 @@ | ||||||
| module Types | module Types | ||||||
|   module PermissionTypes |   module PermissionTypes | ||||||
|     class MergeRequest < BasePermissionType |     class MergeRequest < BasePermissionType | ||||||
|  |       graphql_name 'MergeRequestPermissions' | ||||||
|  |       description 'Check permissions for the current user on a merge request' | ||||||
|  | 
 | ||||||
|  |       present_using MergeRequestPresenter | ||||||
|  | 
 | ||||||
|       PERMISSION_FIELDS = %i[push_to_source_branch |       PERMISSION_FIELDS = %i[push_to_source_branch | ||||||
|                              remove_source_branch |                              remove_source_branch | ||||||
|                              cherry_pick_on_current_merge_request |                              cherry_pick_on_current_merge_request | ||||||
|                              revert_on_current_merge_request].freeze |                              revert_on_current_merge_request].freeze | ||||||
| 
 | 
 | ||||||
|       present_using MergeRequestPresenter |  | ||||||
|       description 'Check permissions for the current user on a merge request' |  | ||||||
|       graphql_name 'MergeRequestPermissions' |  | ||||||
| 
 |  | ||||||
|       abilities :read_merge_request, :admin_merge_request, |       abilities :read_merge_request, :admin_merge_request, | ||||||
|                 :update_merge_request, :create_note |                 :update_merge_request, :create_note | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Types | module Types | ||||||
|   # rubocop: disable Graphql/AuthorizeTypes |   # rubocop: disable Graphql/AuthorizeTypes | ||||||
|   class QueryComplexityType < ::Types::BaseObject |   class QueryComplexityType < ::Types::BaseObject | ||||||
|     ANALYZER = GraphQL::Analysis::QueryComplexity.new { |_query, complexity| complexity } |  | ||||||
| 
 |  | ||||||
|     graphql_name 'QueryComplexity' |     graphql_name 'QueryComplexity' | ||||||
| 
 | 
 | ||||||
|  |     ANALYZER = GraphQL::Analysis::QueryComplexity.new { |_query, complexity| complexity } | ||||||
|  | 
 | ||||||
|     alias_method :query, :object |     alias_method :query, :object | ||||||
| 
 | 
 | ||||||
|     field :limit, GraphQL::Types::Int, |     field :limit, GraphQL::Types::Int, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Types | ||||||
|     # rubocop: disable Graphql/AuthorizeTypes |     # rubocop: disable Graphql/AuthorizeTypes | ||||||
|     # This is presented through `Repository` that has its own authorization |     # This is presented through `Repository` that has its own authorization | ||||||
|     class BlobType < BaseObject |     class BlobType < BaseObject | ||||||
|       present_using BlobPresenter |  | ||||||
| 
 |  | ||||||
|       graphql_name 'RepositoryBlob' |       graphql_name 'RepositoryBlob' | ||||||
| 
 | 
 | ||||||
|  |       present_using BlobPresenter | ||||||
|  | 
 | ||||||
|       field :id, GraphQL::Types::ID, null: false, |       field :id, GraphQL::Types::ID, null: false, | ||||||
|             description: 'ID of the blob.' |             description: 'ID of the blob.' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ | ||||||
| module Types | module Types | ||||||
|   module Terraform |   module Terraform | ||||||
|     class StateVersionType < BaseObject |     class StateVersionType < BaseObject | ||||||
|       include ::API::Helpers::RelatedResourcesHelpers |  | ||||||
| 
 |  | ||||||
|       graphql_name 'TerraformStateVersion' |       graphql_name 'TerraformStateVersion' | ||||||
| 
 | 
 | ||||||
|  |       include ::API::Helpers::RelatedResourcesHelpers | ||||||
|  | 
 | ||||||
|       authorize :read_terraform_state |       authorize :read_terraform_state | ||||||
| 
 | 
 | ||||||
|       field :id, GraphQL::Types::ID, |       field :id, GraphQL::Types::ID, | ||||||
|  |  | ||||||
|  | @ -4,12 +4,11 @@ module Types | ||||||
|     # rubocop: disable Graphql/AuthorizeTypes |     # rubocop: disable Graphql/AuthorizeTypes | ||||||
|     # This is presented through `Repository` that has its own authorization |     # This is presented through `Repository` that has its own authorization | ||||||
|     class BlobType < BaseObject |     class BlobType < BaseObject | ||||||
|       implements Types::Tree::EntryType |  | ||||||
| 
 |  | ||||||
|       present_using BlobPresenter |  | ||||||
| 
 |  | ||||||
|       graphql_name 'Blob' |       graphql_name 'Blob' | ||||||
| 
 | 
 | ||||||
|  |       implements Types::Tree::EntryType | ||||||
|  |       present_using BlobPresenter | ||||||
|  | 
 | ||||||
|       field :web_url, GraphQL::Types::String, null: true, |       field :web_url, GraphQL::Types::String, null: true, | ||||||
|             description: 'Web URL of the blob.' |             description: 'Web URL of the blob.' | ||||||
|       field :web_path, GraphQL::Types::String, null: true, |       field :web_path, GraphQL::Types::String, null: true, | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ module Types | ||||||
|     # rubocop: disable Graphql/AuthorizeTypes |     # rubocop: disable Graphql/AuthorizeTypes | ||||||
|     # This is presented through `Repository` that has its own authorization |     # This is presented through `Repository` that has its own authorization | ||||||
|     class SubmoduleType < BaseObject |     class SubmoduleType < BaseObject | ||||||
|       implements Types::Tree::EntryType |  | ||||||
| 
 |  | ||||||
|       graphql_name 'Submodule' |       graphql_name 'Submodule' | ||||||
| 
 | 
 | ||||||
|  |       implements Types::Tree::EntryType | ||||||
|  | 
 | ||||||
|       field :web_url, type: GraphQL::Types::String, null: true, |       field :web_url, type: GraphQL::Types::String, null: true, | ||||||
|             description: 'Web URL for the sub-module.' |             description: 'Web URL for the sub-module.' | ||||||
|       field :tree_url, type: GraphQL::Types::String, null: true, |       field :tree_url, type: GraphQL::Types::String, null: true, | ||||||
|  |  | ||||||
|  | @ -4,13 +4,12 @@ module Types | ||||||
|     # rubocop: disable Graphql/AuthorizeTypes |     # rubocop: disable Graphql/AuthorizeTypes | ||||||
|     # This is presented through `Repository` that has its own authorization |     # This is presented through `Repository` that has its own authorization | ||||||
|     class TreeEntryType < BaseObject |     class TreeEntryType < BaseObject | ||||||
|       implements Types::Tree::EntryType |  | ||||||
| 
 |  | ||||||
|       present_using TreeEntryPresenter |  | ||||||
| 
 |  | ||||||
|       graphql_name 'TreeEntry' |       graphql_name 'TreeEntry' | ||||||
|       description 'Represents a directory' |       description 'Represents a directory' | ||||||
| 
 | 
 | ||||||
|  |       implements Types::Tree::EntryType | ||||||
|  |       present_using TreeEntryPresenter | ||||||
|  | 
 | ||||||
|       field :web_url, GraphQL::Types::String, null: true, |       field :web_url, GraphQL::Types::String, null: true, | ||||||
|             description: 'Web URL for the tree entry (directory).' |             description: 'Web URL for the tree entry (directory).' | ||||||
|       field :web_path, GraphQL::Types::String, null: true, |       field :web_path, GraphQL::Types::String, null: true, | ||||||
|  |  | ||||||
|  | @ -12,6 +12,8 @@ module Ci | ||||||
|       initial_branch = params[:branch_name] |       initial_branch = params[:branch_name] | ||||||
|       latest_commit = project.repository.commit(initial_branch) || project.commit |       latest_commit = project.repository.commit(initial_branch) || project.commit | ||||||
|       commit_sha = latest_commit ? latest_commit.sha : '' |       commit_sha = latest_commit ? latest_commit.sha : '' | ||||||
|  |       total_branches = project.repository_exists? ? project.repository.branch_count : 0 | ||||||
|  | 
 | ||||||
|       { |       { | ||||||
|         "ci-config-path": project.ci_config_path_or_default, |         "ci-config-path": project.ci_config_path_or_default, | ||||||
|         "ci-examples-help-page-path" => help_page_path('ci/examples/index'), |         "ci-examples-help-page-path" => help_page_path('ci/examples/index'), | ||||||
|  | @ -29,7 +31,7 @@ module Ci | ||||||
|         "project-full-path" => project.full_path, |         "project-full-path" => project.full_path, | ||||||
|         "project-namespace" => project.namespace.full_path, |         "project-namespace" => project.namespace.full_path, | ||||||
|         "runner-help-page-path" => help_page_path('ci/runners/index'), |         "runner-help-page-path" => help_page_path('ci/runners/index'), | ||||||
|         "total-branches" => project.repository.branches.length, |         "total-branches" => total_branches, | ||||||
|         "yml-help-page-path" => help_page_path('ci/yaml/index') |         "yml-help-page-path" => help_page_path('ci/yaml/index') | ||||||
|       } |       } | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | -# This partial renders a GlToggle root element. | ||||||
|  | -# To actually initialize the component, make sure to call the initToggle helper from ~/toggles. | ||||||
|  | 
 | ||||||
|  | - classes = local_assigns.fetch(:classes) | ||||||
|  | - name = local_assigns.fetch(:name, nil) | ||||||
|  | - is_checked = local_assigns.fetch(:is_checked, false).to_s | ||||||
|  | - disabled = local_assigns.fetch(:disabled, false).to_s | ||||||
|  | - is_loading = local_assigns.fetch(:is_loading, false).to_s | ||||||
|  | - label = local_assigns.fetch(:label, nil) | ||||||
|  | - help = local_assigns.fetch(:help, nil) | ||||||
|  | - label_position = local_assigns.fetch(:label_position, nil) | ||||||
|  | - data = local_assigns.fetch(:data, {}) | ||||||
|  | 
 | ||||||
|  | %span{ class: classes, | ||||||
|  |   data: { name: name, | ||||||
|  |     is_checked: is_checked, | ||||||
|  |     disabled: disabled, | ||||||
|  |     is_loading: is_loading, | ||||||
|  |     label: label, | ||||||
|  |     help: help, | ||||||
|  |     label_position: label_position, | ||||||
|  |     **data } } | ||||||
|  | 
 | ||||||
|  | -# Leverage this block to render a rich help text. To render a plain text help text, | ||||||
|  | -# prefer the `help` parameter. | ||||||
|  | - if yield.present? | ||||||
|  |   .gl-text-secondary.gl-mt-1 | ||||||
|  |     = yield | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class StartBackfillCiQueuingTables < Gitlab::Database::Migration[1.0] | ||||||
|  |   MIGRATION = 'BackfillCiQueuingTables' | ||||||
|  |   BATCH_SIZE = 500 | ||||||
|  |   DELAY_INTERVAL = 2.minutes | ||||||
|  | 
 | ||||||
|  |   disable_ddl_transaction! | ||||||
|  | 
 | ||||||
|  |   def up | ||||||
|  |     return if Gitlab.com? | ||||||
|  | 
 | ||||||
|  |     queue_background_migration_jobs_by_range_at_intervals( | ||||||
|  |       Gitlab::BackgroundMigration::BackfillCiQueuingTables::Ci::Build.pending, | ||||||
|  |       MIGRATION, | ||||||
|  |       DELAY_INTERVAL, | ||||||
|  |       batch_size: BATCH_SIZE, | ||||||
|  |       track_jobs: true) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def down | ||||||
|  |     # no-op | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | dbe6760198b8fa068c30871a439298e56802867044a178baa6b8b009f8da13e6 | ||||||
|  | @ -528,7 +528,7 @@ You can use it either for personal or business websites, such as portfolios, doc | ||||||
| 
 | 
 | ||||||
| #### GitLab Runner | #### GitLab Runner | ||||||
| 
 | 
 | ||||||
| - [Project page](https://gitlab.com/gitlab-org/gitlab-runner/blob/master/README.md) | - [Project page](https://gitlab.com/gitlab-org/gitlab-runner/blob/main/README.md) | ||||||
| - Configuration: | - Configuration: | ||||||
|   - [Omnibus](https://docs.gitlab.com/runner/) |   - [Omnibus](https://docs.gitlab.com/runner/) | ||||||
|   - [Charts](https://docs.gitlab.com/runner/install/kubernetes.html) |   - [Charts](https://docs.gitlab.com/runner/install/kubernetes.html) | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ the GitLab team to run the job. | ||||||
| If you want to know the in-depth details, here's what's really happening: | If you want to know the in-depth details, here's what's really happening: | ||||||
| 
 | 
 | ||||||
| 1. You manually run the `review-docs-deploy` job in a merge request. | 1. You manually run the `review-docs-deploy` job in a merge request. | ||||||
| 1. The job runs the [`scripts/trigger-build`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/trigger-build) | 1. The job runs the [`scripts/trigger-build.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/trigger-build.rb) | ||||||
|    script with the `docs deploy` flag, which triggers the "Triggered from `gitlab-org/gitlab` 'review-docs-deploy' job" |    script with the `docs deploy` flag, which triggers the "Triggered from `gitlab-org/gitlab` 'review-docs-deploy' job" | ||||||
|    pipeline trigger in the `gitlab-org/gitlab-docs` project for the `$DOCS_BRANCH` (defaults to `main`). |    pipeline trigger in the `gitlab-org/gitlab-docs` project for the `$DOCS_BRANCH` (defaults to `main`). | ||||||
| 1. The preview URL is shown both at the job output and in the merge request | 1. The preview URL is shown both at the job output and in the merge request | ||||||
|  |  | ||||||
|  | @ -99,7 +99,7 @@ The pipeline in the `gitlab-docs` project: | ||||||
| 
 | 
 | ||||||
| Once a week on Mondays, a scheduled pipeline runs and rebuilds the Docker images | Once a week on Mondays, a scheduled pipeline runs and rebuilds the Docker images | ||||||
| used in various pipeline jobs, like `docs-lint`. The Docker image configuration files are | used in various pipeline jobs, like `docs-lint`. The Docker image configuration files are | ||||||
| located in the [Dockerfiles directory](https://gitlab.com/gitlab-org/gitlab-docs/-/tree/master/dockerfiles). | located in the [Dockerfiles directory](https://gitlab.com/gitlab-org/gitlab-docs/-/tree/main/dockerfiles). | ||||||
| 
 | 
 | ||||||
| If you need to rebuild the Docker images immediately (must have maintainer level permissions): | If you need to rebuild the Docker images immediately (must have maintainer level permissions): | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -199,7 +199,7 @@ You can find Vale configuration in the following projects: | ||||||
| - [`gitlab-runner`](https://gitlab.com/gitlab-org/gitlab-runner/-/tree/main/docs/.vale/gitlab) | - [`gitlab-runner`](https://gitlab.com/gitlab-org/gitlab-runner/-/tree/main/docs/.vale/gitlab) | ||||||
| - [`omnibus-gitlab`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/tree/master/doc/.vale/gitlab) | - [`omnibus-gitlab`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/tree/master/doc/.vale/gitlab) | ||||||
| - [`charts`](https://gitlab.com/gitlab-org/charts/gitlab/-/tree/master/doc/.vale/gitlab) | - [`charts`](https://gitlab.com/gitlab-org/charts/gitlab/-/tree/master/doc/.vale/gitlab) | ||||||
| - [`gitlab-development-kit`](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/master/doc/.vale/gitlab) | - [`gitlab-development-kit`](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/main/doc/.vale/gitlab) | ||||||
| 
 | 
 | ||||||
| This configuration is also used in build pipelines, where | This configuration is also used in build pipelines, where | ||||||
| [error-level rules](#vale-result-types) are enforced. | [error-level rules](#vale-result-types) are enforced. | ||||||
|  |  | ||||||
|  | @ -1389,7 +1389,7 @@ The JSON report artifacts are not a public API of DAST and their format is expec | ||||||
| 
 | 
 | ||||||
| The DAST tool always emits a JSON report file called `gl-dast-report.json` and | The DAST tool always emits a JSON report file called `gl-dast-report.json` and | ||||||
| sample reports can be found in the | sample reports can be found in the | ||||||
| [DAST repository](https://gitlab.com/gitlab-org/security-products/dast/-/tree/master/test/end-to-end/expect). | [DAST repository](https://gitlab.com/gitlab-org/security-products/dast/-/tree/main/test/end-to-end/expect). | ||||||
| 
 | 
 | ||||||
| ## Optimizing DAST | ## Optimizing DAST | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ group: Product Planning | ||||||
| info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments | info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| # Planning hierarchies **(PREMIUM)** | # Planning hierarchies **(FREE)** | ||||||
| 
 | 
 | ||||||
| Planning hierarchies are an integral part of breaking down your work in GitLab. | Planning hierarchies are an integral part of breaking down your work in GitLab. | ||||||
| To understand how you can use epics and issues together in hierarchies, remember the following: | To understand how you can use epics and issues together in hierarchies, remember the following: | ||||||
|  | @ -22,7 +22,7 @@ portfolio management, see | ||||||
| 
 | 
 | ||||||
| ## View planning hierarchies | ## View planning hierarchies | ||||||
| 
 | 
 | ||||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340844/) in GitLab 14.8 and is behind the feature flag `work_items_hierarchy`. | > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340844/) in GitLab 14.8. | ||||||
| 
 | 
 | ||||||
| To view the planning hierarchy in a project: | To view the planning hierarchy in a project: | ||||||
| 
 | 
 | ||||||
|  | @ -34,7 +34,7 @@ The work items outside your subscription plan show up below **Unavailable struct | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| ## Hierarchies with epics | ## Hierarchies with epics **(PREMIUM)** | ||||||
| 
 | 
 | ||||||
| With epics, you can achieve the following hierarchy: | With epics, you can achieve the following hierarchy: | ||||||
| 
 | 
 | ||||||
|  | @ -68,14 +68,14 @@ Epic "1"*-- "0..*" Issue | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| ## View ancestry of an epic |  | ||||||
| 
 |  | ||||||
| In an epic, you can view the ancestors as parents in the right sidebar under **Ancestors**. |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
| 
 |  | ||||||
| ## View ancestry of an issue | ## View ancestry of an issue | ||||||
| 
 | 
 | ||||||
| In an issue, you can view the parented epic above the issue in the right sidebar under **Epic**. | In an issue, you can view the parented epic above the issue in the right sidebar under **Epic**. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ## View ancestry of an epic **(PREMIUM)** | ||||||
|  | 
 | ||||||
|  | In an epic, you can view the ancestors as parents in the right sidebar under **Ancestors**. | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,153 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Gitlab | ||||||
|  |   module BackgroundMigration | ||||||
|  |     # Ensure queuing entries are present even if admins skip upgrades. | ||||||
|  |     class BackfillCiQueuingTables | ||||||
|  |       class Namespace < ActiveRecord::Base # rubocop:disable Style/Documentation | ||||||
|  |         self.table_name = 'namespaces' | ||||||
|  |         self.inheritance_column = :_type_disabled | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       class Project < ActiveRecord::Base # rubocop:disable Style/Documentation | ||||||
|  |         self.table_name = 'projects' | ||||||
|  | 
 | ||||||
|  |         belongs_to :namespace | ||||||
|  |         has_one :ci_cd_settings, class_name: 'Gitlab::BackgroundMigration::BackfillCiQueuingTables::ProjectCiCdSetting' | ||||||
|  | 
 | ||||||
|  |         def group_runners_enabled? | ||||||
|  |           return false unless ci_cd_settings | ||||||
|  | 
 | ||||||
|  |           ci_cd_settings.group_runners_enabled? | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       class ProjectCiCdSetting < ActiveRecord::Base # rubocop:disable Style/Documentation | ||||||
|  |         self.table_name = 'project_ci_cd_settings' | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       class Taggings < ActiveRecord::Base # rubocop:disable Style/Documentation | ||||||
|  |         self.table_name = 'taggings' | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       module Ci | ||||||
|  |         class Build < ActiveRecord::Base # rubocop:disable Style/Documentation | ||||||
|  |           include EachBatch | ||||||
|  | 
 | ||||||
|  |           self.table_name = 'ci_builds' | ||||||
|  |           self.inheritance_column = :_type_disabled | ||||||
|  | 
 | ||||||
|  |           belongs_to :project | ||||||
|  | 
 | ||||||
|  |           scope :pending, -> do | ||||||
|  |             where(status: :pending, type: 'Ci::Build', runner_id: nil) | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def self.each_batch(of: 1000, column: :id, order: { runner_id: :asc, id: :asc }, order_hint: nil) | ||||||
|  |             start = except(:select).select(column).reorder(order) | ||||||
|  |             start = start.take | ||||||
|  |             return unless start | ||||||
|  | 
 | ||||||
|  |             start_id = start[column] | ||||||
|  |             arel_table = self.arel_table | ||||||
|  | 
 | ||||||
|  |             1.step do |index| | ||||||
|  |               start_cond = arel_table[column].gteq(start_id) | ||||||
|  |               stop = except(:select).select(column).where(start_cond).reorder(order) | ||||||
|  |               stop = stop.offset(of).limit(1).take | ||||||
|  |               relation = where(start_cond) | ||||||
|  | 
 | ||||||
|  |               if stop | ||||||
|  |                 stop_id = stop[column] | ||||||
|  |                 start_id = stop_id | ||||||
|  |                 stop_cond = arel_table[column].lt(stop_id) | ||||||
|  |                 relation = relation.where(stop_cond) | ||||||
|  |               end | ||||||
|  | 
 | ||||||
|  |               # Any ORDER BYs are useless for this relation and can lead to less | ||||||
|  |               # efficient UPDATE queries, hence we get rid of it. | ||||||
|  |               relation = relation.except(:order) | ||||||
|  | 
 | ||||||
|  |               # Using unscoped is necessary to prevent leaking the current scope used by | ||||||
|  |               # ActiveRecord to chain `each_batch` method. | ||||||
|  |               unscoped { yield relation, index } | ||||||
|  | 
 | ||||||
|  |               break unless stop | ||||||
|  |             end | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           def tags_ids | ||||||
|  |             BackfillCiQueuingTables::Taggings | ||||||
|  |               .where(taggable_id: id, taggable_type: 'CommitStatus') | ||||||
|  |               .pluck(:tag_id) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         class PendingBuild < ActiveRecord::Base # rubocop:disable Style/Documentation | ||||||
|  |           self.table_name = 'ci_pending_builds' | ||||||
|  | 
 | ||||||
|  |           class << self | ||||||
|  |             def upsert_from_build!(build) | ||||||
|  |               entry = self.new(args_from_build(build)) | ||||||
|  | 
 | ||||||
|  |               self.upsert( | ||||||
|  |                 entry.attributes.compact, | ||||||
|  |                 returning: %w[build_id], | ||||||
|  |                 unique_by: :build_id) | ||||||
|  |             end | ||||||
|  | 
 | ||||||
|  |             def args_from_build(build) | ||||||
|  |               project = build.project | ||||||
|  | 
 | ||||||
|  |               { | ||||||
|  |                 build_id: build.id, | ||||||
|  |                 project_id: build.project_id, | ||||||
|  |                 protected: build.protected?, | ||||||
|  |                 namespace_id: project.namespace_id, | ||||||
|  |                 tag_ids: build.tags_ids, | ||||||
|  |                 instance_runners_enabled: project.shared_runners_enabled?, | ||||||
|  |                 namespace_traversal_ids: namespace_traversal_ids(project) | ||||||
|  |               } | ||||||
|  |             end | ||||||
|  | 
 | ||||||
|  |             def namespace_traversal_ids(project) | ||||||
|  |               if project.group_runners_enabled? | ||||||
|  |                 project.namespace.traversal_ids | ||||||
|  |               else | ||||||
|  |                 [] | ||||||
|  |               end | ||||||
|  |             end | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       BATCH_SIZE = 100 | ||||||
|  | 
 | ||||||
|  |       def perform(start_id, end_id) | ||||||
|  |         scope = BackfillCiQueuingTables::Ci::Build.pending.where(id: start_id..end_id) | ||||||
|  |         pending_builds_query = BackfillCiQueuingTables::Ci::PendingBuild | ||||||
|  |           .where('ci_builds.id = ci_pending_builds.build_id') | ||||||
|  |           .select(1) | ||||||
|  | 
 | ||||||
|  |         scope.each_batch(of: BATCH_SIZE) do |builds| | ||||||
|  |           builds = builds.where('NOT EXISTS (?)', pending_builds_query) | ||||||
|  |           builds = builds.includes(:project, project: [:namespace, :ci_cd_settings]) | ||||||
|  | 
 | ||||||
|  |           builds.each do |build| | ||||||
|  |             BackfillCiQueuingTables::Ci::PendingBuild.upsert_from_build!(build) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         mark_job_as_succeeded(start_id, end_id) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       private | ||||||
|  | 
 | ||||||
|  |       def mark_job_as_succeeded(*arguments) | ||||||
|  |         Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded( | ||||||
|  |           self.class.name.demodulize, | ||||||
|  |            arguments) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -57,8 +57,8 @@ module SystemCheck | ||||||
|           WHERE (p.repository_storage LIKE ?) |           WHERE (p.repository_storage LIKE ?) | ||||||
|         " |         " | ||||||
| 
 | 
 | ||||||
|         query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, storage_name]) # rubocop:disable GitlabSecurity/PublicSend |         query = ::Project.sanitize_sql_array([sql, storage_name]) | ||||||
|         ActiveRecord::Base.connection.select_all(query).rows.try(:flatten!) || [] |         ::Project.connection.select_all(query).rows.try(:flatten!) || [] | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def fetch_disk_namespaces(storage_path) |       def fetch_disk_namespaces(storage_path) | ||||||
|  |  | ||||||
|  | @ -14377,6 +14377,9 @@ msgstr "" | ||||||
| msgid "Escalation policies must have at least one rule" | msgid "Escalation policies must have at least one rule" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Escalation policy" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Escalation policy:" | msgid "Escalation policy:" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -19112,6 +19115,12 @@ msgstr "" | ||||||
| msgid "IncidentManagement|Open" | msgid "IncidentManagement|Open" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "IncidentManagement|Page your team with escalation policies" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | msgid "IncidentManagement|Paged" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "IncidentManagement|Published" | msgid "IncidentManagement|Published" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -19139,6 +19148,9 @@ msgstr "" | ||||||
| msgid "IncidentManagement|Unpublished" | msgid "IncidentManagement|Unpublished" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "IncidentManagement|Use escalation policies to automatically page your team when incidents are created." | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "IncidentSettings|Activate \"time to SLA\" countdown timer" | msgid "IncidentSettings|Activate \"time to SLA\" countdown timer" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -19184,7 +19196,10 @@ msgstr "" | ||||||
| msgid "Incidents" | msgid "Incidents" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Incidents|Add a URL" | msgid "Incidents|Add image details" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | msgid "Incidents|Add text or a link to display with your image. If you don't add either, the file name displays instead." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Incidents|Drop or %{linkStart}upload%{linkEnd} a metric screenshot to attach it to the incident" | msgid "Incidents|Drop or %{linkStart}upload%{linkEnd} a metric screenshot to attach it to the incident" | ||||||
|  | @ -19199,10 +19214,10 @@ msgstr "" | ||||||
| msgid "Incidents|There was an issue loading metric images." | msgid "Incidents|There was an issue loading metric images." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Incidents|There was an issue uploading your image." | msgid "Incidents|There was an issue updating your image." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Incidents|You can optionally add a URL to link users to the original graph." | msgid "Incidents|There was an issue uploading your image." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Incident|Alert details" | msgid "Incident|Alert details" | ||||||
|  | @ -19211,9 +19226,18 @@ msgstr "" | ||||||
| msgid "Incident|Are you sure you wish to delete this image?" | msgid "Incident|Are you sure you wish to delete this image?" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Incident|Delete image" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Incident|Deleting %{filename}" | msgid "Incident|Deleting %{filename}" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Incident|Edit image text or link" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | msgid "Incident|Editing %{filename}" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Incident|Metrics" | msgid "Incident|Metrics" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -21740,6 +21764,9 @@ msgstr "" | ||||||
| msgid "Link" | msgid "Link" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Link (optional)" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Link Prometheus monitoring to GitLab." | msgid "Link Prometheus monitoring to GitLab." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -35999,6 +36026,9 @@ msgstr "" | ||||||
| msgid "Tests" | msgid "Tests" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Text (optional)" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Text added to the body of all email messages. %{character_limit} character limit" | msgid "Text added to the body of all email messages. %{character_limit} character limit" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ module QA | ||||||
|             end |             end | ||||||
| 
 | 
 | ||||||
|             base.view 'app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue' do |             base.view 'app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue' do | ||||||
|               element :milestone_link, 'data-qa-selector="`${issuableAttribute}_link`"' # rubocop:disable QA/ElementWithPattern |               element :milestone_link, 'data-qa-selector="`${formatIssuableAttribute.snake}_link`"' # rubocop:disable QA/ElementWithPattern | ||||||
|             end |             end | ||||||
| 
 | 
 | ||||||
|             base.view 'app/assets/javascripts/sidebar/components/sidebar_editable_item.vue' do |             base.view 'app/assets/javascripts/sidebar/components/sidebar_editable_item.vue' do | ||||||
|  |  | ||||||
|  | @ -21,6 +21,12 @@ module Trigger | ||||||
|     variable_value |     variable_value | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def self.variables_for_env_file(variables) | ||||||
|  |     variables.map do |key, value| | ||||||
|  |       %Q(#{key}=#{value}) | ||||||
|  |     end.join("\n") | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   class Base |   class Base | ||||||
|     # Can be overridden |     # Can be overridden | ||||||
|     def self.access_token |     def self.access_token | ||||||
|  | @ -57,6 +63,21 @@ module Trigger | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     def variables | ||||||
|  |       simple_forwarded_variables.merge(base_variables, extra_variables, version_file_variables) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def simple_forwarded_variables | ||||||
|  |       { | ||||||
|  |         'TRIGGER_SOURCE' => ENV['CI_JOB_URL'], | ||||||
|  |         'TOP_UPSTREAM_SOURCE_PROJECT' => ENV['CI_PROJECT_PATH'], | ||||||
|  |         'TOP_UPSTREAM_SOURCE_REF' => ENV['CI_COMMIT_REF_NAME'], | ||||||
|  |         'TOP_UPSTREAM_SOURCE_JOB' => ENV['CI_JOB_URL'], | ||||||
|  |         'TOP_UPSTREAM_MERGE_REQUEST_PROJECT_ID' => ENV['CI_MERGE_REQUEST_PROJECT_ID'], | ||||||
|  |         'TOP_UPSTREAM_MERGE_REQUEST_IID' => ENV['CI_MERGE_REQUEST_IID'] | ||||||
|  |       } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     private |     private | ||||||
| 
 | 
 | ||||||
|     # Override to trigger and work with pipeline on different GitLab instance |     # Override to trigger and work with pipeline on different GitLab instance | ||||||
|  | @ -95,23 +116,13 @@ module Trigger | ||||||
|       ENV[version_file]&.strip || File.read(version_file).strip |       ENV[version_file]&.strip || File.read(version_file).strip | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def variables |  | ||||||
|       base_variables.merge(extra_variables).merge(version_file_variables) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def base_variables |     def base_variables | ||||||
|       # Use CI_MERGE_REQUEST_SOURCE_BRANCH_SHA for omnibus checkouts due to pipeline for merged results, |       # Use CI_MERGE_REQUEST_SOURCE_BRANCH_SHA for omnibus checkouts due to pipeline for merged results, | ||||||
|       # and fallback to CI_COMMIT_SHA for the `detached` pipelines. |       # and fallback to CI_COMMIT_SHA for the `detached` pipelines. | ||||||
|       { |       { | ||||||
|         'GITLAB_REF_SLUG' => ENV['CI_COMMIT_TAG'] ? ENV['CI_COMMIT_REF_NAME'] : ENV['CI_COMMIT_REF_SLUG'], |         'GITLAB_REF_SLUG' => ENV['CI_COMMIT_TAG'] ? ENV['CI_COMMIT_REF_NAME'] : ENV['CI_COMMIT_REF_SLUG'], | ||||||
|         'TRIGGERED_USER' => ENV['TRIGGERED_USER'] || ENV['GITLAB_USER_NAME'], |         'TRIGGERED_USER' => ENV['TRIGGERED_USER'] || ENV['GITLAB_USER_NAME'], | ||||||
|         'TRIGGER_SOURCE' => ENV['CI_JOB_URL'], |         'TOP_UPSTREAM_SOURCE_SHA' => Trigger.non_empty_variable_value('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || ENV['CI_COMMIT_SHA'] | ||||||
|         'TOP_UPSTREAM_SOURCE_PROJECT' => ENV['CI_PROJECT_PATH'], |  | ||||||
|         'TOP_UPSTREAM_SOURCE_JOB' => ENV['CI_JOB_URL'], |  | ||||||
|         'TOP_UPSTREAM_SOURCE_SHA' => Trigger.non_empty_variable_value('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || ENV['CI_COMMIT_SHA'], |  | ||||||
|         'TOP_UPSTREAM_SOURCE_REF' => ENV['CI_COMMIT_REF_NAME'], |  | ||||||
|         'TOP_UPSTREAM_MERGE_REQUEST_PROJECT_ID' => ENV['CI_MERGE_REQUEST_PROJECT_ID'], |  | ||||||
|         'TOP_UPSTREAM_MERGE_REQUEST_IID' => ENV['CI_MERGE_REQUEST_IID'] |  | ||||||
|       } |       } | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | @ -163,17 +174,16 @@ module Trigger | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   class CNG < Base |   class CNG < Base | ||||||
|     def self.access_token |     def variables | ||||||
|       # Default to "Multi-pipeline (from 'gitlab-org/gitlab' 'cloud-native-image' job)" at https://gitlab.com/gitlab-org/build/CNG/-/settings/access_tokens |       # Delete variables that aren't useful when using native triggers. | ||||||
|       ENV['CNG_PROJECT_ACCESS_TOKEN'] || super |       super.tap do |hash| | ||||||
|  |         hash.delete('TRIGGER_SOURCE') | ||||||
|  |         hash.delete('TRIGGERED_USER') | ||||||
|  |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private |     private | ||||||
| 
 | 
 | ||||||
|     def downstream_project_path |  | ||||||
|       ENV.fetch('CNG_PROJECT_PATH', 'gitlab-org/build/CNG') |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     def ref |     def ref | ||||||
|       return ENV['CI_COMMIT_REF_NAME'] if ENV['CI_COMMIT_REF_NAME'] =~ /^[\d-]+-stable(-ee)?$/ |       return ENV['CI_COMMIT_REF_NAME'] if ENV['CI_COMMIT_REF_NAME'] =~ /^[\d-]+-stable(-ee)?$/ | ||||||
| 
 | 
 | ||||||
|  | @ -181,17 +191,17 @@ module Trigger | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def extra_variables |     def extra_variables | ||||||
|       edition = Trigger.ee? ? 'EE' : 'CE' |  | ||||||
|       # Use CI_MERGE_REQUEST_SOURCE_BRANCH_SHA (MR HEAD commit) so that the image is in sync with the assets and QA images. |       # Use CI_MERGE_REQUEST_SOURCE_BRANCH_SHA (MR HEAD commit) so that the image is in sync with the assets and QA images. | ||||||
|       source_sha = Trigger.non_empty_variable_value('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || ENV['CI_COMMIT_SHA'] |       source_sha = Trigger.non_empty_variable_value('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || ENV['CI_COMMIT_SHA'] | ||||||
| 
 | 
 | ||||||
|       { |       { | ||||||
|         "ee" => Trigger.ee? ? "true" : "false", |         "TRIGGER_BRANCH" => ref, | ||||||
|         "GITLAB_VERSION" => source_sha, |         "GITLAB_VERSION" => source_sha, | ||||||
|         "GITLAB_TAG" => ENV['CI_COMMIT_TAG'], |         "GITLAB_TAG" => ENV['CI_COMMIT_TAG'], # Always set a value, even an empty string, so that the downstream pipeline can correctly check it. | ||||||
|         "GITLAB_ASSETS_TAG" => ENV['CI_COMMIT_TAG'] ? ENV['CI_COMMIT_REF_NAME'] : source_sha, |         "GITLAB_ASSETS_TAG" => ENV['CI_COMMIT_TAG'] ? ENV['CI_COMMIT_REF_NAME'] : source_sha, | ||||||
|         "FORCE_RAILS_IMAGE_BUILDS" => 'true', |         "FORCE_RAILS_IMAGE_BUILDS" => 'true', | ||||||
|         "#{edition}_PIPELINE" => 'true' |         "CE_PIPELINE" => Trigger.ee? ? nil : "true", # Always set a value, even an empty string, so that the downstream pipeline can correctly check it. | ||||||
|  |         "EE_PIPELINE" => Trigger.ee? ? "true" : nil # Always set a value, even an empty string, so that the downstream pipeline can correctly check it. | ||||||
|       } |       } | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  | @ -445,28 +455,30 @@ module Trigger | ||||||
|   Job = Class.new(Pipeline) |   Job = Class.new(Pipeline) | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| case ARGV[0] | if $0 == __FILE__ | ||||||
| when 'omnibus' |   case ARGV[0] | ||||||
|   Trigger::Omnibus.new.invoke!(post_comment: true, downstream_job_name: 'Trigger:qa-test').wait! |   when 'omnibus' | ||||||
| when 'cng' |     Trigger::Omnibus.new.invoke!(post_comment: true, downstream_job_name: 'Trigger:qa-test').wait! | ||||||
|   Trigger::CNG.new.invoke!.wait! |   when 'cng' | ||||||
| when 'gitlab-com-database-testing' |     Trigger::CNG.new.invoke!.wait! | ||||||
|   Trigger::DatabaseTesting.new.invoke! |   when 'gitlab-com-database-testing' | ||||||
| when 'docs' |     Trigger::DatabaseTesting.new.invoke! | ||||||
|   docs_trigger = Trigger::Docs.new |   when 'docs' | ||||||
|  |     docs_trigger = Trigger::Docs.new | ||||||
| 
 | 
 | ||||||
|   case ARGV[1] |     case ARGV[1] | ||||||
|   when 'deploy' |     when 'deploy' | ||||||
|     docs_trigger.deploy! |       docs_trigger.deploy! | ||||||
|   when 'cleanup' |     when 'cleanup' | ||||||
|     docs_trigger.cleanup! |       docs_trigger.cleanup! | ||||||
|  |     else | ||||||
|  |       puts 'usage: trigger-build docs <deploy|cleanup>' | ||||||
|  |       exit 1 | ||||||
|  |     end | ||||||
|   else |   else | ||||||
|     puts 'usage: trigger-build docs <deploy|cleanup>' |     puts "Please provide a valid option: | ||||||
|     exit 1 |     omnibus - Triggers a pipeline that builds the omnibus-gitlab package | ||||||
|  |     cng - Triggers a pipeline that builds images used by the GitLab helm chart | ||||||
|  |     gitlab-com-database-testing - Triggers a pipeline that tests database changes on GitLab.com data" | ||||||
|   end |   end | ||||||
| else |  | ||||||
|   puts "Please provide a valid option: |  | ||||||
|   omnibus - Triggers a pipeline that builds the omnibus-gitlab package |  | ||||||
|   cng - Triggers a pipeline that builds images used by the GitLab helm chart |  | ||||||
|   gitlab-com-database-testing - Triggers a pipeline that tests database changes on GitLab.com data" |  | ||||||
| end | end | ||||||
|  | @ -61,6 +61,15 @@ FactoryBot.define do | ||||||
|     factory :incident do |     factory :incident do | ||||||
|       issue_type { :incident } |       issue_type { :incident } | ||||||
|       association :work_item_type, :default, :incident |       association :work_item_type, :default, :incident | ||||||
|  | 
 | ||||||
|  |       # An escalation status record is created for all incidents | ||||||
|  |       # in app code. This is a trait to avoid creating escalation | ||||||
|  |       # status records in specs which do not need them. | ||||||
|  |       trait :with_escalation_status do | ||||||
|  |         after(:create) do |incident| | ||||||
|  |           create(:incident_management_issuable_escalation_status, issue: incident) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ import { createAlert } from '~/flash'; | ||||||
| 
 | 
 | ||||||
| import { getIdFromGraphQLId } from '~/graphql_shared/utils'; | import { getIdFromGraphQLId } from '~/graphql_shared/utils'; | ||||||
| import RunnerHeader from '~/runner/components/runner_header.vue'; | import RunnerHeader from '~/runner/components/runner_header.vue'; | ||||||
| import RunnerDetails from '~/runner/components/runner_details.vue'; |  | ||||||
| import RunnerPauseButton from '~/runner/components/runner_pause_button.vue'; | import RunnerPauseButton from '~/runner/components/runner_pause_button.vue'; | ||||||
| import RunnerEditButton from '~/runner/components/runner_edit_button.vue'; | import RunnerEditButton from '~/runner/components/runner_edit_button.vue'; | ||||||
| import getRunnerQuery from '~/runner/graphql/get_runner.query.graphql'; | import getRunnerQuery from '~/runner/graphql/get_runner.query.graphql'; | ||||||
|  | @ -30,7 +29,6 @@ describe('AdminRunnerShowApp', () => { | ||||||
|   let mockRunnerQuery; |   let mockRunnerQuery; | ||||||
| 
 | 
 | ||||||
|   const findRunnerHeader = () => wrapper.findComponent(RunnerHeader); |   const findRunnerHeader = () => wrapper.findComponent(RunnerHeader); | ||||||
|   const findRunnerDetails = () => wrapper.findComponent(RunnerDetails); |  | ||||||
|   const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton); |   const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton); | ||||||
|   const findRunnerPauseButton = () => wrapper.findComponent(RunnerPauseButton); |   const findRunnerPauseButton = () => wrapper.findComponent(RunnerPauseButton); | ||||||
| 
 | 
 | ||||||
|  | @ -80,8 +78,7 @@ describe('AdminRunnerShowApp', () => { | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('shows basic runner details', async () => { |     it('shows basic runner details', async () => { | ||||||
|       const expected = `Details
 |       const expected = `Description Instance runner
 | ||||||
|                         Description Instance runner |  | ||||||
|                         Last contact Never contacted |                         Last contact Never contacted | ||||||
|                         Version 1.0.0 |                         Version 1.0.0 | ||||||
|                         IP Address 127.0.0.1 |                         IP Address 127.0.0.1 | ||||||
|  | @ -89,7 +86,7 @@ describe('AdminRunnerShowApp', () => { | ||||||
|                         Maximum job timeout None |                         Maximum job timeout None | ||||||
|                         Tags None`.replace(/\s+/g, ' ');
 |                         Tags None`.replace(/\s+/g, ' ');
 | ||||||
| 
 | 
 | ||||||
|       expect(findRunnerDetails().text()).toMatchInterpolatedText(expected); |       expect(wrapper.text().replace(/\s+/g, ' ')).toContain(expected); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     describe('when runner cannot be updated', () => { |     describe('when runner cannot be updated', () => { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { GlSprintf, GlIntersperse } from '@gitlab/ui'; | import { GlSprintf, GlIntersperse } from '@gitlab/ui'; | ||||||
| import { createWrapper, ErrorWrapper } from '@vue/test-utils'; | import { createWrapper, ErrorWrapper } from '@vue/test-utils'; | ||||||
| import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; | import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; | ||||||
| import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; | import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; | ||||||
| import { useFakeDate } from 'helpers/fake_date'; | import { useFakeDate } from 'helpers/fake_date'; | ||||||
| import { ACCESS_LEVEL_REF_PROTECTED, ACCESS_LEVEL_NOT_PROTECTED } from '~/runner/constants'; | import { ACCESS_LEVEL_REF_PROTECTED, ACCESS_LEVEL_NOT_PROTECTED } from '~/runner/constants'; | ||||||
|  | @ -8,6 +8,8 @@ import { ACCESS_LEVEL_REF_PROTECTED, ACCESS_LEVEL_NOT_PROTECTED } from '~/runner | ||||||
| import RunnerDetails from '~/runner/components/runner_details.vue'; | import RunnerDetails from '~/runner/components/runner_details.vue'; | ||||||
| import RunnerDetail from '~/runner/components/runner_detail.vue'; | import RunnerDetail from '~/runner/components/runner_detail.vue'; | ||||||
| import RunnerGroups from '~/runner/components/runner_groups.vue'; | import RunnerGroups from '~/runner/components/runner_groups.vue'; | ||||||
|  | import RunnerTags from '~/runner/components/runner_tags.vue'; | ||||||
|  | import RunnerTag from '~/runner/components/runner_tag.vue'; | ||||||
| 
 | 
 | ||||||
| import { runnerData, runnerWithGroupData } from '../mock_data'; | import { runnerData, runnerWithGroupData } from '../mock_data'; | ||||||
| 
 | 
 | ||||||
|  | @ -37,16 +39,14 @@ describe('RunnerDetails', () => { | ||||||
| 
 | 
 | ||||||
|   const findDetailGroups = () => wrapper.findComponent(RunnerGroups); |   const findDetailGroups = () => wrapper.findComponent(RunnerGroups); | ||||||
| 
 | 
 | ||||||
|   const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => { |   const createComponent = ({ props = {}, mountFn = shallowMountExtended, stubs } = {}) => { | ||||||
|     wrapper = mountFn(RunnerDetails, { |     wrapper = mountFn(RunnerDetails, { | ||||||
|       propsData: { |       propsData: { | ||||||
|         ...props, |         ...props, | ||||||
|       }, |       }, | ||||||
|       stubs: { |       stubs: { | ||||||
|         GlIntersperse, |  | ||||||
|         GlSprintf, |  | ||||||
|         TimeAgo, |  | ||||||
|         RunnerDetail, |         RunnerDetail, | ||||||
|  |         ...stubs, | ||||||
|       }, |       }, | ||||||
|     }); |     }); | ||||||
|   }; |   }; | ||||||
|  | @ -65,76 +65,85 @@ describe('RunnerDetails', () => { | ||||||
|     expect(wrapper.text()).toBe(''); |     expect(wrapper.text()).toBe(''); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   describe.each` |   describe('Details tab', () => { | ||||||
|     field                    | runner                                                             | expectedValue |     describe.each` | ||||||
|     ${'Description'}         | ${{ description: 'My runner' }}                                    | ${'My runner'} |       field                    | runner                                                             | expectedValue | ||||||
|     ${'Description'}         | ${{ description: null }}                                           | ${'None'} |       ${'Description'}         | ${{ description: 'My runner' }}                                    | ${'My runner'} | ||||||
|     ${'Last contact'}        | ${{ contactedAt: mockOneHourAgo }}                                 | ${'1 hour ago'} |       ${'Description'}         | ${{ description: null }}                                           | ${'None'} | ||||||
|     ${'Last contact'}        | ${{ contactedAt: null }}                                           | ${'Never contacted'} |       ${'Last contact'}        | ${{ contactedAt: mockOneHourAgo }}                                 | ${'1 hour ago'} | ||||||
|     ${'Version'}             | ${{ version: '12.3' }}                                             | ${'12.3'} |       ${'Last contact'}        | ${{ contactedAt: null }}                                           | ${'Never contacted'} | ||||||
|     ${'Version'}             | ${{ version: null }}                                               | ${'None'} |       ${'Version'}             | ${{ version: '12.3' }}                                             | ${'12.3'} | ||||||
|     ${'IP Address'}          | ${{ ipAddress: '127.0.0.1' }}                                      | ${'127.0.0.1'} |       ${'Version'}             | ${{ version: null }}                                               | ${'None'} | ||||||
|     ${'IP Address'}          | ${{ ipAddress: null }}                                             | ${'None'} |       ${'IP Address'}          | ${{ ipAddress: '127.0.0.1' }}                                      | ${'127.0.0.1'} | ||||||
|     ${'Configuration'}       | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: true }}  | ${'Protected, Runs untagged jobs'} |       ${'IP Address'}          | ${{ ipAddress: null }}                                             | ${'None'} | ||||||
|     ${'Configuration'}       | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: false }} | ${'Protected'} |       ${'Configuration'}       | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: true }}  | ${'Protected, Runs untagged jobs'} | ||||||
|     ${'Configuration'}       | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED, runUntagged: true }}  | ${'Runs untagged jobs'} |       ${'Configuration'}       | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: false }} | ${'Protected'} | ||||||
|     ${'Configuration'}       | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED, runUntagged: false }} | ${'None'} |       ${'Configuration'}       | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED, runUntagged: true }}  | ${'Runs untagged jobs'} | ||||||
|     ${'Maximum job timeout'} | ${{ maximumTimeout: null }}                                        | ${'None'} |       ${'Configuration'}       | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED, runUntagged: false }} | ${'None'} | ||||||
|     ${'Maximum job timeout'} | ${{ maximumTimeout: 0 }}                                           | ${'0 seconds'} |       ${'Maximum job timeout'} | ${{ maximumTimeout: null }}                                        | ${'None'} | ||||||
|     ${'Maximum job timeout'} | ${{ maximumTimeout: 59 }}                                          | ${'59 seconds'} |       ${'Maximum job timeout'} | ${{ maximumTimeout: 0 }}                                           | ${'0 seconds'} | ||||||
|     ${'Maximum job timeout'} | ${{ maximumTimeout: 10 * 60 + 5 }}                                 | ${'10 minutes 5 seconds'} |       ${'Maximum job timeout'} | ${{ maximumTimeout: 59 }}                                          | ${'59 seconds'} | ||||||
|   `('"$field" field', ({ field, runner, expectedValue }) => {
 |       ${'Maximum job timeout'} | ${{ maximumTimeout: 10 * 60 + 5 }}                                 | ${'10 minutes 5 seconds'} | ||||||
|     beforeEach(() => { |     `('"$field" field', ({ field, runner, expectedValue }) => {
 | ||||||
|       createComponent({ |       beforeEach(() => { | ||||||
|         props: { |         createComponent({ | ||||||
|           runner: { |           props: { | ||||||
|             ...mockRunner, |             runner: { | ||||||
|             ...runner, |               ...mockRunner, | ||||||
|  |               ...runner, | ||||||
|  |             }, | ||||||
|           }, |           }, | ||||||
|         }, |           stubs: { | ||||||
|  |             GlIntersperse, | ||||||
|  |             GlSprintf, | ||||||
|  |             TimeAgo, | ||||||
|  |           }, | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it(`displays expected value "${expectedValue}"`, () => { | ||||||
|  |         expect(findDd(field).text()).toBe(expectedValue); | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it(`displays expected value "${expectedValue}"`, () => { |     describe('"Tags" field', () => { | ||||||
|       expect(findDd(field).text()).toBe(expectedValue); |       const stubs = { RunnerTags, RunnerTag }; | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 | 
 | ||||||
|   describe('"Tags" field', () => { |       it('displays expected value "tag-1 tag-2"', () => { | ||||||
|     it('displays expected value "tag-1 tag-2"', () => { |         createComponent({ | ||||||
|       createComponent({ |           props: { | ||||||
|         props: { |             runner: { ...mockRunner, tagList: ['tag-1', 'tag-2'] }, | ||||||
|           runner: { ...mockRunner, tagList: ['tag-1', 'tag-2'] }, |           }, | ||||||
|         }, |           stubs, | ||||||
|         mountFn: mountExtended, |         }); | ||||||
|  | 
 | ||||||
|  |         expect(findDd('Tags').text().replace(/\s+/g, ' ')).toBe('tag-1 tag-2'); | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|       expect(findDd('Tags').text().replace(/\s+/g, ' ')).toBe('tag-1 tag-2'); |       it('displays "None" when runner has no tags', () => { | ||||||
|     }); |         createComponent({ | ||||||
|  |           props: { | ||||||
|  |             runner: { ...mockRunner, tagList: [] }, | ||||||
|  |           }, | ||||||
|  |           stubs, | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|     it('displays "None" when runner has no tags', () => { |         expect(findDd('Tags').text().replace(/\s+/g, ' ')).toBe('None'); | ||||||
|       createComponent({ |  | ||||||
|         props: { |  | ||||||
|           runner: { ...mockRunner, tagList: [] }, |  | ||||||
|         }, |  | ||||||
|         mountFn: mountExtended, |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       expect(findDd('Tags').text().replace(/\s+/g, ' ')).toBe('None'); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('Group runners', () => { |  | ||||||
|     beforeEach(() => { |  | ||||||
|       createComponent({ |  | ||||||
|         props: { |  | ||||||
|           runner: mockGroupRunner, |  | ||||||
|         }, |  | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('Shows a group runner details', () => { |     describe('Group runners', () => { | ||||||
|       expect(findDetailGroups().props('runner')).toEqual(mockGroupRunner); |       beforeEach(() => { | ||||||
|  |         createComponent({ | ||||||
|  |           props: { | ||||||
|  |             runner: mockGroupRunner, | ||||||
|  |           }, | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('Shows a group runner details', () => { | ||||||
|  |         expect(findDetailGroups().props('runner')).toEqual(mockGroupRunner); | ||||||
|  |       }); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,65 @@ | ||||||
|  | import { formatJobCount, tableField, getPaginationVariables } from '~/runner/utils'; | ||||||
|  | 
 | ||||||
|  | describe('~/runner/utils', () => { | ||||||
|  |   describe('formatJobCount', () => { | ||||||
|  |     it('formats a number', () => { | ||||||
|  |       expect(formatJobCount(1)).toBe('1'); | ||||||
|  |       expect(formatJobCount(99)).toBe('99'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('formats a large count', () => { | ||||||
|  |       expect(formatJobCount(1000)).toBe('1,000'); | ||||||
|  |       expect(formatJobCount(1001)).toBe('1,000+'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('returns an empty string for non-numeric values', () => { | ||||||
|  |       expect(formatJobCount(undefined)).toBe(''); | ||||||
|  |       expect(formatJobCount(null)).toBe(''); | ||||||
|  |       expect(formatJobCount('number')).toBe(''); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('tableField', () => { | ||||||
|  |     it('a field with options', () => { | ||||||
|  |       expect(tableField({ key: 'name' })).toEqual({ | ||||||
|  |         key: 'name', | ||||||
|  |         label: '', | ||||||
|  |         tdAttr: { 'data-testid': 'td-name' }, | ||||||
|  |         thClass: expect.any(Array), | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('a field with a label', () => { | ||||||
|  |       const label = 'A field name'; | ||||||
|  | 
 | ||||||
|  |       expect(tableField({ key: 'name', label })).toMatchObject({ | ||||||
|  |         label, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('a field with custom classes', () => { | ||||||
|  |       const mockClasses = ['foo', 'bar']; | ||||||
|  | 
 | ||||||
|  |       expect(tableField({ thClasses: mockClasses })).toMatchObject({ | ||||||
|  |         thClass: expect.arrayContaining(mockClasses), | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('getPaginationVariables', () => { | ||||||
|  |     const after = 'AFTER_CURSOR'; | ||||||
|  |     const before = 'BEFORE_CURSOR'; | ||||||
|  | 
 | ||||||
|  |     it.each` | ||||||
|  |       case                         | pagination    | pageSize     | variables | ||||||
|  |       ${'next page'}               | ${{ after }}  | ${undefined} | ${{ after, first: 10 }} | ||||||
|  |       ${'prev page'}               | ${{ before }} | ${undefined} | ${{ before, last: 10 }} | ||||||
|  |       ${'first page'}              | ${{}}         | ${undefined} | ${{ first: 10 }} | ||||||
|  |       ${'next page with N items'}  | ${{ after }}  | ${20}        | ${{ after, first: 20 }} | ||||||
|  |       ${'prev page with N items'}  | ${{ before }} | ${20}        | ${{ before, last: 20 }} | ||||||
|  |       ${'first page with N items'} | ${{}}         | ${20}        | ${{ first: 20 }} | ||||||
|  |     `('navigates to $case', ({ pagination, pageSize, variables }) => {
 | ||||||
|  |       expect(getPaginationVariables(pagination, pageSize)).toEqual(variables); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,149 @@ | ||||||
|  | import { createWrapper } from '@vue/test-utils'; | ||||||
|  | import { GlToggle } from '@gitlab/ui'; | ||||||
|  | import { initToggle } from '~/toggles'; | ||||||
|  | 
 | ||||||
|  | // Selectors
 | ||||||
|  | const TOGGLE_WRAPPER_CLASS = '.gl-toggle-wrapper'; | ||||||
|  | const TOGGLE_LABEL_CLASS = '.gl-toggle-label'; | ||||||
|  | const CHECKED_CLASS = '.is-checked'; | ||||||
|  | const DISABLED_CLASS = '.is-disabled'; | ||||||
|  | const LOADING_CLASS = '.toggle-loading'; | ||||||
|  | const HELP_TEXT_SELECTOR = '[data-testid="toggle-help"]'; | ||||||
|  | 
 | ||||||
|  | // Toggle settings
 | ||||||
|  | const toggleClassName = 'js-custom-toggle-class'; | ||||||
|  | const toggleLabel = 'Toggle label'; | ||||||
|  | 
 | ||||||
|  | describe('toggles/index.js', () => { | ||||||
|  |   let instance; | ||||||
|  |   let toggleWrapper; | ||||||
|  | 
 | ||||||
|  |   const createRootEl = (dataAttrs) => { | ||||||
|  |     const dataset = { | ||||||
|  |       label: toggleLabel, | ||||||
|  |       ...dataAttrs, | ||||||
|  |     }; | ||||||
|  |     const el = document.createElement('span'); | ||||||
|  |     el.classList.add(toggleClassName); | ||||||
|  | 
 | ||||||
|  |     Object.entries(dataset).forEach(([key, value]) => { | ||||||
|  |       el.dataset[key] = value; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     document.body.appendChild(el); | ||||||
|  | 
 | ||||||
|  |     return el; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const initToggleWithOptions = (options = {}) => { | ||||||
|  |     const el = createRootEl(options); | ||||||
|  |     instance = initToggle(el); | ||||||
|  |     toggleWrapper = document.querySelector(TOGGLE_WRAPPER_CLASS); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   afterEach(() => { | ||||||
|  |     document.body.innerHTML = ''; | ||||||
|  |     instance = null; | ||||||
|  |     toggleWrapper = null; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('initToggle', () => { | ||||||
|  |     describe('default state', () => { | ||||||
|  |       beforeEach(() => { | ||||||
|  |         initToggleWithOptions(); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('attaches a GlToggle to the element', async () => { | ||||||
|  |         expect(toggleWrapper).not.toBe(null); | ||||||
|  |         expect(toggleWrapper.querySelector(TOGGLE_LABEL_CLASS).textContent).toBe(toggleLabel); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('passes CSS classes down to GlToggle', () => { | ||||||
|  |         expect(toggleWrapper.className).toContain(toggleClassName); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('is not checked', () => { | ||||||
|  |         expect(toggleWrapper.querySelector(CHECKED_CLASS)).toBe(null); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('is enabled', () => { | ||||||
|  |         expect(toggleWrapper.querySelector(DISABLED_CLASS)).toBe(null); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('is not loading', () => { | ||||||
|  |         expect(toggleWrapper.querySelector(LOADING_CLASS)).toBe(null); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('emits "change" event when value changes', () => { | ||||||
|  |         const wrapper = createWrapper(instance); | ||||||
|  |         const event = 'change'; | ||||||
|  |         const listener = jest.fn(); | ||||||
|  | 
 | ||||||
|  |         instance.$on(event, listener); | ||||||
|  | 
 | ||||||
|  |         expect(listener).toHaveBeenCalledTimes(0); | ||||||
|  | 
 | ||||||
|  |         wrapper.find(GlToggle).vm.$emit(event, true); | ||||||
|  | 
 | ||||||
|  |         expect(listener).toHaveBeenCalledTimes(1); | ||||||
|  |         expect(listener).toHaveBeenLastCalledWith(true); | ||||||
|  | 
 | ||||||
|  |         wrapper.find(GlToggle).vm.$emit(event, false); | ||||||
|  | 
 | ||||||
|  |         expect(listener).toHaveBeenCalledTimes(2); | ||||||
|  |         expect(listener).toHaveBeenLastCalledWith(false); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('with custom options', () => { | ||||||
|  |       const name = 'toggle-name'; | ||||||
|  |       const help = 'Help text'; | ||||||
|  |       const foo = 'bar'; | ||||||
|  | 
 | ||||||
|  |       beforeEach(() => { | ||||||
|  |         initToggleWithOptions({ | ||||||
|  |           name, | ||||||
|  |           isChecked: true, | ||||||
|  |           disabled: true, | ||||||
|  |           isLoading: true, | ||||||
|  |           help, | ||||||
|  |           labelPosition: 'hidden', | ||||||
|  |           foo, | ||||||
|  |         }); | ||||||
|  |         toggleWrapper = document.querySelector(TOGGLE_WRAPPER_CLASS); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('sets the custom name', () => { | ||||||
|  |         const input = toggleWrapper.querySelector('input[type="hidden"]'); | ||||||
|  | 
 | ||||||
|  |         expect(input.name).toBe(name); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('is checked', () => { | ||||||
|  |         expect(toggleWrapper.querySelector(CHECKED_CLASS)).not.toBe(null); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('is disabled', () => { | ||||||
|  |         expect(toggleWrapper.querySelector(DISABLED_CLASS)).not.toBe(null); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('is loading', () => { | ||||||
|  |         expect(toggleWrapper.querySelector(LOADING_CLASS)).not.toBe(null); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('sets the custom help text', () => { | ||||||
|  |         expect(toggleWrapper.querySelector(HELP_TEXT_SELECTOR).textContent).toBe(help); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('hides the label', () => { | ||||||
|  |         expect( | ||||||
|  |           toggleWrapper.querySelector(TOGGLE_LABEL_CLASS).classList.contains('gl-sr-only'), | ||||||
|  |         ).toBe(true); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       it('passes custom dataset to the wrapper', () => { | ||||||
|  |         expect(toggleWrapper.dataset.foo).toBe('bar'); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -88,6 +88,17 @@ RSpec.describe Ci::PipelineEditorHelper do | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     context 'with a project with no repository' do | ||||||
|  |       let(:project) { create(:project) } | ||||||
|  | 
 | ||||||
|  |       it 'returns pipeline editor data' do | ||||||
|  |         expect(pipeline_editor_data).to include({ | ||||||
|  |           "pipeline_etag" => '', | ||||||
|  |           "total-branches" => 0 | ||||||
|  |         }) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     context 'with a non-default branch name' do |     context 'with a non-default branch name' do | ||||||
|       let(:user) { create(:user) } |       let(:user) { create(:user) } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,244 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe Gitlab::BackgroundMigration::BackfillCiQueuingTables, :migration, schema: 20220208115439 do | ||||||
|  |   let(:namespaces)      { table(:namespaces) } | ||||||
|  |   let(:projects)        { table(:projects) } | ||||||
|  |   let(:ci_cd_settings)  { table(:project_ci_cd_settings) } | ||||||
|  |   let(:builds)          { table(:ci_builds) } | ||||||
|  |   let(:queuing_entries) { table(:ci_pending_builds) } | ||||||
|  |   let(:tags)            { table(:tags) } | ||||||
|  |   let(:taggings)        { table(:taggings) } | ||||||
|  | 
 | ||||||
|  |   subject { described_class.new } | ||||||
|  | 
 | ||||||
|  |   describe '#perform' do | ||||||
|  |     let!(:namespace) do | ||||||
|  |       namespaces.create!( | ||||||
|  |         id: 10, | ||||||
|  |         name: 'namespace10', | ||||||
|  |         path: 'namespace10', | ||||||
|  |         traversal_ids: [10]) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let!(:other_namespace) do | ||||||
|  |       namespaces.create!( | ||||||
|  |         id: 11, | ||||||
|  |         name: 'namespace11', | ||||||
|  |         path: 'namespace11', | ||||||
|  |         traversal_ids: [11]) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let!(:project) do | ||||||
|  |       projects.create!(id: 5, namespace_id: 10, name: 'test1', path: 'test1') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let!(:ci_cd_setting) do | ||||||
|  |       ci_cd_settings.create!(id: 5, project_id: 5, group_runners_enabled: true) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let!(:other_project) do | ||||||
|  |       projects.create!(id: 7, namespace_id: 11, name: 'test2', path: 'test2') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let!(:other_ci_cd_setting) do | ||||||
|  |       ci_cd_settings.create!(id: 7, project_id: 7, group_runners_enabled: false) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let!(:another_project) do | ||||||
|  |       projects.create!(id: 9, namespace_id: 10, name: 'test3', path: 'test3', shared_runners_enabled: false) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let!(:ruby_tag) do | ||||||
|  |       tags.create!(id: 22, name: 'ruby') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let!(:postgres_tag) do | ||||||
|  |       tags.create!(id: 23, name: 'postgres') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'creates ci_pending_builds for all pending builds in range' do | ||||||
|  |       builds.create!(id: 50, status: :pending, name: 'test1', project_id: 5, type: 'Ci::Build') | ||||||
|  |       builds.create!(id: 51, status: :created, name: 'test2', project_id: 5, type: 'Ci::Build') | ||||||
|  |       builds.create!(id: 52, status: :pending, name: 'test3', project_id: 5, protected: true, type: 'Ci::Build') | ||||||
|  | 
 | ||||||
|  |       taggings.create!(taggable_id: 52, taggable_type: 'CommitStatus', tag_id: 22) | ||||||
|  |       taggings.create!(taggable_id: 52, taggable_type: 'CommitStatus', tag_id: 23) | ||||||
|  | 
 | ||||||
|  |       builds.create!(id: 60, status: :pending, name: 'test1', project_id: 7, type: 'Ci::Build') | ||||||
|  |       builds.create!(id: 61, status: :running, name: 'test2', project_id: 7, protected: true, type: 'Ci::Build') | ||||||
|  |       builds.create!(id: 62, status: :pending, name: 'test3', project_id: 7, type: 'Ci::Build') | ||||||
|  | 
 | ||||||
|  |       taggings.create!(taggable_id: 60, taggable_type: 'CommitStatus', tag_id: 23) | ||||||
|  |       taggings.create!(taggable_id: 62, taggable_type: 'CommitStatus', tag_id: 22) | ||||||
|  | 
 | ||||||
|  |       builds.create!(id: 70, status: :pending, name: 'test1', project_id: 9, protected: true, type: 'Ci::Build') | ||||||
|  |       builds.create!(id: 71, status: :failed, name: 'test2', project_id: 9, type: 'Ci::Build') | ||||||
|  |       builds.create!(id: 72, status: :pending, name: 'test3', project_id: 9, type: 'Ci::Build') | ||||||
|  | 
 | ||||||
|  |       taggings.create!(taggable_id: 71, taggable_type: 'CommitStatus', tag_id: 22) | ||||||
|  | 
 | ||||||
|  |       subject.perform(1, 100) | ||||||
|  | 
 | ||||||
|  |       expect(queuing_entries.all).to contain_exactly( | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 50, | ||||||
|  |           project_id: 5, | ||||||
|  |           namespace_id: 10, | ||||||
|  |           protected: false, | ||||||
|  |           instance_runners_enabled: true, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [], | ||||||
|  |           namespace_traversal_ids: [10]), | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 52, | ||||||
|  |           project_id: 5, | ||||||
|  |           namespace_id: 10, | ||||||
|  |           protected: true, | ||||||
|  |           instance_runners_enabled: true, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [22, 23], | ||||||
|  |           namespace_traversal_ids: [10]), | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 60, | ||||||
|  |           project_id: 7, | ||||||
|  |           namespace_id: 11, | ||||||
|  |           protected: false, | ||||||
|  |           instance_runners_enabled: true, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [23], | ||||||
|  |           namespace_traversal_ids: []), | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 62, | ||||||
|  |           project_id: 7, | ||||||
|  |           namespace_id: 11, | ||||||
|  |           protected: false, | ||||||
|  |           instance_runners_enabled: true, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [22], | ||||||
|  |           namespace_traversal_ids: []), | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 70, | ||||||
|  |           project_id: 9, | ||||||
|  |           namespace_id: 10, | ||||||
|  |           protected: true, | ||||||
|  |           instance_runners_enabled: false, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [], | ||||||
|  |           namespace_traversal_ids: []), | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 72, | ||||||
|  |           project_id: 9, | ||||||
|  |           namespace_id: 10, | ||||||
|  |           protected: false, | ||||||
|  |           instance_runners_enabled: false, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [], | ||||||
|  |           namespace_traversal_ids: []) | ||||||
|  |       ) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'skips builds that already have ci_pending_builds' do | ||||||
|  |       builds.create!(id: 50, status: :pending, name: 'test1', project_id: 5, type: 'Ci::Build') | ||||||
|  |       builds.create!(id: 51, status: :created, name: 'test2', project_id: 5, type: 'Ci::Build') | ||||||
|  |       builds.create!(id: 52, status: :pending, name: 'test3', project_id: 5, protected: true, type: 'Ci::Build') | ||||||
|  | 
 | ||||||
|  |       taggings.create!(taggable_id: 50, taggable_type: 'CommitStatus', tag_id: 22) | ||||||
|  |       taggings.create!(taggable_id: 52, taggable_type: 'CommitStatus', tag_id: 23) | ||||||
|  | 
 | ||||||
|  |       queuing_entries.create!(build_id: 50, project_id: 5, namespace_id: 10) | ||||||
|  | 
 | ||||||
|  |       subject.perform(1, 100) | ||||||
|  | 
 | ||||||
|  |       expect(queuing_entries.all).to contain_exactly( | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 50, | ||||||
|  |           project_id: 5, | ||||||
|  |           namespace_id: 10, | ||||||
|  |           protected: false, | ||||||
|  |           instance_runners_enabled: false, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [], | ||||||
|  |           namespace_traversal_ids: []), | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 52, | ||||||
|  |           project_id: 5, | ||||||
|  |           namespace_id: 10, | ||||||
|  |           protected: true, | ||||||
|  |           instance_runners_enabled: true, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [23], | ||||||
|  |           namespace_traversal_ids: [10]) | ||||||
|  |       ) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'upserts values in case of conflicts' do | ||||||
|  |       builds.create!(id: 50, status: :pending, name: 'test1', project_id: 5, type: 'Ci::Build') | ||||||
|  |       queuing_entries.create!(build_id: 50, project_id: 5, namespace_id: 10) | ||||||
|  | 
 | ||||||
|  |       build = described_class::Ci::Build.find(50) | ||||||
|  |       described_class::Ci::PendingBuild.upsert_from_build!(build) | ||||||
|  | 
 | ||||||
|  |       expect(queuing_entries.all).to contain_exactly( | ||||||
|  |         an_object_having_attributes( | ||||||
|  |           build_id: 50, | ||||||
|  |           project_id: 5, | ||||||
|  |           namespace_id: 10, | ||||||
|  |           protected: false, | ||||||
|  |           instance_runners_enabled: true, | ||||||
|  |           minutes_exceeded: false, | ||||||
|  |           tag_ids: [], | ||||||
|  |           namespace_traversal_ids: [10]) | ||||||
|  |       ) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'Ci::Build' do | ||||||
|  |     describe '.each_batch' do | ||||||
|  |       let(:model) { described_class::Ci::Build } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         builds.create!(id: 1, status: :pending, name: 'test1', project_id: 5, type: 'Ci::Build') | ||||||
|  |         builds.create!(id: 2, status: :pending, name: 'test2', project_id: 5, type: 'Ci::Build') | ||||||
|  |         builds.create!(id: 3, status: :pending, name: 'test3', project_id: 5, type: 'Ci::Build') | ||||||
|  |         builds.create!(id: 4, status: :pending, name: 'test4', project_id: 5, type: 'Ci::Build') | ||||||
|  |         builds.create!(id: 5, status: :pending, name: 'test5', project_id: 5, type: 'Ci::Build') | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'yields an ActiveRecord::Relation when a block is given' do | ||||||
|  |         model.each_batch do |relation| | ||||||
|  |           expect(relation).to be_a_kind_of(ActiveRecord::Relation) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'yields a batch index as the second argument' do | ||||||
|  |         model.each_batch do |_, index| | ||||||
|  |           expect(index).to eq(1) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'accepts a custom batch size' do | ||||||
|  |         amount = 0 | ||||||
|  | 
 | ||||||
|  |         model.each_batch(of: 1) { amount += 1 } | ||||||
|  | 
 | ||||||
|  |         expect(amount).to eq(5) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'does not include ORDER BYs in the yielded relations' do | ||||||
|  |         model.each_batch do |relation| | ||||||
|  |           expect(relation.to_sql).not_to include('ORDER BY') | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'orders ascending' do | ||||||
|  |         ids = [] | ||||||
|  | 
 | ||||||
|  |         model.each_batch(of: 1) { |rel| ids.concat(rel.ids) } | ||||||
|  | 
 | ||||||
|  |         expect(ids).to eq(ids.sort) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1,48 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | require_migration! | ||||||
|  | 
 | ||||||
|  | RSpec.describe StartBackfillCiQueuingTables do | ||||||
|  |   let(:namespaces) { table(:namespaces) } | ||||||
|  |   let(:projects)   { table(:projects) } | ||||||
|  |   let(:builds)     { table(:ci_builds) } | ||||||
|  | 
 | ||||||
|  |   let!(:namespace) do | ||||||
|  |     namespaces.create!(name: 'namespace1', path: 'namespace1') | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   let!(:project) do | ||||||
|  |     projects.create!(namespace_id: namespace.id, name: 'test1', path: 'test1') | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   let!(:pending_build_1) do | ||||||
|  |     builds.create!(status: :pending, name: 'test1', type: 'Ci::Build', project_id: project.id) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   let!(:running_build) do | ||||||
|  |     builds.create!(status: :running, name: 'test2', type: 'Ci::Build', project_id: project.id) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   let!(:pending_build_2) do | ||||||
|  |     builds.create!(status: :pending, name: 'test3', type: 'Ci::Build', project_id: project.id) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   before do | ||||||
|  |     stub_const("#{described_class.name}::BATCH_SIZE", 1) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'schedules jobs for builds that are pending' do | ||||||
|  |     Sidekiq::Testing.fake! do | ||||||
|  |       freeze_time do | ||||||
|  |         migrate! | ||||||
|  | 
 | ||||||
|  |         expect(described_class::MIGRATION).to be_scheduled_delayed_migration( | ||||||
|  |           2.minutes, pending_build_1.id, pending_build_1.id) | ||||||
|  |         expect(described_class::MIGRATION).to be_scheduled_delayed_migration( | ||||||
|  |           4.minutes, pending_build_2.id, pending_build_2.id) | ||||||
|  |         expect(BackgroundMigrationWorker.jobs.size).to eq(2) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -66,7 +66,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| | ||||||
| 
 | 
 | ||||||
|   it 'shows the help state when icon is clicked' do |   it 'shows the help state when icon is clicked' do | ||||||
|     page.within '.time-tracking-component-wrap' do |     page.within '.time-tracking-component-wrap' do | ||||||
|       find('.help-button').click |       find('[data-testid="helpButton"]').click | ||||||
|       expect(page).to have_content 'Track time with quick actions' |       expect(page).to have_content 'Track time with quick actions' | ||||||
|       expect(page).to have_content 'Learn more' |       expect(page).to have_content 'Learn more' | ||||||
|     end |     end | ||||||
|  | @ -92,8 +92,8 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| | ||||||
| 
 | 
 | ||||||
|   it 'hides the help state when close icon is clicked' do |   it 'hides the help state when close icon is clicked' do | ||||||
|     page.within '.time-tracking-component-wrap' do |     page.within '.time-tracking-component-wrap' do | ||||||
|       find('.help-button').click |       find('[data-testid="helpButton"]').click | ||||||
|       find('.close-help-button').click |       find('[data-testid="closeHelpButton"]').click | ||||||
| 
 | 
 | ||||||
|       expect(page).not_to have_content 'Track time with quick actions' |       expect(page).not_to have_content 'Track time with quick actions' | ||||||
|       expect(page).not_to have_content 'Learn more' |       expect(page).not_to have_content 'Learn more' | ||||||
|  | @ -102,7 +102,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| | ||||||
| 
 | 
 | ||||||
|   it 'displays the correct help url' do |   it 'displays the correct help url' do | ||||||
|     page.within '.time-tracking-component-wrap' do |     page.within '.time-tracking-component-wrap' do | ||||||
|       find('.help-button').click |       find('[data-testid="helpButton"]').click | ||||||
| 
 | 
 | ||||||
|       expect(find_link('Learn more')[:href]).to have_content('/help/user/project/time_tracking.md') |       expect(find_link('Learn more')[:href]).to have_content('/help/user/project/time_tracking.md') | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe 'shared/_gl_toggle.html.haml' do | ||||||
|  |   context 'defaults' do | ||||||
|  |     before do | ||||||
|  |       render partial: 'shared/gl_toggle', locals: { | ||||||
|  |         classes: '.js-gl-toggle' | ||||||
|  |       } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'does not set a name' do | ||||||
|  |       expect(rendered).not_to have_selector('[data-name]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets default is-checked attributes' do | ||||||
|  |       expect(rendered).to have_selector('[data-is-checked="false"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets default disabled attributes' do | ||||||
|  |       expect(rendered).to have_selector('[data-disabled="false"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets default is-loading attributes' do | ||||||
|  |       expect(rendered).to have_selector('[data-is-loading="false"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'does not set a label' do | ||||||
|  |       expect(rendered).not_to have_selector('[data-label]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'does not set a label position' do | ||||||
|  |       expect(rendered).not_to have_selector('[data-label-position]') | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'with custom options' do | ||||||
|  |     before do | ||||||
|  |       render partial: 'shared/gl_toggle', locals: { | ||||||
|  |         classes: 'js-custom-gl-toggle', | ||||||
|  |         name: 'toggle-name', | ||||||
|  |         is_checked: true, | ||||||
|  |         disabled: true, | ||||||
|  |         is_loading: true, | ||||||
|  |         label: 'Custom label', | ||||||
|  |         label_position: 'top', | ||||||
|  |         data: { | ||||||
|  |           foo: 'bar' | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets the custom class' do | ||||||
|  |       expect(rendered).to have_selector('.js-custom-gl-toggle') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets the custom name' do | ||||||
|  |       expect(rendered).to have_selector('[data-name="toggle-name"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets the custom is-checked attributes' do | ||||||
|  |       expect(rendered).to have_selector('[data-is-checked="true"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets the custom disabled attributes' do | ||||||
|  |       expect(rendered).to have_selector('[data-disabled="true"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets the custom is-loading attributes' do | ||||||
|  |       expect(rendered).to have_selector('[data-is-loading="true"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets the custom label' do | ||||||
|  |       expect(rendered).to have_selector('[data-label="Custom label"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets the cutom label position' do | ||||||
|  |       expect(rendered).to have_selector('[data-label-position="top"]') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     it 'sets cutom data attributes' do | ||||||
|  |       expect(rendered).to have_selector('[data-foo="bar"]') | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
		Loading…
	
		Reference in New Issue