diff options
Diffstat (limited to 'app')
29 files changed, 198 insertions, 178 deletions
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 1352211b927..2f8719ee6af 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://gitlab.com/.gitlab-ci.yml", "title": "Gitlab CI configuration", - "description": "Gitlab has a built-in solution for doing CI called Gitlab CI. It is configured by supplying a file called `.gitlab-ci.yml`, which will list all the jobs that are going to run for the project. A full list of all options can be found at https://docs.gitlab.com/ee/ci/yaml/. You can read more about Gitlab CI at https://docs.gitlab.com/ee/ci/README.html.", + "markdownDescription": "Gitlab has a built-in solution for doing CI called Gitlab CI. It is configured by supplying a file called `.gitlab-ci.yml`, which will list all the jobs that are going to run for the project. A full list of all options can be found [here](https://docs.gitlab.com/ee/ci/yaml). [Learn More](https://docs.gitlab.com/ee/ci/index.html).", "type": "object", "properties": { "$schema": { @@ -33,7 +33,7 @@ }, "stages": { "type": "array", - "description": "Groups jobs into stages. All jobs in one stage must complete before next stage is executed. Defaults to ['build', 'test', 'deploy'].", + "markdownDescription": "Groups jobs into stages. All jobs in one stage must complete before next stage is executed. Defaults to ['build', 'test', 'deploy']. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#stages).", "default": ["build", "test", "deploy"], "items": { "type": "string" @@ -42,7 +42,7 @@ "minItems": 1 }, "include": { - "description": "Can be `IncludeItem` or `IncludeItem[]`. Each `IncludeItem` will be a string, or an object with properties for the method if including external YAML file. The external content will be fetched, included and evaluated along the `.gitlab-ci.yml`.", + "markdownDescription": "Can be `IncludeItem` or `IncludeItem[]`. Each `IncludeItem` will be a string, or an object with properties for the method if including external YAML file. The external content will be fetched, included and evaluated along the `.gitlab-ci.yml`. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#include).", "oneOf": [ { "$ref": "#/definitions/include_item" }, { @@ -53,7 +53,7 @@ }, "pages": { "$ref": "#/definitions/job", - "description": "A special job used to upload static sites to Gitlab pages. Requires a `public/` directory with `artifacts.path` pointing to it." + "markdownDescription": "A special job used to upload static sites to Gitlab pages. Requires a `public/` directory with `artifacts.path` pointing to it. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#pages)." }, "workflow": { "type": "object", @@ -93,12 +93,12 @@ "definitions": { "artifacts": { "type": "object", - "description": "Used to specify a list of files and directories that should be attached to the job if it succeeds. Artifacts are sent to Gitlab where they can be downloaded.", + "markdownDescription": "Used to specify a list of files and directories that should be attached to the job if it succeeds. Artifacts are sent to Gitlab where they can be downloaded. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifacts).", "additionalProperties": false, "properties": { "paths": { "type": "array", - "description": "A list of paths to files/folders that should be included in the artifact.", + "markdownDescription": "A list of paths to files/folders that should be included in the artifact. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactspaths).", "items": { "type": "string" }, @@ -106,7 +106,7 @@ }, "exclude": { "type": "array", - "description": "A list of paths to files/folders that should be excluded in the artifact.", + "markdownDescription": "A list of paths to files/folders that should be excluded in the artifact. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsexclude).", "items": { "type": "string" }, @@ -114,19 +114,19 @@ }, "expose_as": { "type": "string", - "description": "Can be used to expose job artifacts in the merge request UI. GitLab will add a link <expose_as> to the relevant merge request that points to the artifact." + "markdownDescription": "Can be used to expose job artifacts in the merge request UI. GitLab will add a link <expose_as> to the relevant merge request that points to the artifact. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsexpose_as)." }, "name": { "type": "string", - "description": "Name for the archive created on job success. Can use variables in the name, e.g. '$CI_JOB_NAME'" + "markdownDescription": "Name for the archive created on job success. Can use variables in the name, e.g. '$CI_JOB_NAME' [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsname)." }, "untracked": { "type": "boolean", - "description": "Whether to add all untracked files (along with 'artifacts.paths') to the artifact.", + "markdownDescription": "Whether to add all untracked files (along with 'artifacts.paths') to the artifact. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsuntracked).", "default": false }, "when": { - "description": "Configure when artifacts are uploaded depended on job status.", + "markdownDescription": "Configure when artifacts are uploaded depended on job status. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactswhen).", "default": "on_success", "oneOf": [ { @@ -145,12 +145,12 @@ }, "expire_in": { "type": "string", - "description": "How long artifacts should be kept. They are saved 30 days by default. Artifacts that have expired are removed periodically via cron job. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'.", + "markdownDescription": "How long artifacts should be kept. They are saved 30 days by default. Artifacts that have expired are removed periodically via cron job. Supports a wide variety of formats, e.g. '1 week', '3 mins 4 sec', '2 hrs 20 min', '2h20min', '6 mos 1 day', '47 yrs 6 mos and 4d', '3 weeks and 2 days'. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsexpire_in).", "default": "30 days" }, "reports": { "type": "object", - "description": "Reports will be uploaded as artifacts, and often displayed in the Gitlab UI, such as in Merge Requests.", + "markdownDescription": "Reports will be uploaded as artifacts, and often displayed in the Gitlab UI, such as in Merge Requests. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsreports).", "additionalProperties": false, "properties": { "junit": { @@ -367,11 +367,11 @@ "required": ["name"] } ], - "description": "Specifies the docker image to use for the job or globally for all jobs. Job configuration takes precedence over global setting. Requires a certain kind of Gitlab runner executor." + "markdownDescription": "Specifies the docker image to use for the job or globally for all jobs. Job configuration takes precedence over global setting. Requires a certain kind of Gitlab runner executor. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#image)." }, "services": { "type": "array", - "description": "Similar to `image` property, but will link the specified services to the `image` container.", + "markdownDescription": "Similar to `image` property, but will link the specified services to the `image` container. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#services).", "items": { "oneOf": [ { @@ -418,7 +418,7 @@ }, "secrets": { "type": "object", - "description": "Defines secrets to be injected as environment variables", + "markdownDescription": "Defines secrets to be injected as environment variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#secrets).", "additionalProperties": { "type": "object", "description": "Environment variable name", @@ -453,7 +453,7 @@ }, "before_script": { "type": "array", - "description": "Defines scripts that should run *before* the job. Can be set globally or per job.", + "markdownDescription": "Defines scripts that should run *before* the job. Can be set globally or per job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#before_script).", "items": { "anyOf": [ { @@ -470,7 +470,7 @@ }, "after_script": { "type": "array", - "description": "Defines scripts that should run *after* the job. Can be set globally or per job.", + "markdownDescription": "Defines scripts that should run *after* the job. Can be set globally or per job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#after_script).", "items": { "anyOf": [ { @@ -487,7 +487,7 @@ }, "rules": { "type": "array", - "description": "Rules allows for an array of individual rule objects to be evaluated in order, until one matches and dynamically provides attributes to the job.", + "markdownDescription": "Rules allows for an array of individual rule objects to be evaluated in order, until one matches and dynamically provides attributes to the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rules).", "items": { "type": "object", "additionalProperties": false, @@ -503,7 +503,7 @@ } }, "globalVariables": { - "description": "Defines environment variables globally. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off. You can use the value and description keywords to define variables that are prefilled when running a pipeline manually.", + "markdownDescription": "Defines environment variables globally. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off. You can use the value and description keywords to define variables that are prefilled when running a pipeline manually. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variables).", "type": "object", "additionalProperties": { "anyOf": [ @@ -523,41 +523,41 @@ }, "if": { "type": "string", - "description": "Expression to evaluate whether additional attributes should be provided to the job" + "markdownDescription": "Expression to evaluate whether additional attributes should be provided to the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rulesif)." }, "changes": { "type": "array", - "description": "Additional attributes will be provided to job if any of the provided paths matches a modified file", + "markdownDescription": "Additional attributes will be provided to job if any of the provided paths matches a modified file. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#ruleschanges).", "items": { "type": "string" } }, "exists": { "type": "array", - "description": "Additional attributes will be provided to job if any of the provided paths matches an existing file in the repository", + "markdownDescription": "Additional attributes will be provided to job if any of the provided paths matches an existing file in the repository. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rulesexists).", "items": { "type": "string" } }, "variables": { "type": "object", - "description": "Defines environment variables for specific jobs. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off.", + "markdownDescription": "Defines environment variables for specific jobs. Job level property overrides global variables. If a job sets `variables: {}`, all global variables are turned off. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#rulesvariables).", "additionalProperties": { "type": ["string", "integer"] } }, "timeout": { "type": "string", - "description": "Allows you to configure a timeout for a specific job (e.g. `1 minute`, `1h 30m 12s`). Read more: https://docs.gitlab.com/ee/ci/yaml/README.html#timeout", + "markdownDescription": "Allows you to configure a timeout for a specific job (e.g. `1 minute`, `1h 30m 12s`). [Learn More](https://docs.gitlab.com/ee/ci/yaml/index.html#timeout).", "minLength": 1 }, "start_in": { "type": "string", - "description": "Used in conjunction with 'when: delayed' to set how long to delay before starting a job. e.g. '5', 5 seconds, 30 minutes, 1 week, etc. Read more: https://docs.gitlab.com/ee/ci/jobs/job_control.html#run-a-job-after-a-delay", + "markdownDescription": "Used in conjunction with 'when: delayed' to set how long to delay before starting a job. e.g. '5', 5 seconds, 30 minutes, 1 week, etc. [Learn More](https://docs.gitlab.com/ee/ci/jobs/job_control.html#run-a-job-after-a-delay).", "minLength": 1 }, "allow_failure": { - "description": "Allow job to fail. A failed job does not cause the pipeline to fail.", + "markdownDescription": "Allow job to fail. A failed job does not cause the pipeline to fail. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#allow_failure).", "oneOf": [ { "description": "Setting this option to true will allow the job to fail while still letting the pipeline pass.", @@ -594,7 +594,7 @@ ] }, "when": { - "description": "Describes the conditions for when to run the job. Defaults to 'on_success'.", + "markdownDescription": "Describes the conditions for when to run the job. Defaults to 'on_success'.", "default": "on_success", "oneOf": [ { @@ -611,11 +611,11 @@ }, { "enum": ["manual"], - "description": "Execute the job manually from Gitlab UI or API. Read more: https://docs.gitlab.com/ee/ci/yaml/#when-manual" + "markdownDescription": "Execute the job manually from Gitlab UI or API. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when)." }, { "enum": ["delayed"], - "description": "Execute a job after the time limit in 'start_in' expires. Read more: https://docs.gitlab.com/ee/ci/yaml/#when-delayed" + "markdownDescription": "Execute a job after the time limit in 'start_in' expires. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#when)." }, { "enum": ["never"], @@ -626,7 +626,7 @@ "cache": { "properties": { "when": { - "description": "Defines when to save the cache, based on the status of the job.", + "markdownDescription": "Defines when to save the cache, based on the status of the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#cachewhen).", "default": "on_success", "oneOf": [ { @@ -778,7 +778,7 @@ }, "variables": { "type": "array", - "description": "Filter job by checking comparing values of environment variables. Read more about variable expressions: https://docs.gitlab.com/ee/ci/variables/README.html#variables-expressions", + "markdownDescription": "Filter job by checking comparing values of CI/CD variables. [Learn More](https://docs.gitlab.com/ee/ci/jobs/job_control.html#cicd-variable-expressions).", "items": { "type": "string" } @@ -795,7 +795,7 @@ ] }, "retry": { - "description": "Retry a job if it fails. Can be a simple integer or object definition.", + "markdownDescription": "Retry a job if it fails. Can be a simple integer or object definition. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#retry).", "oneOf": [ { "$ref": "#/definitions/retry_max" }, { @@ -804,7 +804,7 @@ "properties": { "max": { "$ref": "#/definitions/retry_max" }, "when": { - "description": "Either a single or array of error types to trigger job retry.", + "markdownDescription": "Either a single or array of error types to trigger job retry. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#retrywhen).", "oneOf": [ { "$ref": "#/definitions/retry_errors" }, { @@ -884,7 +884,7 @@ }, "interruptible": { "type": "boolean", - "description": "Interruptible is used to indicate that a job should be canceled if made redundant by a newer pipeline run.", + "markdownDescription": "Interruptible is used to indicate that a job should be canceled if made redundant by a newer pipeline run. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#interruptible).", "default": false }, "job": { @@ -1241,11 +1241,11 @@ "description": "Limit job concurrency. Can be used to ensure that the Runner will not run certain jobs simultaneously." }, "trigger": { - "description": "Trigger allows you to define downstream pipeline trigger. When a job created from trigger definition is started by GitLab, a downstream pipeline gets created. Read more: https://docs.gitlab.com/ee/ci/yaml/README.html#trigger", + "markdownDescription": "Trigger allows you to define downstream pipeline trigger. When a job created from trigger definition is started by GitLab, a downstream pipeline gets created. [Learn More](https://docs.gitlab.com/ee/ci/yaml/index.html#trigger).", "oneOf": [ { "type": "object", - "description": "Trigger a multi-project pipeline. Read more: https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#specify-a-downstream-pipeline-branch", + "markdownDescription": "Trigger a multi-project pipeline. [Learn More](https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#specify-a-downstream-pipeline-branch).", "additionalProperties": false, "properties": { "project": { @@ -1287,7 +1287,7 @@ }, { "type": "object", - "description": "Trigger a child pipeline. Read more: https://docs.gitlab.com/ee/ci/pipelines/parent_child_pipelines.html", + "description": "Trigger a child pipeline. [Learn More](https://docs.gitlab.com/ee/ci/pipelines/parent_child_pipelines.html).", "additionalProperties": false, "properties": { "include": { @@ -1398,7 +1398,7 @@ } }, { - "description": "Path to the project, e.g. `group/project`, or `group/sub-group/project`. Read more: https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#define-multi-project-pipelines-in-your-gitlab-ciyml-file", + "markdownDescription": "Path to the project, e.g. `group/project`, or `group/sub-group/project`. [Learn More](https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#define-multi-project-pipelines-in-your-gitlab-ciyml-file).", "type": "string", "pattern": "\\S/\\S" } @@ -1406,10 +1406,10 @@ }, "inherit": { "type": "object", - "description": "Controls inheritance of globally-defined defaults and variables. Boolean values control inheritance of all default: or variables: keywords. To inherit only a subset of default: or variables: keywords, specify what you wish to inherit. Anything not listed is not inherited.", + "markdownDescription": "Controls inheritance of globally-defined defaults and variables. Boolean values control inheritance of all default: or variables: keywords. To inherit only a subset of default: or variables: keywords, specify what you wish to inherit. Anything not listed is not inherited. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#inherit).", "properties": { "default": { - "description": "Whether to inherit all globally-defined defaults or not. Or subset of inherited defaults", + "markdownDescription": "Whether to inherit all globally-defined defaults or not. Or subset of inherited defaults. [Learn more](https://docs.gitlab.com/ee/ci/yaml/#inheritdefault).", "oneOf": [ { "type": "boolean" @@ -1435,7 +1435,7 @@ ] }, "variables": { - "description": "Whether to inherit all globally-defined variables or not. Or subset of inherited variables", + "markdownDescription": "Whether to inherit all globally-defined variables or not. Or subset of inherited variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#inheritvariables).", "oneOf": [ { "type": "boolean" }, { @@ -1470,7 +1470,7 @@ }, "tags": { "type": "array", - "description": "Used to select runners from the list of available runners. A runner must have all tags listed here to run the job.", + "markdownDescription": "Used to select runners from the list of available runners. A runner must have all tags listed here to run the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#tags).", "items": { "type": "string" } diff --git a/app/assets/javascripts/issuable/issuable_form.js b/app/assets/javascripts/issuable/issuable_form.js index 8e76a33c7dd..38453072af8 100644 --- a/app/assets/javascripts/issuable/issuable_form.js +++ b/app/assets/javascripts/issuable/issuable_form.js @@ -46,6 +46,9 @@ function getFallbackKey() { export default class IssuableForm { constructor(form) { + if (form.length === 0) { + return; + } this.form = form; this.toggleWip = this.toggleWip.bind(this); this.renderWipExplanation = this.renderWipExplanation.bind(this); diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index b81ab103271..9d8b339e813 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -23,6 +23,7 @@ import CsvImportExportButtons from '~/issuable/components/csv_import_export_butt import IssuableByEmail from '~/issuable/components/issuable_by_email.vue'; import { IssuableStatus } from '~/issues/constants'; import axios from '~/lib/utils/axios_utils'; +import { isPositiveInteger } from '~/lib/utils/number_utils'; import { scrollUp } from '~/lib/utils/scroll_utils'; import { getParameterByName, joinPaths } from '~/lib/utils/url_utility'; import { @@ -45,6 +46,8 @@ import { ISSUE_REFERENCE, MAX_LIST_SIZE, PAGE_SIZE, + PARAM_FIRST_PAGE_SIZE, + PARAM_LAST_PAGE_SIZE, PARAM_PAGE_AFTER, PARAM_PAGE_BEFORE, PARAM_SORT, @@ -390,12 +393,14 @@ export default { }, urlParams() { return { - page_after: this.pageParams.afterCursor, - page_before: this.pageParams.beforeCursor, search: this.searchQuery, sort: urlSortParams[this.sortKey], state: this.state, ...this.urlFilterParams, + first_page_size: this.pageParams.firstPageSize, + last_page_size: this.pageParams.lastPageSize, + page_after: this.pageParams.afterCursor, + page_before: this.pageParams.beforeCursor, }; }, hasCrmParameter() { @@ -632,6 +637,8 @@ export default { this.showBulkEditSidebar = showBulkEditSidebar; }, updateData(sortValue) { + const firstPageSize = getParameterByName(PARAM_FIRST_PAGE_SIZE); + const lastPageSize = getParameterByName(PARAM_LAST_PAGE_SIZE); const pageAfter = getParameterByName(PARAM_PAGE_AFTER); const pageBefore = getParameterByName(PARAM_PAGE_BEFORE); const state = getParameterByName(PARAM_STATE); @@ -660,7 +667,13 @@ export default { this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery(); this.filterTokens = isSearchDisabled ? [] : getFilterTokens(window.location.search); - this.pageParams = getInitialPageParams(sortKey, pageAfter, pageBefore); + this.pageParams = getInitialPageParams( + sortKey, + isPositiveInteger(firstPageSize) ? parseInt(firstPageSize, 10) : undefined, + isPositiveInteger(lastPageSize) ? parseInt(lastPageSize, 10) : undefined, + pageAfter, + pageBefore, + ); this.sortKey = sortKey; this.state = state || IssuableStates.Opened; }, diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js index 0795df10a7c..c7098d048ad 100644 --- a/app/assets/javascripts/issues/list/constants.js +++ b/app/assets/javascripts/issues/list/constants.js @@ -57,6 +57,8 @@ export const MAX_LIST_SIZE = 10; export const PAGE_SIZE = 20; export const PAGE_SIZE_MANUAL = 100; export const PARAM_ASSIGNEE_ID = 'assignee_id'; +export const PARAM_FIRST_PAGE_SIZE = 'first_page_size'; +export const PARAM_LAST_PAGE_SIZE = 'last_page_size'; export const PARAM_PAGE_AFTER = 'page_after'; export const PARAM_PAGE_BEFORE = 'page_before'; export const PARAM_SORT = 'sort'; diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js index 3ca93069628..dfdc6e27f0d 100644 --- a/app/assets/javascripts/issues/list/utils.js +++ b/app/assets/javascripts/issues/list/utils.js @@ -46,8 +46,15 @@ import { WEIGHT_DESC, } from './constants'; -export const getInitialPageParams = (sortKey, afterCursor, beforeCursor) => ({ - firstPageSize: sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE, +export const getInitialPageParams = ( + sortKey, + firstPageSize = sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE, + lastPageSize, + afterCursor, + beforeCursor, +) => ({ + firstPageSize: lastPageSize ? undefined : firstPageSize, + lastPageSize, afterCursor, beforeCursor, }); diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue index 15d92ab0ef7..9176210eb22 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue @@ -25,6 +25,7 @@ import { NOT_AVAILABLE_TEXT, NOT_AVAILABLE_SIZE, MORE_ACTIONS_TEXT, + COPY_IMAGE_PATH_TITLE, } from '../../constants/index'; export default { @@ -72,6 +73,7 @@ export default { CONFIGURATION_DETAILS_ROW_TEST, MISSING_MANIFEST_WARNING_TOOLTIP, MORE_ACTIONS_TEXT, + COPY_IMAGE_PATH_TITLE, }, computed: { formattedSize() { @@ -138,7 +140,7 @@ export default { <clipboard-button v-if="tag.location" - :title="tag.location" + :title="$options.i18n.COPY_IMAGE_PATH_TITLE" :text="tag.location" category="tertiary" :disabled="disabled" diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue index d76a8245b63..e67d77210bb 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue @@ -14,6 +14,7 @@ import { IMAGE_FAILED_DELETED_STATUS, IMAGE_MIGRATING_STATE, ROOT_IMAGE_TEXT, + COPY_IMAGE_PATH_TITLE, } from '../../constants/index'; import DeleteButton from '../delete_button.vue'; import CleanupStatus from './cleanup_status.vue'; @@ -52,6 +53,7 @@ export default { i18n: { REMOVE_REPOSITORY_LABEL, ROW_SCHEDULED_FOR_DELETION, + COPY_IMAGE_PATH_TITLE, }, computed: { disabledDelete() { @@ -115,7 +117,7 @@ export default { v-if="item.location" :disabled="deleting" :text="item.location" - :title="item.location" + :title="$options.i18n.COPY_IMAGE_PATH_TITLE" category="tertiary" /> </template> diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js index ceaf8a65a10..c6a7591e0d9 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js @@ -41,6 +41,8 @@ export const EMPTY_RESULT_MESSAGE = s__( 'ContainerRegistry|To widen your search, change or remove the filters above.', ); +export const COPY_IMAGE_PATH_TITLE = s__('ContainerRegistry|Copy image path'); + // Parameters export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED'; diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue index 58df98d0fb7..8e95fad1e48 100644 --- a/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue +++ b/app/assets/javascripts/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue @@ -1,7 +1,6 @@ <script> import { GlButton } from '@gitlab/ui'; import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { EDITOR_APP_STATUS_EMPTY, EDITOR_APP_STATUS_LOADING } from '../../constants'; import FileTreePopover from '../popovers/file_tree_popover.vue'; import BranchSwitcher from './branch_switcher.vue'; @@ -12,7 +11,6 @@ export default { FileTreePopover, GlButton, }, - mixins: [glFeatureFlagMixin()], props: { hasUnsavedChanges: { type: Boolean, @@ -43,11 +41,7 @@ export default { return this.appStatus === EDITOR_APP_STATUS_LOADING; }, showFileTreeToggle() { - return ( - this.glFeatures.pipelineEditorFileTree && - !this.isNewCiConfigFile && - this.appStatus !== EDITOR_APP_STATUS_EMPTY - ); + return !this.isNewCiConfigFile && this.appStatus !== EDITOR_APP_STATUS_EMPTY; }, }, methods: { diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue index 59022a91322..f26cdd8b017 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue @@ -1,7 +1,6 @@ <script> import { GlModal } from '@gitlab/ui'; import { __ } from '~/locale'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import CommitSection from './components/commit/commit_section.vue'; import PipelineEditorDrawer from './components/drawer/pipeline_editor_drawer.vue'; import PipelineEditorFileNav from './components/file_nav/pipeline_editor_file_nav.vue'; @@ -34,7 +33,6 @@ export default { PipelineEditorHeader, PipelineEditorTabs, }, - mixins: [glFeatureFlagMixin()], props: { ciConfigData: { type: Object, @@ -76,9 +74,6 @@ export default { includesFiles() { return this.ciConfigData?.includes || []; }, - isFileTreeVisible() { - return this.showFileTree && this.glFeatures.pipelineEditorFileTree; - }, }, mounted() { this.showFileTree = JSON.parse(localStorage.getItem(FILE_TREE_DISPLAY_KEY)) || false; @@ -140,7 +135,7 @@ export default { /> <div class="gl-display-flex gl-w-full gl-sm-flex-direction-column"> <pipeline-editor-file-tree - v-if="isFileTreeVisible" + v-if="showFileTree" class="gl-flex-shrink-0" :includes="includesFiles" /> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue index 0e31f97b9db..b1c4f7c5a7c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue @@ -98,10 +98,10 @@ export default { <template> <div data-qa-selector="approvals_summary_content"> - <strong>{{ approvalLeftMessage }}</strong> + <span class="gl-font-weight-bold">{{ approvalLeftMessage }}</span> <template v-if="hasApprovers"> <span v-if="approvalLeftMessage">{{ message }}</span> - <strong v-else>{{ message }}</strong> + <span v-else class="gl-font-weight-bold">{{ message }}</span> <user-avatar-list class="gl-display-inline-block gl-vertical-align-middle" :img-size="24" diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index f76a0cbbae8..7c03a2bb049 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -234,6 +234,8 @@ .navbar-sub-nav { display: flex; + align-items: center; + height: 100%; margin: 0 0 0 6px; .dropdown-chevron { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index bc649b6407d..194ccb6b73e 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -439,7 +439,7 @@ $browser-scrollbar-size: 10px; /* * Misc */ -$header-height: var(--header-height, 40px); +$header-height: var(--header-height, 48px); $header-zindex: 1000; $zindex-dropdown-menu: 300; $suggestion-header-height: 46px; diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index 001431e517b..b2a5906bb01 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -755,7 +755,7 @@ input { padding: 0 16px; z-index: 1000; margin-bottom: 0; - min-height: var(--header-height, 40px); + min-height: var(--header-height, 48px); border: 0; position: fixed; top: 0; @@ -771,7 +771,7 @@ input { display: flex; justify-content: space-between; position: relative; - min-height: var(--header-height, 40px); + min-height: var(--header-height, 48px); padding-left: 0; } .navbar-gitlab .header-content .title { @@ -787,9 +787,6 @@ input { .navbar-gitlab .header-content .title img { height: 24px; } -.navbar-gitlab .header-content .title img + .logo-text { - margin-left: 8px; -} .navbar-gitlab .header-content .title a { display: flex; align-items: center; @@ -915,6 +912,8 @@ input { } .navbar-sub-nav { display: flex; + align-items: center; + height: 100%; margin: 0 0 0 6px; } .caret-down, @@ -1034,7 +1033,7 @@ input { left: 0; z-index: 600; width: 220px; - top: var(--header-height, 40px); + top: var(--header-height, 48px); background-color: #303030; transform: translate3d(0, 0, 0); } @@ -2044,19 +2043,9 @@ body.gl-dark { .gl-display-none { display: none; } -@media (min-width: 992px) { - .gl-lg-display-none\! { - display: none !important; - } -} .gl-display-flex { display: flex; } -@media (min-width: 992px) { - .gl-lg-display-flex { - display: flex; - } -} @media (min-width: 576px) { .gl-sm-display-block { display: block; diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index c42b5554d8d..37d0c624eea 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -740,7 +740,7 @@ input { padding: 0 16px; z-index: 1000; margin-bottom: 0; - min-height: var(--header-height, 40px); + min-height: var(--header-height, 48px); border: 0; position: fixed; top: 0; @@ -756,7 +756,7 @@ input { display: flex; justify-content: space-between; position: relative; - min-height: var(--header-height, 40px); + min-height: var(--header-height, 48px); padding-left: 0; } .navbar-gitlab .header-content .title { @@ -772,9 +772,6 @@ input { .navbar-gitlab .header-content .title img { height: 24px; } -.navbar-gitlab .header-content .title img + .logo-text { - margin-left: 8px; -} .navbar-gitlab .header-content .title a { display: flex; align-items: center; @@ -900,6 +897,8 @@ input { } .navbar-sub-nav { display: flex; + align-items: center; + height: 100%; margin: 0 0 0 6px; } .caret-down, @@ -1019,7 +1018,7 @@ input { left: 0; z-index: 600; width: 220px; - top: var(--header-height, 40px); + top: var(--header-height, 48px); background-color: #f0f0f0; transform: translate3d(0, 0, 0); } @@ -1704,19 +1703,9 @@ svg.s16 { .gl-display-none { display: none; } -@media (min-width: 992px) { - .gl-lg-display-none\! { - display: none !important; - } -} .gl-display-flex { display: flex; } -@media (min-width: 992px) { - .gl-lg-display-flex { - display: flex; - } -} @media (min-width: 576px) { .gl-sm-display-block { display: block; diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss index 020ed9c040b..7ac1a187bb8 100644 --- a/app/assets/stylesheets/startup/startup-signin.scss +++ b/app/assets/stylesheets/startup/startup-signin.scss @@ -419,7 +419,7 @@ body.navless { } } .navless-container { - margin-top: var(--header-height, 40px); + margin-top: var(--header-height, 48px); padding-top: 32px; } .btn { @@ -506,7 +506,7 @@ label.label-bold { } .navbar-empty { justify-content: center; - height: var(--header-height, 40px); + height: var(--header-height, 48px); background: #fff; border-bottom: 1px solid #dbdbdb; } diff --git a/app/controllers/mailgun/webhooks_controller.rb b/app/controllers/mailgun/webhooks_controller.rb new file mode 100644 index 00000000000..faeda99769f --- /dev/null +++ b/app/controllers/mailgun/webhooks_controller.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Mailgun + class WebhooksController < ApplicationController + respond_to :json + + skip_before_action :authenticate_user! + skip_before_action :verify_authenticity_token + + before_action :ensure_feature_enabled! + before_action :authenticate_signature! + + feature_category :team_planning + + WEBHOOK_PROCESSORS = [ + ::Members::Mailgun::ProcessWebhookService + ].freeze + + def process_webhook + WEBHOOK_PROCESSORS.each do |processor_class| + processor = processor_class.new(params['event-data'] || {}) + processor.execute if processor.should_process? + end + + head :ok + end + + private + + def ensure_feature_enabled! + render_406 unless Gitlab::CurrentSettings.mailgun_events_enabled? + end + + def authenticate_signature! + access_denied! unless valid_signature? + end + + def valid_signature? + return false if Gitlab::CurrentSettings.mailgun_signing_key.blank? + + # per this guide: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks + digest = OpenSSL::Digest.new('SHA256') + data = [params.dig(:signature, :timestamp), params.dig(:signature, :token)].join + + hmac_digest = OpenSSL::HMAC.hexdigest(digest, Gitlab::CurrentSettings.mailgun_signing_key, data) + + ActiveSupport::SecurityUtils.secure_compare(params.dig(:signature, :signature), hmac_digest) + end + + def render_406 + # failure to stop retries per https://documentation.mailgun.com/en/latest/user_manual.html#webhooks + head :not_acceptable + end + end +end diff --git a/app/controllers/members/mailgun/permanent_failures_controller.rb b/app/controllers/members/mailgun/permanent_failures_controller.rb deleted file mode 100644 index 685faa34694..00000000000 --- a/app/controllers/members/mailgun/permanent_failures_controller.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -module Members - module Mailgun - class PermanentFailuresController < ApplicationController - respond_to :json - - skip_before_action :authenticate_user! - skip_before_action :verify_authenticity_token - - before_action :ensure_feature_enabled! - before_action :authenticate_signature! - before_action :validate_invite_email! - - feature_category :authentication_and_authorization - - def create - webhook_processor.execute - - head :ok - end - - private - - def ensure_feature_enabled! - render_406 unless Gitlab::CurrentSettings.mailgun_events_enabled? - end - - def authenticate_signature! - access_denied! unless valid_signature? - end - - def valid_signature? - return false if Gitlab::CurrentSettings.mailgun_signing_key.blank? - - # per this guide: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks - digest = OpenSSL::Digest.new('SHA256') - data = [params.dig(:signature, :timestamp), params.dig(:signature, :token)].join - - hmac_digest = OpenSSL::HMAC.hexdigest(digest, Gitlab::CurrentSettings.mailgun_signing_key, data) - - ActiveSupport::SecurityUtils.secure_compare(params.dig(:signature, :signature), hmac_digest) - end - - def validate_invite_email! - # permanent_failures webhook does not provide a way to filter failures, so we'll get them all on this endpoint - # and we only care about our invite_emails - render_406 unless payload[:tags]&.include?(::Members::Mailgun::INVITE_EMAIL_TAG) - end - - def webhook_processor - ::Members::Mailgun::ProcessWebhookService.new(payload) - end - - def payload - @payload ||= params.permit!['event-data'] - end - - def render_406 - # failure to stop retries per https://documentation.mailgun.com/en/latest/user_manual.html#webhooks - head :not_acceptable - end - end - end -end diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb index dbf3b2051fb..84e5d59a2c3 100644 --- a/app/controllers/projects/ci/pipeline_editor_controller.rb +++ b/app/controllers/projects/ci/pipeline_editor_controller.rb @@ -4,7 +4,6 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController before_action :check_can_collaborate! before_action do push_frontend_feature_flag(:schema_linting, @project) - push_frontend_feature_flag(:pipeline_editor_file_tree, @project) end feature_category :pipeline_authoring diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb index a72b9a09118..697cc6f5b03 100644 --- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -52,6 +52,7 @@ module ResolvesMergeRequests security_auto_fix: [:author], head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }], timelogs: [:timelogs], + pipelines: [:merge_request_diffs], # used by `recent_diff_head_shas` to load pipelines committers: [merge_request_diff: [:merge_request_diff_commits]] } end diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb index f84eedb4c3b..deb698c63e1 100644 --- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb +++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb @@ -21,8 +21,9 @@ module Resolvers super end - def query_for(args) - resolve_pipelines(project, args).merge(merge_request.all_pipelines) + def query_for(input) + mr, args = input + resolve_pipelines(mr.source_project, args).merge(mr.all_pipelines) end def model_class @@ -30,7 +31,7 @@ module Resolvers end def query_input(**args) - args + [merge_request, args] end def project diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 39b5949ea7a..57eaf27069d 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1696,7 +1696,12 @@ class MergeRequest < ApplicationRecord service_class.new(project, current_user, id: id, report_type: report_type).execute(comparison_base_pipeline(identifier), actual_head_pipeline) end - def recent_diff_head_shas(limit = 100) + MAX_RECENT_DIFF_HEAD_SHAS = 100 + + def recent_diff_head_shas(limit = MAX_RECENT_DIFF_HEAD_SHAS) + # see MergeRequestDiff.recent + return merge_request_diffs.to_a.sort_by(&:id).reverse.first(limit).pluck(:head_commit_sha) if merge_request_diffs.loaded? + merge_request_diffs.recent(limit).pluck(:head_commit_sha) end diff --git a/app/services/members/mailgun/process_webhook_service.rb b/app/services/members/mailgun/process_webhook_service.rb index e359a83ad42..c0a9c2d5290 100644 --- a/app/services/members/mailgun/process_webhook_service.rb +++ b/app/services/members/mailgun/process_webhook_service.rb @@ -16,6 +16,10 @@ module Members Gitlab::ErrorTracking.track_exception(e) end + def should_process? + payload['event'] == 'failed' && payload['severity'] == 'permanent' && payload['tags']&.include?(::Members::Mailgun::INVITE_EMAIL_TAG) + end + private attr_reader :payload, :member diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 2ec965fe2f4..c6ea364320f 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -30,6 +30,7 @@ module Projects validate_state! validate_max_size! + validate_public_folder! validate_max_entries! build.artifacts_file.use_file do |artifacts_path| @@ -180,6 +181,10 @@ module Projects end end + def validate_public_folder! + raise InvalidStateError, 'Error: The `public/` folder is missing, or not declared in `.gitlab-ci.yml`.' unless total_size > 0 + end + def entries_count # we're using the full archive and pages daemon needs to read it # so we want the total count from entries, not only "public/" directory diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 3cae8186750..4ba2a2d4fe4 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -9,10 +9,7 @@ %h1.title %span.gl-sr-only GitLab = link_to root_path, title: _('Dashboard'), id: 'logo', **tracking_attrs('main_navigation', 'click_gitlab_logo_link', 'navigation') do - %span{ :class => "gl-display-none gl-lg-display-flex" } - = brand_header_logo({add_gitlab_white_text: true}) - %span{ :class => "gl-lg-display-none! gl-display-flex" } - = brand_header_logo + = brand_header_logo - if Gitlab.com_and_canary? = link_to Gitlab::Saas.canary_toggle_com_url, class: 'canary-badge bg-transparent', data: { qa_selector: 'canary_badge_link' }, target: :_blank, rel: 'noopener noreferrer' do = gl_badge_tag({ variant: :success, size: :sm }) do diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 88ca64f2af0..1d53726e25c 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -1,3 +1,5 @@ - page_title _('Edit Release') +- add_to_breadcrumbs _('Releases'), project_releases_path(@project) +- add_to_breadcrumbs @release.name, project_release_path(@project, @release) #js-edit-release-page{ data: data_for_edit_release_page } diff --git a/app/workers/database/batched_background_migration/single_database_worker.rb b/app/workers/database/batched_background_migration/single_database_worker.rb index aeadda4b8e1..8a0de268048 100644 --- a/app/workers/database/batched_background_migration/single_database_worker.rb +++ b/app/workers/database/batched_background_migration/single_database_worker.rb @@ -7,6 +7,7 @@ module Database include ApplicationWorker include CronjobQueue # rubocop:disable Scalability/CronWorkerContext + include Gitlab::Utils::StrongMemoize LEASE_TIMEOUT_MULTIPLIER = 3 MINIMUM_LEASE_TIMEOUT = 10.minutes.freeze @@ -44,6 +45,15 @@ module Database return end + if shares_db_config? + Sidekiq.logger.info( + class: self.class.name, + database: self.class.tracking_database, + message: 'skipping migration execution for database that shares database configuration with another database') + + return + end + Gitlab::Database::SharedModel.using_connection(base_model.connection) do break unless self.class.enabled? && active_migration @@ -63,7 +73,7 @@ module Database private def active_migration - @active_migration ||= Gitlab::Database::BackgroundMigration::BatchedMigration.active_migration + @active_migration ||= Gitlab::Database::BackgroundMigration::BatchedMigration.active_migration(connection: base_model.connection) end def run_active_migration @@ -71,7 +81,13 @@ module Database end def base_model - @base_model ||= Gitlab::Database.database_base_models[self.class.tracking_database] + strong_memoize(:base_model) do + Gitlab::Database.database_base_models[self.class.tracking_database] + end + end + + def shares_db_config? + base_model && Gitlab::Database.db_config_share_with(base_model.connection_db_config).present? end def with_exclusive_lease(interval) diff --git a/app/workers/namespaces/process_sync_events_worker.rb b/app/workers/namespaces/process_sync_events_worker.rb index 269710dd804..ea28fcd8720 100644 --- a/app/workers/namespaces/process_sync_events_worker.rb +++ b/app/workers/namespaces/process_sync_events_worker.rb @@ -13,7 +13,7 @@ module Namespaces urgency :high idempotent! - deduplicate :until_executing + deduplicate :until_executed def perform results = ::Ci::ProcessSyncEventsService.new( diff --git a/app/workers/projects/process_sync_events_worker.rb b/app/workers/projects/process_sync_events_worker.rb index 1330ae47a68..6b6b4e3db7f 100644 --- a/app/workers/projects/process_sync_events_worker.rb +++ b/app/workers/projects/process_sync_events_worker.rb @@ -13,7 +13,7 @@ module Projects urgency :high idempotent! - deduplicate :until_executing + deduplicate :until_executed def perform results = ::Ci::ProcessSyncEventsService.new( |