diff options
26 files changed, 337 insertions, 159 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml index 62546e59368..b871ba33974 100644 --- a/.gitlab/ci/docs.gitlab-ci.yml +++ b/.gitlab/ci/docs.gitlab-ci.yml @@ -42,7 +42,7 @@ docs lint: extends: - .default-retry - .docs:rules:docs-lint - image: "registry.gitlab.com/gitlab-org/gitlab-docs:lint" + image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint:vale-2.3.3-markdownlint-0.23.2" stage: test needs: [] script: @@ -26,7 +26,7 @@ gem 'marginalia', '~> 1.9.0' # Authentication libraries gem 'devise', '~> 4.6' -gem 'doorkeeper', '~> 5.0.3' +gem 'doorkeeper', '~> 5.1.1' gem 'doorkeeper-openid_connect', '~> 1.6.3' gem 'omniauth', '~> 1.8' gem 'omniauth-auth0', '~> 2.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 5d64692e6fb..0f5cfcdc056 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -254,8 +254,8 @@ GEM docile (1.3.2) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - doorkeeper (5.0.3) - railties (>= 4.2) + doorkeeper (5.1.1) + railties (>= 5) doorkeeper-openid_connect (1.6.3) doorkeeper (>= 5.0, < 5.2) json-jwt (~> 1.6) @@ -1265,7 +1265,7 @@ DEPENDENCIES diff_match_patch (~> 0.1.0) diffy (~> 3.3) discordrb-webhooks-blackst0ne (~> 3.3) - doorkeeper (~> 5.0.3) + doorkeeper (~> 5.1.1) doorkeeper-openid_connect (~> 1.6.3) ed25519 (~> 1.2) elasticsearch-api (~> 6.8) diff --git a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue index 6a20517eed7..cd2545b48de 100644 --- a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue +++ b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue @@ -62,22 +62,20 @@ export default { activeDiscussion: { query: activeDiscussionQuery, result({ data }) { - const discussionId = data.activeDiscussion.id; if (this.discussion.resolved && !this.resolvedDiscussionsExpanded) { return; } - // We watch any changes to the active discussion from the design pins and scroll to this discussion if it exists - // We don't want scrollIntoView to be triggered from the discussion click itself - if ( - discussionId && - data.activeDiscussion.source === ACTIVE_DISCUSSION_SOURCE_TYPES.pin && - discussionId === this.discussion.notes[0].id - ) { - this.$el.scrollIntoView({ - behavior: 'smooth', - inline: 'start', - }); - } + + this.$nextTick(() => { + // We watch any changes to the active discussion from the design pins and scroll to this discussion if it exists. + // We don't want scrollIntoView to be triggered from the discussion click itself. + if (this.$el && this.shouldScrollToDiscussion(data.activeDiscussion)) { + this.$el.scrollIntoView({ + behavior: 'smooth', + inline: 'start', + }); + } + }); }, }, }, @@ -136,6 +134,18 @@ export default { isFormVisible() { return this.isFormRendered && this.discussionWithOpenForm === this.discussion.id; }, + shouldScrollToDiscussion(activeDiscussion) { + const ALLOWED_ACTIVE_DISCUSSION_SOURCES = [ + ACTIVE_DISCUSSION_SOURCE_TYPES.pin, + ACTIVE_DISCUSSION_SOURCE_TYPES.url, + ]; + const { id: activeDiscussionId, source: activeDiscussionSource } = activeDiscussion; + + return ( + ALLOWED_ACTIVE_DISCUSSION_SOURCES.includes(activeDiscussionSource) && + activeDiscussionId === this.discussion.notes[0].id + ); + }, }, methods: { addDiscussionComment( diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue index 43165261f6b..eab19e38a45 100644 --- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue +++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue @@ -60,9 +60,11 @@ export default { }, }, mounted() { - if (this.isNoteLinked) { - this.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' }); - } + this.$nextTick(() => { + if (this.isNoteLinked) { + this.$el.scrollIntoView({ behavior: 'smooth', inline: 'start' }); + } + }); }, methods: { hideForm() { diff --git a/app/assets/javascripts/design_management/constants.js b/app/assets/javascripts/design_management/constants.js index 21ff361a277..63a92ef5ec0 100644 --- a/app/assets/javascripts/design_management/constants.js +++ b/app/assets/javascripts/design_management/constants.js @@ -11,6 +11,7 @@ export const VALID_DATA_TRANSFER_TYPE = 'Files'; export const ACTIVE_DISCUSSION_SOURCE_TYPES = { pin: 'pin', discussion: 'discussion', + url: 'url', }; export const DESIGN_DETAIL_LAYOUT_CLASSLIST = ['design-detail-layout', 'overflow-hidden', 'm-0']; diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue index 17b72e73127..93fb9f37b72 100644 --- a/app/assets/javascripts/design_management/pages/design/index.vue +++ b/app/assets/javascripts/design_management/pages/design/index.vue @@ -19,6 +19,8 @@ import { extractDiscussions, extractDesign, updateImageDiffNoteOptimisticResponse, + toDiffNoteGid, + extractDesignNoteId, } from '../../utils/design_management_utils'; import { updateStoreAfterAddImageDiffNote, @@ -145,8 +147,11 @@ export default { mounted() { Mousetrap.bind('esc', this.closeDesign); this.trackEvent(); - // We need to reset the active discussion when opening a new design - this.updateActiveDiscussion(); + + // Set active discussion immediately. + // This will ensure that, if a note is specified in the URL hash, + // the browser will scroll to, and highlight, the note in the UI + this.updateActiveDiscussionFromUrl(); }, beforeDestroy() { Mousetrap.unbind('esc', this.closeDesign); @@ -266,15 +271,20 @@ export default { this.isLatestVersion, ); }, - updateActiveDiscussion(id) { + updateActiveDiscussion(id, source = ACTIVE_DISCUSSION_SOURCE_TYPES.discussion) { this.$apollo.mutate({ mutation: updateActiveDiscussionMutation, variables: { id, - source: ACTIVE_DISCUSSION_SOURCE_TYPES.discussion, + source, }, }); }, + updateActiveDiscussionFromUrl() { + const noteId = extractDesignNoteId(this.$route.hash); + const diffNoteGid = noteId ? toDiffNoteGid(noteId) : undefined; + return this.updateActiveDiscussion(diffNoteGid, ACTIVE_DISCUSSION_SOURCE_TYPES.url); + }, toggleResolvedComments() { this.resolvedDiscussionsExpanded = !this.resolvedDiscussionsExpanded; }, diff --git a/app/assets/javascripts/design_management/utils/design_management_utils.js b/app/assets/javascripts/design_management/utils/design_management_utils.js index da8f89ff960..a5514597fcf 100644 --- a/app/assets/javascripts/design_management/utils/design_management_utils.js +++ b/app/assets/javascripts/design_management/utils/design_management_utils.js @@ -34,6 +34,17 @@ export const extractDesigns = data => data.project.issue.designCollection.design export const extractDesign = data => (extractDesigns(data) || [])[0]; +export const toDiffNoteGid = noteId => `gid://gitlab/DiffNote/${noteId}`; + +/** + * Return the note ID from a URL hash parameter + * @param {String} urlHash URL hash, including `#` prefix + */ +export const extractDesignNoteId = urlHash => { + const [, noteId] = urlHash.match('#note_([0-9]+$)') || []; + return noteId || null; +}; + /** * Generates optimistic response for a design upload mutation * @param {Array<File>} files diff --git a/changelogs/unreleased/232839-fj-track-unique-edit-web-ide-action.yml b/changelogs/unreleased/232839-fj-track-unique-edit-web-ide-action.yml new file mode 100644 index 00000000000..8ebdb49ffc4 --- /dev/null +++ b/changelogs/unreleased/232839-fj-track-unique-edit-web-ide-action.yml @@ -0,0 +1,5 @@ +--- +title: Track unique web ide edit action for usage ping +merge_request: 40246 +author: +type: changed diff --git a/changelogs/unreleased/fix-design-managemnt-comment-highlight.yml b/changelogs/unreleased/fix-design-managemnt-comment-highlight.yml new file mode 100644 index 00000000000..d808e52e9b7 --- /dev/null +++ b/changelogs/unreleased/fix-design-managemnt-comment-highlight.yml @@ -0,0 +1,5 @@ +--- +title: Ensure design comment is highlighted when comment is in URL +merge_request: 40477 +author: +type: fixed diff --git a/changelogs/unreleased/id-bump-doorkeeper-5-1.yml b/changelogs/unreleased/id-bump-doorkeeper-5-1.yml new file mode 100644 index 00000000000..d8a5d2749b2 --- /dev/null +++ b/changelogs/unreleased/id-bump-doorkeeper-5-1.yml @@ -0,0 +1,5 @@ +--- +title: Bump doorkeeper to 5.1.1 +merge_request: 40546 +author: +type: changed diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md index 23cc1352d11..44c210efa62 100644 --- a/doc/administration/reference_architectures/2k_users.md +++ b/doc/administration/reference_architectures/2k_users.md @@ -405,7 +405,7 @@ To configure the Gitaly server: # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API. # The following two values must be the same as their respective values # of the GitLab Rails application setup - gitaly['auth_token'] = 'gitlaysecret' + gitaly['auth_token'] = 'gitalysecret' gitlab_shell['secret_token'] = 'shellsecret' # Avoid running unnecessary services on the Gitaly server @@ -586,7 +586,7 @@ On each node perform the following: # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API. # The following two values must be the same as their respective values # of the Gitaly setup - gitlab_rails['gitaly_token'] = 'gitalyecret' + gitlab_rails['gitaly_token'] = 'gitalysecret' gitlab_shell['secret_token'] = 'shellsecret' git_data_dirs({ diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md index 2a9590d1908..e1e6250632e 100644 --- a/doc/administration/reference_architectures/3k_users.md +++ b/doc/administration/reference_architectures/3k_users.md @@ -1131,7 +1131,7 @@ On each node: # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API. # The following two values must be the same as their respective values # of the GitLab Rails application setup - gitaly['auth_token'] = 'gitlaysecret' + gitaly['auth_token'] = 'gitalysecret' gitlab_shell['secret_token'] = 'shellsecret' # Avoid running unnecessary services on the Gitaly server @@ -1474,7 +1474,7 @@ On each node perform the following: # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API. # The following two values must be the same as their respective values # of the Gitaly setup - gitlab_rails['gitaly_token'] = 'gitalyecret' + gitlab_rails['gitaly_token'] = 'gitalysecret' gitlab_shell['secret_token'] = 'shellsecret' git_data_dirs({ diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md index f485b7d35e8..5a52b6d9d0f 100644 --- a/doc/administration/reference_architectures/5k_users.md +++ b/doc/administration/reference_architectures/5k_users.md @@ -1130,7 +1130,7 @@ On each node: # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API. # The following two values must be the same as their respective values # of the GitLab Rails application setup - gitaly['auth_token'] = 'gitlaysecret' + gitaly['auth_token'] = 'gitalysecret' gitlab_shell['secret_token'] = 'shellsecret' # Avoid running unnecessary services on the Gitaly server @@ -1473,7 +1473,7 @@ On each node perform the following: # to Gitaly, and a second for authentication callbacks from GitLab-Shell to the GitLab internal API. # The following two values must be the same as their respective values # of the Gitaly setup - gitlab_rails['gitaly_token'] = 'gitalyecret' + gitlab_rails['gitaly_token'] = 'gitalysecret' gitlab_shell['secret_token'] = 'shellsecret' git_data_dirs({ diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 050df243af4..25421b067dc 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -11,9 +11,9 @@ GitLab offers a [continuous integration](https://about.gitlab.com/stages-devops- [pipeline](../pipelines/index.md), you must: - Add a [`.gitlab-ci.yml` file](#creating-a-gitlab-ciyml-file) to your repository's root directory. -- Ensure your project is configured to use a [Runner](#configuring-a-runner). +- Ensure your project is configured to use a [runner](#configuring-a-runner). -The `.gitlab-ci.yml` file tells the GitLab Runner what to do. A simple pipeline commonly has +The `.gitlab-ci.yml` file tells the runner what to do. A simple pipeline commonly has three [stages](../yaml/README.md#stages): - `build` @@ -57,7 +57,7 @@ The `.gitlab-ci.yml` file is where you configure what CI does with your project. It lives in the root of your repository. On any push to your repository, GitLab will look for the `.gitlab-ci.yml` -file and start jobs on _Runners_ according to the contents of the file, +file and start jobs on _runners_ according to the contents of the file, for that commit. Because `.gitlab-ci.yml` is in the repository and is version controlled, old @@ -109,7 +109,7 @@ The `.gitlab-ci.yml` file defines sets of jobs with constraints of how and when they should be run. The jobs are defined as top-level elements with a name (in our case `rspec` and `rubocop`) and always have to contain the `script` keyword. Jobs are used to create jobs, which are then picked by -[Runners](../runners/README.md) and executed within the environment of the Runner. +[runners](../runners/README.md) and executed within the environment of the runner. What is important is that each job is run independently from each other. @@ -148,59 +148,54 @@ Clicking on it you will be directed to the jobs page for that specific commit. ![Single commit jobs page](img/single_commit_status_pending.png) Notice that there is a pending job which is named after what we wrote in -`.gitlab-ci.yml`. "stuck" indicates that there is no Runner configured +`.gitlab-ci.yml`. "stuck" indicates that there is no runner configured yet for this job. -The next step is to configure a Runner so that it picks the pending jobs. +The next step is to configure a runner so that it picks the pending jobs. -## Configuring a Runner +## Configuring a runner -In GitLab, Runners run the jobs that you define in `.gitlab-ci.yml`. A Runner -can be a virtual machine, a VPS, a bare-metal machine, a Docker container or -even a cluster of containers. GitLab and the Runners communicate through an API, -so the only requirement is that the Runner's machine has network access to the +In GitLab, runners run the jobs that you define in `.gitlab-ci.yml`. A runner +can be a virtual machine, a VPS, a bare-metal machine, a Docker container, or +even a cluster of containers. GitLab and the runner communicate through an API, +so the only requirement is that the runner's machine has network access to the GitLab server. -A Runner can be specific to a certain project or serve multiple projects in -GitLab. If it serves all projects it's called a _Shared Runner_. +A runner can be specific to a certain project or serve multiple projects in +GitLab. If it serves all projects, it's called a _shared runner_. -Find more information about different Runners in the -[Runners](../runners/README.md) documentation. +Find more information about runners in the +[runner](../runners/README.md) documentation. -You can find whether any Runners are assigned to your project by going to -**Settings ➔ CI/CD**. Setting up a Runner is easy and straightforward. The -official Runner supported by GitLab is written in Go and its documentation -can be found at <https://docs.gitlab.com/runner/>. +The official runner supported by GitLab is written in Go. +View [the documentation](https://docs.gitlab.com/runner/). -In order to have a functional Runner you need to follow two steps: +For a runner to be available in GitLab, you must: -1. [Install it](https://docs.gitlab.com/runner/install/) -1. [Configure it](https://docs.gitlab.com/runner/configuration/) +1. [Install GitLab Runner](https://docs.gitlab.com/runner/install/). +1. [Register a runner for your group or project](https://docs.gitlab.com/runner/register/). -Follow the links above to set up your own Runner or use a Shared Runner as -described in the next section. - -Once the Runner has been set up, you should see it on the Runners page of your -project, following **Settings ➔ CI/CD**. +When a runner is available, you can view it by +clicking **Settings > CI/CD** and expanding **Runners**. ![Activated runners](img/runners_activated.png) -### Shared Runners +### Shared runners -If you use [GitLab.com](https://gitlab.com/) you can use the **Shared Runners** -provided by GitLab Inc. +If you use [GitLab.com](https://gitlab.com/), you can use the **shared runners** +provided by GitLab. These are special virtual machines that run on GitLab's infrastructure and can build any project. -To enable the **Shared Runners** you have to go to your project's -**Settings ➔ CI/CD** and click **Enable shared runners**. +To enable shared runners, go to your project's or group's +**Settings > CI/CD** and click **Enable shared runners**. -[Read more on Shared Runners](../runners/README.md). +[Read more about shared runners](../runners/README.md#shared-runners). -## Seeing the status of your pipeline and jobs +## Viewing the status of your pipeline and jobs -After configuring the Runner successfully, you should see the status of your +After configuring the runner successfully, you should see the status of your last commit change from _pending_ to either _running_, _success_ or _failed_. You can view all pipelines by going to the **Pipelines** page in your project. diff --git a/doc/development/cicd/templates.md b/doc/development/cicd/templates.md index 7d0cd1ec08a..efcc7b363ff 100644 --- a/doc/development/cicd/templates.md +++ b/doc/development/cicd/templates.md @@ -64,6 +64,65 @@ users have to fix their `.gitlab-ci.yml` that could annoy their workflow. Please read [versioning](#versioning) section for introducing breaking change safely. +## Versioning + +Versioning allows you to introduce a new template without modifying the existing +one. This process is useful when we need to introduce a breaking change, +but don't want to affect the existing projects that depends on the current template. + +### Stable version + +A stable CI/CD template is a template that only introduces breaking changes in major +release milestones. Name the stable version of a template as `<template-name>.gitlab-ci.yml`, +for example `Jobs/Deploy.gitlab-ci.yml`. + +You can make a new stable template by copying [the latest template](#latest-version) +available in a major milestone release of GitLab like `13.0`. All breaking changes +must be announced in a blog post before the official release, for example +[GitLab.com is moving to 13.0, with narrow breaking changes](https://about.gitlab.com/releases/2020/05/06/gitlab-com-13-0-breaking-changes/) + +You can change a stable template version in a minor GitLab release like `13.1` if: + +- The change is not a [breaking change](#backward-compatibility). +- The change is ported to [the latest template](#latest-version), if one exists. + +### Latest version + +Templates marked as `latest` can be updated in any release, even with +[breaking changes](#backward-compatibility). Add `.latest` to the template name if +it's considered the latest version, for example `Jobs/Deploy.latest.gitlab-ci.yml`. + +When you introduce [a breaking change](#backward-compatibility), +you **must** test and document [the upgrade path](#verify-breaking-changes). +In general, we should not promote the latest template as the best option, as it could surprise users with unexpected problems. + +If the `latest` template does not exist yet, you can copy [the stable template](#stable-version). + +### How to include an older stable template + +Users may want to use an older [stable template](#stable-version) that is not bundled +in the current GitLab package. For example, the stable templates in GitLab v13.0 and +GitLab v14.0 could be so different that a user will want to continue using the v13.0 template even +after upgrading to GitLab 14.0. + +You can add a note in the template or in documentation explaining how to use `include:remote` +to include older template versions: + +```yaml +# To use the v13 stable template, which is not included in v14, fetch the specifc +# template from the remote template repository with the `include:remote:` keyword. +# If you fetch from the GitLab canonical project, use the following URL format: +# https://gitlab.com/gitlab-org/gitlab/-/raw/<version>/lib/gitlab/ci/templates/<template-name> +include: + remote: https://gitlab.com/gitlab-org/gitlab/-/raw/v13.0.1-ee/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml +``` + +### Further reading + +There is an [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/17716) about +introducing versioning concepts in GitLab CI Templates. You can check that issue to +follow the progress. + ## Testing Each CI/CD template must be tested in order to make sure that it's safe to be published. @@ -95,18 +154,20 @@ You should write an RSpec test to make sure that pipeline jobs will be generated 1. Add a test file at `spec/lib/gitlab/ci/templates/<template-category>/<template-name>_spec.rb` 1. Test that pipeline jobs are properly created via `Ci::CreatePipelineService`. +### Verify breaking changes + +When you introduce a breaking change to [a `latest` template](#latest-version), +you must: + +1. Test the upgrade path from [the stable template](#stable-version). +1. Verify what kind of errors users will encounter. +1. Document it as a troubleshooting guide. + +This information will be important for users when [a stable template](#stable-version) +is updated in a major version GitLab release. + ## Security A template could contain malicious code. For example, a template that contains the `export` shell command in a job might accidentally expose project secret variables in a job log. If you're unsure if it's secure or not, you need to ask security experts for cross-validation. - -## Versioning - -Versioning allows you to introduce a new template without modifying the existing -one. This is useful process especially when we need to introduce a breaking change, -but don't want to affect the existing projects that depends on the current template. - -There is an [open issue](https://gitlab.com/gitlab-org/gitlab/-/issues/17716) for -introducing versioning concept in GitLab Ci Template. Please follow the issue for -checking the progress. diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md index f98a8eb9c6d..7329725a643 100644 --- a/doc/user/packages/maven_repository/index.md +++ b/doc/user/packages/maven_repository/index.md @@ -20,7 +20,7 @@ NOTE: **Note:** This option is available only if your GitLab administrator has [enabled support for the Maven repository](../../../administration/packages/index.md). -After the Packages feature is enabled, the Maven Repository will be available for +After the Packages feature is enabled, the Maven Repository is available for all new projects by default. To enable it for existing projects, or if you want to disable it: @@ -34,7 +34,7 @@ repository. ## Getting Started with Maven -This section will cover installing Maven and building a package. This is a +This section covers installing Maven and building a package. This is a quickstart to help if you're new to building Maven packages. If you're already using Maven and understand how to build your own packages, move onto the [next section](#adding-the-gitlab-package-registry-as-a-maven-remote). @@ -107,7 +107,7 @@ You should see a new directory where you ran this command matching your ## Getting started with Gradle -This section will cover installing Gradle and initializing a Java project. This is a +This section covers installing Gradle and initializing a Java project. This is a quickstart to help if you're new to Gradle. If you're already using Gradle and understand how to build your own packages, move onto the [next section](#adding-the-gitlab-package-registry-as-a-maven-remote). @@ -128,7 +128,7 @@ If you want to use an existing Gradle project, installation is not necessary. Simply execute `gradlew` (on Linux) or `gradlew.bat` (on Windows) in the project directory instead. -You should see something imilar to the below printed in the output: +You should see something similar to the below printed in the output: ```plaintext ------------------------------------------------------------ @@ -191,7 +191,7 @@ Select build script DSL: Enter selection (default: Groovy) [1..2] ``` -Choose `1` to create a new Java Library project which will be described in Groovy DSL. The output should be: +Choose `1` to create a new Java Library project which is described in Groovy DSL. The output should be: ```plaintext Select test framework: @@ -213,7 +213,7 @@ Enter a project name or hit enter to use the directory name as project name. The next step is to add the GitLab Package Registry as a Maven remote. If a project is private or you want to upload Maven artifacts to GitLab, -credentials will need to be provided for authorization too. Support is available +credentials must be provided for authorization too. Support is available for [personal access tokens](#authenticating-with-a-personal-access-token), [CI job tokens](#authenticating-with-a-ci-job-token), and [deploy tokens](../../project/deploy_tokens/index.md) only. Regular username/password @@ -388,7 +388,7 @@ repositories { To download and upload packages from GitLab, you need a `repository` and `distributionManagement` section in your `pom.xml` file. If you're following the -steps from above, then you'll need to add the following information to your +steps from above, then you must add the following information to your `my-project/pom.xml` file. Depending on your workflow and the amount of Maven packages you have, there are @@ -462,13 +462,13 @@ project's ID can be used for uploading. If you rely on many packages, it might be inefficient to include the `repository` section with a unique URL for each package. Instead, you can use the group level endpoint for all your Maven packages stored within one GitLab group. Only packages you have access to -will be available for download. +are available for download. The group level endpoint works with any package names, which means the you have the flexibility of naming compared to [instance level endpoint](#instance-level-maven-endpoint). -However, GitLab will not guarantee the uniqueness of the package names within +However, GitLab does not guarantee the uniqueness of the package names within the group. You can have two projects with the same package name and package -version. As a result, GitLab will serve whichever one is more recent. +version. As a result, GitLab serves whichever one is more recent. The example below shows how the relevant `repository` section of your `pom.xml` would look like. You still need a project specific URL for uploading a package in @@ -524,7 +524,7 @@ For retrieving artifacts, you can use either the If you rely on many packages, it might be inefficient to include the `repository` section with a unique URL for each package. Instead, you can use the instance level endpoint for -all maven packages stored in GitLab and the packages you have access to will be available +all maven packages stored in GitLab and the packages you have access to are available for download. Note that **only packages that have the same path as the project** are exposed via @@ -662,7 +662,7 @@ artifacts or even delete them. Installing a package from the GitLab Package Registry requires that you set up the [remote and authentication](#adding-the-gitlab-package-registry-as-a-maven-remote) -as above. Once this is completed, there are two ways for installaing a package. +as above. Once this is completed, there are two ways to install a package. ### Install using Maven with `mvn install` @@ -732,7 +732,7 @@ you can configure GitLab CI/CD to build new packages automatically. The example below shows how to create a new package each time the `master` branch is updated: -1. Create a `ci_settings.xml` file that will serve as Maven's `settings.xml` file. +1. Create a `ci_settings.xml` file that serves as Maven's `settings.xml` file. Add the server section with the same ID you defined in your `pom.xml` file. For example, in our case it's `gitlab-maven`: @@ -792,9 +792,9 @@ is updated: 1. Push those files to your repository. -The next time the `deploy` job runs, it will copy `ci_settings.xml` to the +The next time the `deploy` job runs, it copies `ci_settings.xml` to the user's home location (in this case the user is `root` since it runs in a -Docker container), and Maven will utilize the configured CI +Docker container), and Maven uses the configured CI [environment variables](../../../ci/variables/README.md#predefined-environment-variables). ### Creating Maven packages with GitLab CI/CD using Gradle diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 3c7ed2a25a0..20877fb5c5f 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -136,7 +136,10 @@ module API if result[:status] == :success commit_detail = user_project.repository.commit(result[:result]) - Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count if find_user_from_warden + if find_user_from_warden + Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count + Gitlab::UsageDataCounters::EditorUniqueCounter.track_web_ide_edit_action(author: current_user) + end present commit_detail, with: Entities::CommitDetail, stats: params[:stats] else diff --git a/spec/controllers/oauth/token_info_controller_spec.rb b/spec/controllers/oauth/token_info_controller_spec.rb index 91a986db251..6d01a534673 100644 --- a/spec/controllers/oauth/token_info_controller_spec.rb +++ b/spec/controllers/oauth/token_info_controller_spec.rb @@ -5,10 +5,10 @@ require 'spec_helper' RSpec.describe Oauth::TokenInfoController do describe '#show' do context 'when the user is not authenticated' do - it 'responds with a 401' do + it 'responds with a 400' do get :show - expect(response).to have_gitlab_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:bad_request) expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_request') end end @@ -36,10 +36,10 @@ RSpec.describe Oauth::TokenInfoController do end context 'when the doorkeeper_token is not recognised' do - it 'responds with a 401' do + it 'responds with a 400' do get :show, params: { access_token: 'unknown_token' } - expect(response).to have_gitlab_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:bad_request) expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_request') end end @@ -49,10 +49,10 @@ RSpec.describe Oauth::TokenInfoController do create(:oauth_access_token, created_at: 2.days.ago, expires_in: 10.minutes) end - it 'responds with a 401' do + it 'responds with a 400' do get :show, params: { access_token: access_token.token } - expect(response).to have_gitlab_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:bad_request) expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_request') end end @@ -60,10 +60,10 @@ RSpec.describe Oauth::TokenInfoController do context 'when the token is revoked' do let(:access_token) { create(:oauth_access_token, revoked_at: 2.days.ago) } - it 'responds with a 401' do + it 'responds with a 400' do get :show, params: { access_token: access_token.token } - expect(response).to have_gitlab_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:bad_request) expect(Gitlab::Json.parse(response.body)).to include('error' => 'invalid_request') end end diff --git a/spec/frontend/design_management/components/design_notes/design_note_spec.js b/spec/frontend/design_management/components/design_notes/design_note_spec.js index 8eeaa6cd5ab..a59a2223cf3 100644 --- a/spec/frontend/design_management/components/design_notes/design_note_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_note_spec.js @@ -91,7 +91,9 @@ describe('Design note component', () => { note, }); - expect(scrollIntoViewMock).toHaveBeenCalled(); + return wrapper.vm.$nextTick().then(() => { + expect(scrollIntoViewMock).toHaveBeenCalled(); + }); }); it('should not render edit icon when user does not have a permission', () => { diff --git a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap index 5fdc41bbfbf..3c5de543eb3 100644 --- a/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap +++ b/spec/frontend/design_management/pages/design/__snapshots__/index_spec.js.snap @@ -57,7 +57,7 @@ exports[`Design management design index page renders design index 1`] = ` <design-discussion-stub data-testid="unresolved-discussion" - designid="test" + designid="design-id" discussion="[object Object]" discussionwithopenform="" markdownpreviewpath="/project-path/preview_markdown?target_type=Issue" @@ -105,7 +105,7 @@ exports[`Design management design index page renders design index 1`] = ` > <design-discussion-stub data-testid="resolved-discussion" - designid="test" + designid="design-id" discussion="[object Object]" discussionwithopenform="" markdownpreviewpath="/project-path/preview_markdown?target_type=Issue" diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js index 369c8667f4d..d189bdb4345 100644 --- a/spec/frontend/design_management/pages/design/index_spec.js +++ b/spec/frontend/design_management/pages/design/index_spec.js @@ -7,18 +7,19 @@ import DesignIndex from '~/design_management/pages/design/index.vue'; import DesignSidebar from '~/design_management/components/design_sidebar.vue'; import DesignPresentation from '~/design_management/components/design_presentation.vue'; import createImageDiffNoteMutation from '~/design_management/graphql/mutations/create_image_diff_note.mutation.graphql'; -import design from '../../mock_data/design'; -import mockResponseWithDesigns from '../../mock_data/designs'; -import mockResponseNoDesigns from '../../mock_data/no_designs'; -import mockAllVersions from '../../mock_data/all_versions'; +import updateActiveDiscussion from '~/design_management/graphql/mutations/update_active_discussion.mutation.graphql'; import { DESIGN_NOT_FOUND_ERROR, DESIGN_VERSION_NOT_EXIST_ERROR, } from '~/design_management/utils/error_messages'; -import { DESIGNS_ROUTE_NAME } from '~/design_management/router/constants'; +import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management/router/constants'; import createRouter from '~/design_management/router'; import * as utils from '~/design_management/utils/design_management_utils'; import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management/constants'; +import design from '../../mock_data/design'; +import mockResponseWithDesigns from '../../mock_data/designs'; +import mockResponseNoDesigns from '../../mock_data/no_designs'; +import mockAllVersions from '../../mock_data/all_versions'; jest.mock('~/flash'); jest.mock('mousetrap', () => ({ @@ -34,6 +35,12 @@ const DesignReplyForm = { focusInput, }, }; +const mockDesignNoDiscussions = { + ...design, + discussions: { + nodes: [], + }, +}; const localVue = createLocalVue(); localVue.use(VueRouter); @@ -75,7 +82,7 @@ describe('Design management design index page', () => { const findSidebar = () => wrapper.find(DesignSidebar); const findDesignPresentation = () => wrapper.find(DesignPresentation); - function createComponent(loading = false, data = {}) { + function createComponent({ loading = false } = {}, { data = {}, intialRouteOptions = {} } = {}) { const $apollo = { queries: { design: { @@ -87,6 +94,8 @@ describe('Design management design index page', () => { router = createRouter(); + router.push({ name: DESIGN_ROUTE_NAME, params: { id: design.id }, ...intialRouteOptions }); + wrapper = shallowMount(DesignIndex, { propsData: { id: '1' }, mocks: { $apollo }, @@ -126,29 +135,28 @@ describe('Design management design index page', () => { }, }; jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockEl); - createComponent(true); + createComponent({ loading: true }); - wrapper.vm.$router.push('/designs/test'); expect(mockEl.classList.add).toHaveBeenCalledTimes(1); expect(mockEl.classList.add).toHaveBeenCalledWith(...DESIGN_DETAIL_LAYOUT_CLASSLIST); }); }); it('sets loading state', () => { - createComponent(true); + createComponent({ loading: true }); expect(wrapper.element).toMatchSnapshot(); }); it('renders design index', () => { - createComponent(false, { design }); + createComponent({ loading: false }, { data: { design } }); expect(wrapper.element).toMatchSnapshot(); expect(wrapper.find(GlAlert).exists()).toBe(false); }); it('passes correct props to sidebar component', () => { - createComponent(false, { design }); + createComponent({ loading: false }, { data: { design } }); expect(findSidebar().props()).toEqual({ design, @@ -158,14 +166,14 @@ describe('Design management design index page', () => { }); it('opens a new discussion form', () => { - createComponent(false, { - design: { - ...design, - discussions: { - nodes: [], + createComponent( + { loading: false }, + { + data: { + design, }, }, - }); + ); findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 }); @@ -175,15 +183,15 @@ describe('Design management design index page', () => { }); it('keeps new discussion form focused', () => { - createComponent(false, { - design: { - ...design, - discussions: { - nodes: [], + createComponent( + { loading: false }, + { + data: { + design, + annotationCoordinates, }, }, - annotationCoordinates, - }); + ); findDesignPresentation().vm.$emit('openCommentForm', { x: 10, y: 10 }); @@ -191,16 +199,16 @@ describe('Design management design index page', () => { }); it('sends a mutation on submitting form and closes form', () => { - createComponent(false, { - design: { - ...design, - discussions: { - nodes: [], + createComponent( + { loading: false }, + { + data: { + design, + annotationCoordinates, + comment: newComment, }, }, - annotationCoordinates, - comment: newComment, - }); + ); findDiscussionForm().vm.$emit('submitForm'); expect(mutate).toHaveBeenCalledWith(createDiscussionMutationVariables); @@ -216,16 +224,16 @@ describe('Design management design index page', () => { }); it('closes the form and clears the comment on canceling form', () => { - createComponent(false, { - design: { - ...design, - discussions: { - nodes: [], + createComponent( + { loading: false }, + { + data: { + design, + annotationCoordinates, + comment: newComment, }, }, - annotationCoordinates, - comment: newComment, - }); + ); findDiscussionForm().vm.$emit('cancelForm'); @@ -238,15 +246,15 @@ describe('Design management design index page', () => { describe('with error', () => { beforeEach(() => { - createComponent(false, { - design: { - ...design, - discussions: { - nodes: [], + createComponent( + { loading: false }, + { + data: { + design: mockDesignNoDiscussions, + errorMessage: 'woops', }, }, - errorMessage: 'woops', - }); + ); }); it('GlAlert is rendered in correct position with correct content', () => { @@ -257,7 +265,7 @@ describe('Design management design index page', () => { describe('onDesignQueryResult', () => { describe('with no designs', () => { it('redirects to /designs', () => { - createComponent(true); + createComponent({ loading: true }); router.push = jest.fn(); wrapper.vm.onDesignQueryResult({ data: mockResponseNoDesigns, loading: false }); @@ -272,7 +280,7 @@ describe('Design management design index page', () => { describe('when no design exists for given version', () => { it('redirects to /designs', () => { - createComponent(true); + createComponent({ loading: true }); wrapper.setData({ allVersions: mockAllVersions, }); @@ -291,4 +299,24 @@ describe('Design management design index page', () => { }); }); }); + + describe('when hash present in current route', () => { + it('calls updateActiveDiscussion mutation', () => { + createComponent( + { loading: false }, + { + data: { + design, + }, + intialRouteOptions: { hash: '#note_123' }, + }, + ); + + expect(mutate).toHaveBeenCalledTimes(1); + expect(mutate).toHaveBeenCalledWith({ + mutation: updateActiveDiscussion, + variables: { id: 'gid://gitlab/DiffNote/123', source: 'url' }, + }); + }); + }); }); diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js index 4728648b49c..2da02732b1e 100644 --- a/spec/frontend/design_management/pages/index_spec.js +++ b/spec/frontend/design_management/pages/index_spec.js @@ -579,7 +579,9 @@ describe('Design management index page', () => { }); createComponent(true); - expect(scrollIntoViewMock).toHaveBeenCalled(); + return wrapper.vm.$nextTick().then(() => { + expect(scrollIntoViewMock).toHaveBeenCalled(); + }); }); }); }); diff --git a/spec/frontend/design_management/utils/design_management_utils_spec.js b/spec/frontend/design_management/utils/design_management_utils_spec.js index e6d836b9157..7e857d08d25 100644 --- a/spec/frontend/design_management/utils/design_management_utils_spec.js +++ b/spec/frontend/design_management/utils/design_management_utils_spec.js @@ -6,6 +6,7 @@ import { updateImageDiffNoteOptimisticResponse, isValidDesignFile, extractDesign, + extractDesignNoteId, } from '~/design_management/utils/design_management_utils'; import mockResponseNoDesigns from '../mock_data/no_designs'; import mockResponseWithDesigns from '../mock_data/designs'; @@ -171,3 +172,19 @@ describe('extractDesign', () => { }); }); }); + +describe('extractDesignNoteId', () => { + it.each` + hash | expectedNoteId + ${'#note_0'} | ${'0'} + ${'#note_1'} | ${'1'} + ${'#note_23'} | ${'23'} + ${'#note_456'} | ${'456'} + ${'note_1'} | ${null} + ${'#note_'} | ${null} + ${'#note_asd'} | ${null} + ${'#note_1asd'} | ${null} + `('returns $expectedNoteId when hash is $hash', ({ hash, expectedNoteId }) => { + expect(extractDesignNoteId(hash)).toBe(expectedNoteId); + }); +}); diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 21ff0a94db9..d34244771ad 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -367,10 +367,31 @@ RSpec.describe API::Commits do end end - it 'does not increment the usage counters using access token authentication' do - expect(::Gitlab::UsageDataCounters::WebIdeCounter).not_to receive(:increment_commits_count) + context 'when using access token authentication' do + it 'does not increment the usage counters' do + expect(::Gitlab::UsageDataCounters::WebIdeCounter).not_to receive(:increment_commits_count) + expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_web_ide_edit_action) - post api(url, user), params: valid_c_params + post api(url, user), params: valid_c_params + end + end + + context 'when using warden' do + it 'increments usage counters', :clean_gitlab_redis_shared_state do + session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') + session_hash = { 'warden.user.user.key' => [[user.id], user.encrypted_password[0, 29]] } + + Gitlab::Redis::SharedState.with do |redis| + redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) + end + + cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id + + expect(::Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_commits_count) + expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_web_ide_edit_action) + + post api(url), params: valid_c_params + end end context 'a new file in project repo' do diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb index f5971054b3c..23d5df873d4 100644 --- a/spec/requests/api/oauth_tokens_spec.rb +++ b/spec/requests/api/oauth_tokens_spec.rb @@ -20,7 +20,7 @@ RSpec.describe 'OAuth tokens' do request_oauth_token(user, client_basic_auth_header(client)) - expect(response).to have_gitlab_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('invalid_grant') end end @@ -62,7 +62,7 @@ RSpec.describe 'OAuth tokens' do request_oauth_token(user, basic_auth_header(client.uid, 'invalid secret')) - expect(response).to have_gitlab_http_status(:unauthorized) + expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['error']).to eq('invalid_client') end end @@ -72,7 +72,7 @@ RSpec.describe 'OAuth tokens' do shared_examples 'does not create an access token' do let(:user) { create(:user) } - it { expect(response).to have_gitlab_http_status(:unauthorized) } + it { expect(response).to have_gitlab_http_status(:bad_request) } end context 'when user is blocked' do |