Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/CODEOWNERS4
-rw-r--r--app/assets/javascripts/artifacts/components/job_artifacts_table.vue49
-rw-r--r--app/assets/javascripts/artifacts/graphql/cache_update.js42
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js4
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue2
-rw-r--r--app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql7
-rw-r--r--doc/user/discussions/index.md4
-rw-r--r--doc/user/packages/workflows/build_packages.md504
-rw-r--r--doc/user/project/merge_requests/approvals/index.md2
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/badges.rb9
-rw-r--r--locale/gitlab.pot4
-rw-r--r--spec/frontend/artifacts/components/job_artifacts_table_spec.js96
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/view/index_spec.js11
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/view/mock_data.js8
15 files changed, 677 insertions, 71 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index a67374a2bb6..915fa91d873 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -155,9 +155,7 @@ Dangerfile @gl-quality/eng-prod
/app/assets/javascripts/ci_variable_list/ @gitlab-org/ci-cd/verify/frontend
/app/assets/javascripts/ci/pipeline_schedules/ @gitlab-org/ci-cd/verify/frontend
/app/assets/javascripts/pipeline_editor/ @gitlab-org/ci-cd/verify/frontend
-/ee/app/assets/javascripts/ci/ci_minutes_usage/ @gitlab-org/ci-cd/verify/frontend
-/ee/app/assets/javascripts/ci/usage_quotas/ci_minutes_usage/ @gitlab-org/ci-cd/verify/frontend
-/ee/app/assets/javascripts/usage_quotas/pipelines/ @gitlab-org/ci-cd/verify/frontend
+/ee/app/assets/javascripts/ci/ @gitlab-org/ci-cd/verify/frontend
/ee/app/assets/javascripts/reports/ @gitlab-org/ci-cd/verify/frontend
^[Templates]
diff --git a/app/assets/javascripts/artifacts/components/job_artifacts_table.vue b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue
index 1bbc38138e6..34e443f4e58 100644
--- a/app/assets/javascripts/artifacts/components/job_artifacts_table.vue
+++ b/app/assets/javascripts/artifacts/components/job_artifacts_table.vue
@@ -67,11 +67,19 @@ export default {
return this.queryVariables;
},
update({ project: { jobs: { nodes = [], pageInfo = {}, count = 0 } = {} } }) {
- return {
- nodes: nodes.map(mapArchivesToJobNodes).map(mapBooleansToJobNodes),
- count,
- pageInfo,
- };
+ this.pageInfo = pageInfo;
+ this.count = count;
+ return nodes
+ .map(mapArchivesToJobNodes)
+ .map(mapBooleansToJobNodes)
+ .map((jobNode) => {
+ return {
+ ...jobNode,
+ // GlTable uses an item's _showDetails attribute to determine whether
+ // it should show the <template #row-details /> for its table row
+ _showDetails: this.expandedJobs.includes(jobNode.id),
+ };
+ });
},
error() {
createAlert({
@@ -82,11 +90,10 @@ export default {
},
data() {
return {
- jobArtifacts: {
- nodes: [],
- count: 0,
- pageInfo: {},
- },
+ jobArtifacts: [],
+ count: 0,
+ pageInfo: {},
+ expandedJobs: [],
pagination: INITIAL_PAGINATION_STATE,
};
},
@@ -101,13 +108,13 @@ export default {
};
},
showPagination() {
- return this.jobArtifacts.count > JOBS_PER_PAGE;
+ return this.count > JOBS_PER_PAGE;
},
prevPage() {
- return Number(this.jobArtifacts.pageInfo.hasPreviousPage);
+ return Number(this.pageInfo.hasPreviousPage);
},
nextPage() {
- return Number(this.jobArtifacts.pageInfo.hasNextPage);
+ return Number(this.pageInfo.hasNextPage);
},
},
methods: {
@@ -122,7 +129,7 @@ export default {
return `#${id}`;
},
handlePageChange(page) {
- const { startCursor, endCursor } = this.jobArtifacts.pageInfo;
+ const { startCursor, endCursor } = this.pageInfo;
if (page > this.pagination.currentPage) {
this.pagination = {
@@ -139,9 +146,15 @@ export default {
};
}
},
- handleRowToggle(toggleDetails, hasArtifacts) {
+ handleRowToggle(toggleDetails, hasArtifacts, id, detailsShowing) {
if (!hasArtifacts) return;
toggleDetails();
+
+ if (!detailsShowing) {
+ this.expandedJobs.push(id);
+ } else {
+ this.expandedJobs.splice(this.expandedJobs.indexOf(id), 1);
+ }
},
downloadPath(job) {
return job.archive?.downloadPath;
@@ -202,7 +215,7 @@ export default {
<template>
<div>
<gl-table
- :items="jobArtifacts.nodes"
+ :items="jobArtifacts"
:fields="$options.fields"
:busy="$apollo.queries.jobArtifacts.loading"
stacked="sm"
@@ -212,12 +225,12 @@ export default {
<gl-loading-icon size="lg" />
</template>
<template
- #cell(artifacts)="{ item: { artifacts, hasArtifacts }, toggleDetails, detailsShowing }"
+ #cell(artifacts)="{ item: { id, artifacts, hasArtifacts }, toggleDetails, detailsShowing }"
>
<span
:class="{ 'gl-cursor-pointer': hasArtifacts }"
data-testid="job-artifacts-count"
- @click="handleRowToggle(toggleDetails, hasArtifacts)"
+ @click="handleRowToggle(toggleDetails, hasArtifacts, id, detailsShowing)"
>
<gl-icon
v-if="hasArtifacts"
diff --git a/app/assets/javascripts/artifacts/graphql/cache_update.js b/app/assets/javascripts/artifacts/graphql/cache_update.js
index c620e03c80d..9fa6114c7d4 100644
--- a/app/assets/javascripts/artifacts/graphql/cache_update.js
+++ b/app/assets/javascripts/artifacts/graphql/cache_update.js
@@ -3,28 +3,28 @@ import produce from 'immer';
export const hasErrors = ({ errors = [] }) => errors?.length;
export function removeArtifactFromStore(store, deletedArtifactId, query, variables) {
- if (!hasErrors(deletedArtifactId)) {
- const sourceData = store.readQuery({
- query,
- variables,
- });
+ if (hasErrors(deletedArtifactId)) return;
- const data = produce(sourceData, (draftData) => {
- draftData.project.jobs.nodes = draftData.project.jobs.nodes.map((jobNode) => {
- return {
- ...jobNode,
- artifacts: {
- ...jobNode.artifacts,
- nodes: jobNode.artifacts.nodes.filter(({ id }) => id !== deletedArtifactId),
- },
- };
- });
- });
+ const sourceData = store.readQuery({
+ query,
+ variables,
+ });
- store.writeQuery({
- query,
- variables,
- data,
+ const data = produce(sourceData, (draftData) => {
+ draftData.project.jobs.nodes = draftData.project.jobs.nodes.map((jobNode) => {
+ return {
+ ...jobNode,
+ artifacts: {
+ ...jobNode.artifacts,
+ nodes: jobNode.artifacts.nodes.filter(({ id }) => id !== deletedArtifactId),
+ },
+ };
});
- }
+ });
+
+ store.writeQuery({
+ query,
+ variables,
+ data,
+ });
}
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
index 9def0bdc3e5..6da058ebc9c 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js
@@ -21,11 +21,11 @@ export const I18N = {
approvalsTitle: s__('BranchRules|Approvals'),
manageApprovalsLinkTitle: s__('BranchRules|Manage in Merge Request Approvals'),
approvalsDescription: s__(
- 'BranchRules|Approvals to ensure separation of duties for new merge requests. %{linkStart}Lean more.%{linkEnd}',
+ 'BranchRules|Approvals to ensure separation of duties for new merge requests. %{linkStart}Learn more.%{linkEnd}',
),
statusChecksTitle: s__('BranchRules|Status checks'),
statusChecksDescription: s__(
- 'BranchRules|Check for a status response in merge requests. Failures do not block merges. %{linkStart}Lean more.%{linkEnd}',
+ 'BranchRules|Check for a status response in merge requests. Failures do not block merges. %{linkStart}Learn more.%{linkEnd}',
),
statusChecksLinkTitle: s__('BranchRules|Manage in Status checks'),
statusChecksHeader: s__('BranchRules|Status checks (%{total})'),
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
index 38bffb886e0..eb11e17dd1b 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
+++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue
@@ -54,7 +54,7 @@ export default {
const branchRule = branchRules.nodes.find((rule) => rule.name === this.branch);
this.branchProtection = branchRule?.branchProtection;
this.approvalRules = branchRule?.approvalRules;
- this.statusChecks = branchRule?.externalStatusChecks || [];
+ this.statusChecks = branchRule?.externalStatusChecks?.nodes || [];
},
},
},
diff --git a/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql b/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
index 4ca474a5ceb..aa1e4923aa8 100644
--- a/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
+++ b/app/assets/javascripts/projects/settings/branch_rules/queries/branch_rules_details.query.graphql
@@ -61,6 +61,13 @@ query getBranchRulesDetails($projectPath: ID!) {
}
}
}
+ externalStatusChecks {
+ nodes {
+ id
+ name
+ externalUrl
+ }
+ }
}
}
}
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 106e4871736..d9cacb6395d 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -78,7 +78,7 @@ To add a commit diff comment:
You can select multiple lines by dragging the **Comment** (**{comment}**) icon.
1. Enter your comment and select **Start a review** or **Add comment now**.
-The comment is displayed on the merge request's **Discussions** tab.
+The comment is displayed on the merge request's **Overview** tab.
The comment is not displayed on your project's **Repository > Commits** page.
@@ -200,7 +200,7 @@ You can also mark an [issue as confidential](../project/issues/confidential_issu
For issues and merge requests with many comments, you can filter the page to show comments only.
-1. Open a merge request's **Discussion** tab, or epic or issue's **Overview** tab.
+1. Open the **Overview** tab in a merge request, issue, or epic.
1. On the right side of the page, select from the filter:
- **Show all activity**: Display all user comments and system notes.
(issue updates, mentions from other issues, changes to the description, and so on).
diff --git a/doc/user/packages/workflows/build_packages.md b/doc/user/packages/workflows/build_packages.md
new file mode 100644
index 00000000000..063217f0ad7
--- /dev/null
+++ b/doc/user/packages/workflows/build_packages.md
@@ -0,0 +1,504 @@
+---
+stage: Package
+group: Package Registry
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Build packages
+
+Learn how to install and build packages different package formats.
+
+- [Composer](#composer)
+- [Conan](#conan)
+- [Maven](#maven)
+- [Gradle](#gradle)
+- [npm](#npm)
+- [Yarn](#yarn)
+- [NuGet](#nuget)
+- [PyPI](#pypi)
+
+## Composer
+
+1. Create a directory called `my-composer-package` and change to that directory:
+
+ ```shell
+ mkdir my-composer-package && cd my-composer-package
+ ```
+
+1. Run [`composer init`](https://getcomposer.org/doc/03-cli.md#init) and answer the prompts.
+
+ For namespace, enter your unique [namespace](../../../user/namespace/index.md), like your GitLab username or group name.
+
+ A file called `composer.json` is created:
+
+ ```json
+ {
+ "name": "<namespace>/composer-test",
+ "description": "Library XY",
+ "type": "library",
+ "license": "GPL-3.0-only",
+ "authors": [
+ {
+ "name": "John Doe",
+ "email": "john@example.com"
+ }
+ ],
+ "require": {}
+ }
+ ```
+
+## Conan
+
+### Install Conan
+
+Download the Conan package manager to your local development environment by
+following the instructions at [conan.io](https://conan.io/downloads.html).
+
+When installation is complete, verify you can use Conan in your terminal by
+running:
+
+```shell
+conan --version
+```
+
+The Conan version is printed in the output:
+
+```plaintext
+Conan version 1.20.5
+```
+
+### Install CMake
+
+When you develop with C++ and Conan, you can select from many available
+compilers. This example uses the CMake build system generator.
+
+To install CMake:
+
+- For Mac, use [Homebrew](https://brew.sh/) and run `brew install cmake`.
+- For other operating systems, follow the instructions at [cmake.org](https://cmake.org/install/).
+
+When installation is complete, verify you can use CMake in your terminal by
+running:
+
+```shell
+cmake --version
+```
+
+The CMake version is printed in the output.
+
+### Create a project
+
+To test the Package Registry, you need a C++ project. If you don't already have
+one, you can clone the Conan [hello world starter project](https://github.com/conan-io/hello).
+
+### Build a Conan package
+
+To build a package:
+
+1. Open a terminal and navigate to your project's root folder.
+1. Generate a new recipe by running `conan new` with a package name and version:
+
+ ```shell
+ conan new Hello/0.1 -t
+ ```
+
+1. Create a package for the recipe by running `conan create` with the Conan user
+ and channel:
+
+ ```shell
+ conan create . mycompany/beta
+ ```
+
+ NOTE:
+ If you use an [instance remote](../conan_repository/index.md#add-a-remote-for-your-instance), you must
+ follow a specific [naming convention](../conan_repository/index.md#package-recipe-naming-convention-for-instance-remotes).
+
+A package with the recipe `Hello/0.1@mycompany/beta` is created.
+
+For more details about creating and managing Conan packages, see the
+[Conan documentation](https://docs.conan.io/en/latest/creating_packages.html).
+
+## Maven
+
+### Install Maven
+
+The required minimum versions are:
+
+- Java 11.0.5+
+- Maven 3.6+
+
+Follow the instructions at [maven.apache.org](https://maven.apache.org/install.html)
+to download and install Maven for your local development environment. After
+installation is complete, verify you can use Maven in your terminal by running:
+
+```shell
+mvn --version
+```
+
+The output should be similar to:
+
+```shell
+Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-04T20:00:29+01:00)
+Maven home: /Users/<your_user>/apache-maven-3.6.1
+Java version: 12.0.2, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk-12.0.2.jdk/Contents/Home
+Default locale: en_GB, platform encoding: UTF-8
+OS name: "mac os x", version: "10.15.2", arch: "x86_64", family: "mac"
+```
+
+### Build a Maven package
+
+1. Open your terminal and create a directory to store the project.
+1. From the new directory, run this Maven command to initialize a new package:
+
+ ```shell
+ mvn archetype:generate -DgroupId=com.mycompany.mydepartment -DartifactId=my-project -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
+ ```
+
+ The arguments are:
+
+ - `DgroupId`: A unique string that identifies your package. Follow
+ the [Maven naming conventions](https://maven.apache.org/guides/mini/guide-naming-conventions.html).
+ - `DartifactId`: The name of the `JAR`, appended to the end of the `DgroupId`.
+ - `DarchetypeArtifactId`: The archetype used to create the initial structure of
+ the project.
+ - `DinteractiveMode`: Create the project using batch mode (optional).
+
+This message indicates that the project was set up successfully:
+
+```shell
+...
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 3.429 s
+[INFO] Finished at: 2020-01-28T11:47:04Z
+[INFO] ------------------------------------------------------------------------
+```
+
+In the folder where you ran the command, a new directory should be displayed.
+The directory name should match the `DartifactId` parameter, which in this case,
+is `my-project`.
+
+## Gradle
+
+### Install Gradle
+
+If you want to create a new Gradle project, you must install Gradle. Follow
+instructions at [gradle.org](https://gradle.org/install/) to download and install
+Gradle for your local development environment.
+
+In your terminal, verify you can use Gradle by running:
+
+```shell
+gradle -version
+```
+
+To use an existing Gradle project, in the project directory,
+on Linux execute `gradlew`, or on Windows execute `gradlew.bat`.
+
+The output should be similar to:
+
+```plaintext
+------------------------------------------------------------
+Gradle 6.0.1
+------------------------------------------------------------
+
+Build time: 2019-11-18 20:25:01 UTC
+Revision: fad121066a68c4701acd362daf4287a7c309a0f5
+
+Kotlin: 1.3.50
+Groovy: 2.5.8
+Ant: Apache Ant(TM) version 1.10.7 compiled on September 1 2019
+JVM: 11.0.5 (Oracle Corporation 11.0.5+10)
+OS: Windows 10 10.0 amd64
+```
+
+### Create a package
+
+1. Open your terminal and create a directory to store the project.
+1. From this new directory, run this command to initialize a new package:
+
+ ```shell
+ gradle init
+ ```
+
+ The output should be:
+
+ ```plaintext
+ Select type of project to generate:
+ 1: basic
+ 2: application
+ 3: library
+ 4: Gradle plugin
+ Enter selection (default: basic) [1..4]
+ ```
+
+1. Enter `3` to create a new Library project. The output should be:
+
+ ```plaintext
+ Select implementation language:
+ 1: C++
+ 2: Groovy
+ 3: Java
+ 4: Kotlin
+ 5: Scala
+ 6: Swift
+ ```
+
+1. Enter `3` to create a new Java Library project. The output should be:
+
+ ```plaintext
+ Select build script DSL:
+ 1: Groovy
+ 2: Kotlin
+ Enter selection (default: Groovy) [1..2]
+ ```
+
+1. Enter `1` to create a new Java Library project that is described in Groovy DSL, or `2` to create one that is described in Kotlin DSL. The output should be:
+
+ ```plaintext
+ Select test framework:
+ 1: JUnit 4
+ 2: TestNG
+ 3: Spock
+ 4: JUnit Jupiter
+ ```
+
+1. Enter `1` to initialize the project with JUnit 4 testing libraries. The output should be:
+
+ ```plaintext
+ Project name (default: test):
+ ```
+
+1. Enter a project name or press <kbd>Enter</kbd> to use the directory name as project name.
+
+## npm
+
+### Install npm
+
+Install Node.js and npm in your local development environment by following
+the instructions at [npmjs.com](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm/).
+
+When installation is complete, verify you can use npm in your terminal by
+running:
+
+```shell
+npm --version
+```
+
+The npm version is shown in the output:
+
+```plaintext
+6.10.3
+```
+
+### Create an npm package
+
+1. Create an empty directory.
+1. Go to the directory and initialize an empty package by running:
+
+ ```shell
+ npm init
+ ```
+
+1. Enter responses to the questions. Ensure the **package name** follows
+ the [naming convention](../npm_registry/index.md#package-naming-convention) and is scoped to the project or group where the registry exists.
+
+## Yarn
+
+### Install Yarn
+
+As an alternative to npm, you can install Yarn in your local environment by following the
+instructions at [classic.yarnpkg.com](https://classic.yarnpkg.com/en/docs/install).
+
+When installation is complete, verify you can use Yarn in your terminal by
+running:
+
+```shell
+yarn --version
+```
+
+The Yarn version is shown in the output:
+
+```plaintext
+1.19.1
+```
+
+### Create a package
+
+1. Create an empty directory.
+1. Go to the directory and initialize an empty package by running:
+
+ ```shell
+ yarn init
+ ```
+
+1. Enter responses to the questions. Ensure the **package name** follows
+ the [naming convention](../npm_registry/index.md#package-naming-convention) and is scoped to the
+ project or group where the registry exists.
+
+A `package.json` file is created.
+
+## NuGet
+
+### Install NuGet
+
+Follow the instructions from [https://learn.microsoft.com/en-us/nuget/install-nuget-client-tools] Microsoft to install NuGet. If you have
+[Visual Studio](https://visualstudio.microsoft.com/vs/), NuGet is
+probably already installed.
+
+Verify that the [NuGet CLI](https://www.nuget.org/) is installed by running:
+
+```shell
+nuget help
+```
+
+The output should be similar to:
+
+```plaintext
+NuGet Version: 5.1.0.6013
+usage: NuGet <command> [args] [options]
+Type 'NuGet help <command>' for help on a specific command.
+
+Available commands:
+
+[output truncated]
+```
+
+## PyPI
+
+### Install pip and twine
+
+Install a recent version of [pip](https://pypi.org/project/pip/) and
+[twine](https://pypi.org/project/twine/).
+
+### Create a project
+
+Create a test project.
+
+1. Open your terminal.
+1. Create a directory called `MyPyPiPackage`, and then go to that directory:
+
+ ```shell
+ mkdir MyPyPiPackage && cd MyPyPiPackage
+ ```
+
+1. Create another directory and go to it:
+
+ ```shell
+ mkdir mypypipackage && cd mypypipackage
+ ```
+
+1. Create the required files in this directory:
+
+ ```shell
+ touch __init__.py
+ touch greet.py
+ ```
+
+1. Open the `greet.py` file, and then add:
+
+ ```python
+ def SayHello():
+ print("Hello from MyPyPiPackage")
+ return
+ ```
+
+1. Open the `__init__.py` file, and then add:
+
+ ```python
+ from .greet import SayHello
+ ```
+
+1. To test the code, in your `MyPyPiPackage` directory, start the Python prompt.
+
+ ```shell
+ python
+ ```
+
+1. Run this command:
+
+ ```python
+ >>> from mypypipackage import SayHello
+ >>> SayHello()
+ ```
+
+A message indicates that the project was set up successfully:
+
+```plaintext
+Python 3.8.2 (v3.8.2:7b3ab5921f, Feb 24 2020, 17:52:18)
+[Clang 6.0 (clang-600.0.57)] on darwin
+Type "help", "copyright", "credits" or "license" for more information.
+>>> from mypypipackage import SayHello
+>>> SayHello()
+Hello from MyPyPiPackage
+```
+
+### Create a PyPI package
+
+After you create a project, you can create a package.
+
+1. In your terminal, go to the `MyPyPiPackage` directory.
+1. Create a `pyproject.toml` file:
+
+ ```shell
+ touch pyproject.toml
+ ```
+
+ This file contains all the information about the package. For more information
+ about this file, see [creating `pyproject.toml`](https://packaging.python.org/en/latest/tutorials/packaging-projects/#creating-pyproject-toml).
+ Because GitLab identifies packages based on
+ [Python normalized names (PEP-503)](https://www.python.org/dev/peps/pep-0503/#normalized-names),
+ ensure your package name meets these requirements. See the [installation section](../pypi_repository/index.md#authenticate-with-a-ci-job-token)
+ for details.
+
+1. Open the `pyproject.toml` file, and then add basic information:
+
+ ```toml
+ [build-system]
+ requires = ["setuptools>=61.0"]
+ build-backend = "setuptools.build_meta"
+
+ [project]
+ name = "mypypipackage"
+ version = "0.0.1"
+ authors = [
+ { name="Example Author", email="author@example.com" },
+ ]
+ description = "A small example package"
+ requires-python = ">=3.7"
+ classifiers = [
+ "Programming Language :: Python :: 3",
+ "Operating System :: OS Independent",
+ ]
+
+ [tool.setuptools.packages]
+ find = {}
+ ```
+
+1. Save the file.
+1. Install the package build library:
+
+ ```shell
+ pip install build
+ ```
+
+1. Build the package:
+
+ ```shell
+ python -m build
+ ```
+
+The output should be visible in a newly-created `dist` folder:
+
+```shell
+ls dist
+```
+
+The output should appear similar to the following:
+
+```plaintext
+mypypipackage-0.0.1-py3-none-any.whl mypypipackage-0.0.1.tar.gz
+```
+
+The package is now ready to be published to the Package Registry.
diff --git a/doc/user/project/merge_requests/approvals/index.md b/doc/user/project/merge_requests/approvals/index.md
index ea03427161e..eb460225858 100644
--- a/doc/user/project/merge_requests/approvals/index.md
+++ b/doc/user/project/merge_requests/approvals/index.md
@@ -68,7 +68,7 @@ if a user approves a merge request and is shown in the reviewer list, a green ch
After a merge request receives the [number and type of approvals](rules.md) you configure, it can merge
unless it's blocked for another reason. Merge requests can be blocked by other problems,
-such as merge conflicts, [pending discussions](../../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved),
+such as merge conflicts, [unresolved threads](../../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved),
or a [failed CI/CD pipeline](../merge_when_pipeline_succeeds.md).
To prevent merge request authors from approving their own merge requests,
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 04114d38b33..cdd0f90355a 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -175,6 +175,7 @@ module API
mount ::API::Admin::InstanceClusters
mount ::API::Appearance
mount ::API::Applications
+ mount ::API::Badges
mount ::API::BroadcastMessages
mount ::API::BulkImports
mount ::API::Ci::Jobs
@@ -242,7 +243,6 @@ module API
mount ::API::AlertManagementAlerts
mount ::API::Avatar
mount ::API::AwardEmoji
- mount ::API::Badges
mount ::API::Boards
mount ::API::Branches
mount ::API::Ci::JobArtifacts
diff --git a/lib/api/badges.rb b/lib/api/badges.rb
index 0a3f247ffd6..020ba53b9ee 100644
--- a/lib/api/badges.rb
+++ b/lib/api/badges.rb
@@ -28,6 +28,8 @@ module API
desc "Gets a list of #{source_type} badges viewable by the authenticated user." do
detail 'This feature was introduced in GitLab 10.6.'
success Entities::Badge
+ is_array true
+ tags %w[badges]
end
params do
use :pagination
@@ -46,6 +48,7 @@ module API
desc "Preview a badge from a #{source_type}." do
detail 'This feature was introduced in GitLab 10.6.'
success Entities::BasicBadgeDetails
+ tags %w[badges]
end
params do
requires :link_url, type: String, desc: 'URL of the badge link'
@@ -69,6 +72,7 @@ module API
desc "Gets a badge of a #{source_type}." do
detail 'This feature was introduced in GitLab 10.6.'
success Entities::Badge
+ tags %w[badges]
end
params do
requires :badge_id, type: Integer, desc: 'The badge ID'
@@ -86,6 +90,7 @@ module API
desc "Adds a badge to a #{source_type}." do
detail 'This feature was introduced in GitLab 10.6.'
success Entities::Badge
+ tags %w[badges]
end
params do
requires :link_url, type: String, desc: 'URL of the badge link'
@@ -107,6 +112,7 @@ module API
desc "Updates a badge of a #{source_type}." do
detail 'This feature was introduced in GitLab 10.6.'
success Entities::Badge
+ tags %w[badges]
end
params do
optional :link_url, type: String, desc: 'URL of the badge link'
@@ -127,8 +133,9 @@ module API
end
end
- desc 'Removes a badge from a project or group.' do
+ desc "Removes a badge from the #{source_type}." do
detail 'This feature was introduced in GitLab 10.6.'
+ tags %w[badges]
end
params do
requires :badge_id, type: Integer, desc: 'The badge ID'
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ddbd3220077..7326c9f81df 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6932,7 +6932,7 @@ msgstr ""
msgid "BranchRules|Approvals"
msgstr ""
-msgid "BranchRules|Approvals to ensure separation of duties for new merge requests. %{linkStart}Lean more.%{linkEnd}"
+msgid "BranchRules|Approvals to ensure separation of duties for new merge requests. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "BranchRules|Branch"
@@ -6944,7 +6944,7 @@ msgstr ""
msgid "BranchRules|Branch rules details"
msgstr ""
-msgid "BranchRules|Check for a status response in merge requests. Failures do not block merges. %{linkStart}Lean more.%{linkEnd}"
+msgid "BranchRules|Check for a status response in merge requests. Failures do not block merges. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "BranchRules|Create wildcard: %{searchTerm}"
diff --git a/spec/frontend/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
index 8f3d1f9bf92..131b4b99bb2 100644
--- a/spec/frontend/artifacts/components/job_artifacts_table_spec.js
+++ b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
@@ -1,10 +1,12 @@
-import { GlLoadingIcon, GlTable, GlLink, GlBadge, GlPagination } from '@gitlab/ui';
+import { GlLoadingIcon, GlTable, GlLink, GlBadge, GlPagination, GlModal } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import getJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import waitForPromises from 'helpers/wait_for_promises';
import JobArtifactsTable from '~/artifacts/components/job_artifacts_table.vue';
+import ArtifactsTableRowDetails from '~/artifacts/components/artifacts_table_row_details.vue';
+import ArtifactDeleteModal from '~/artifacts/components/artifact_delete_modal.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import getJobArtifactsQuery from '~/artifacts/graphql/queries/get_job_artifacts.query.graphql';
@@ -23,7 +25,14 @@ describe('JobArtifactsTable component', () => {
const findLoadingState = () => wrapper.findComponent(GlLoadingIcon);
const findTable = () => wrapper.findComponent(GlTable);
+ const findDetailsRows = () => wrapper.findAllComponents(ArtifactsTableRowDetails);
+ const findDetailsInRow = (i) =>
+ findTable().findAll('tbody tr').at(i).findComponent(ArtifactsTableRowDetails);
+
const findCount = () => wrapper.findByTestId('job-artifacts-count');
+ const findCountAt = (i) => wrapper.findAllByTestId('job-artifacts-count').at(i);
+
+ const findModal = () => wrapper.findComponent(GlModal);
const findStatuses = () => wrapper.findAllByTestId('job-artifacts-job-status');
const findSuccessfulJobStatus = () => findStatuses().at(0);
@@ -41,6 +50,7 @@ describe('JobArtifactsTable component', () => {
const findDownloadButton = () => wrapper.findByTestId('job-artifacts-download-button');
const findBrowseButton = () => wrapper.findByTestId('job-artifacts-browse-button');
const findDeleteButton = () => wrapper.findByTestId('job-artifacts-delete-button');
+ const findArtifactDeleteButton = () => wrapper.findByTestId('job-artifact-row-delete-button');
const findPagination = () => wrapper.findComponent(GlPagination);
const setPage = async (page) => {
@@ -121,14 +131,6 @@ describe('JobArtifactsTable component', () => {
expect(findCount().text()).toBe(`${job.artifacts.nodes.length} files`);
});
- it('expands to show the list of artifacts', async () => {
- jest.spyOn(wrapper.vm, 'handleRowToggle');
-
- findCount().trigger('click');
-
- expect(wrapper.vm.handleRowToggle).toHaveBeenCalled();
- });
-
it('shows the job status as an icon for a successful job', () => {
expect(findSuccessfulJobStatus().findComponent(CiIcon).exists()).toBe(true);
expect(findSuccessfulJobStatus().findComponent(GlBadge).exists()).toBe(false);
@@ -160,6 +162,72 @@ describe('JobArtifactsTable component', () => {
it('shows the created time', () => {
expect(findCreated().text()).toBe('5 years ago');
});
+
+ describe('row expansion', () => {
+ it('toggles the visibility of the row details', async () => {
+ expect(findDetailsRows().length).toBe(0);
+
+ findCount().trigger('click');
+ await waitForPromises();
+
+ expect(findDetailsRows().length).toBe(1);
+
+ findCount().trigger('click');
+ await waitForPromises();
+
+ expect(findDetailsRows().length).toBe(0);
+ });
+
+ it('expands and collapses jobs', async () => {
+ // both jobs start collapsed
+ expect(findDetailsInRow(0).exists()).toBe(false);
+ expect(findDetailsInRow(1).exists()).toBe(false);
+
+ findCountAt(0).trigger('click');
+ await waitForPromises();
+
+ // first job is expanded, second row has its details
+ expect(findDetailsInRow(0).exists()).toBe(false);
+ expect(findDetailsInRow(1).exists()).toBe(true);
+ expect(findDetailsInRow(2).exists()).toBe(false);
+
+ findCountAt(1).trigger('click');
+ await waitForPromises();
+
+ // both jobs are expanded, each has details below it
+ expect(findDetailsInRow(0).exists()).toBe(false);
+ expect(findDetailsInRow(1).exists()).toBe(true);
+ expect(findDetailsInRow(2).exists()).toBe(false);
+ expect(findDetailsInRow(3).exists()).toBe(true);
+
+ findCountAt(0).trigger('click');
+ await waitForPromises();
+
+ // first job collapsed, second job expanded
+ expect(findDetailsInRow(0).exists()).toBe(false);
+ expect(findDetailsInRow(1).exists()).toBe(false);
+ expect(findDetailsInRow(2).exists()).toBe(true);
+ });
+
+ it('keeps the job expanded when an artifact is deleted', async () => {
+ findCount().trigger('click');
+ await waitForPromises();
+
+ expect(findDetailsInRow(0).exists()).toBe(false);
+ expect(findDetailsInRow(1).exists()).toBe(true);
+
+ findArtifactDeleteButton().trigger('click');
+ await waitForPromises();
+
+ expect(findModal().props('visible')).toBe(true);
+
+ wrapper.findComponent(ArtifactDeleteModal).vm.$emit('primary');
+ await waitForPromises();
+
+ expect(findDetailsInRow(0).exists()).toBe(false);
+ expect(findDetailsInRow(1).exists()).toBe(true);
+ });
+ });
});
describe('download button', () => {
@@ -179,7 +247,7 @@ describe('JobArtifactsTable component', () => {
createComponent(
{ getJobArtifactsQuery: jest.fn() },
- { jobArtifacts: { nodes: [jobWithoutDownloadPath] } },
+ { jobArtifacts: [jobWithoutDownloadPath] },
);
await waitForPromises();
@@ -205,7 +273,7 @@ describe('JobArtifactsTable component', () => {
createComponent(
{ getJobArtifactsQuery: jest.fn() },
- { jobArtifacts: { nodes: [jobWithoutBrowsePath] } },
+ { jobArtifacts: [jobWithoutBrowsePath] },
);
await waitForPromises();
@@ -233,10 +301,8 @@ describe('JobArtifactsTable component', () => {
getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponseThatPaginates),
},
{
- jobArtifacts: {
- count: enoughJobsToPaginate.length,
- pageInfo,
- },
+ count: enoughJobsToPaginate.length,
+ pageInfo,
},
);
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
index 875d3dafcc6..27065a704e2 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
@@ -12,7 +12,11 @@ import {
import Protection from '~/projects/settings/branch_rules/components/view/protection.vue';
import branchRulesQuery from '~/projects/settings/branch_rules/queries/branch_rules_details.query.graphql';
import { sprintf } from '~/locale';
-import { branchProtectionsMockResponse, approvalRulesMock } from './mock_data';
+import {
+ branchProtectionsMockResponse,
+ approvalRulesMock,
+ statusChecksRulesMock,
+} from './mock_data';
jest.mock('~/lib/utils/url_utility', () => ({
getParameterByName: jest.fn().mockReturnValue('main'),
@@ -118,11 +122,10 @@ describe('View branch rules', () => {
expect(findStatusChecksTitle().exists()).toBe(true);
expect(findBranchProtections().at(3).props()).toMatchObject({
- // status checks BE/FE integration will happen on a follow-up, so we expect data to be empty
- header: sprintf(I18N.statusChecksHeader, { total: 0 }),
+ header: sprintf(I18N.statusChecksHeader, { total: 2 }),
headerLinkHref: statusChecksPath,
headerLinkTitle: I18N.statusChecksLinkTitle,
- statusChecks: [],
+ statusChecks: statusChecksRulesMock,
});
});
});
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js b/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
index 326684a2bcc..c07d4673344 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/mock_data.js
@@ -137,6 +137,10 @@ export const branchProtectionsMockResponse = {
__typename: 'ApprovalProjectRuleConnection',
nodes: approvalRulesMock,
},
+ externalStatusChecks: {
+ __typename: 'ExternalStatusCheckConnection',
+ nodes: statusChecksRulesMock,
+ },
},
{
__typename: 'BranchRule',
@@ -158,6 +162,10 @@ export const branchProtectionsMockResponse = {
__typename: 'ApprovalProjectRuleConnection',
nodes: [],
},
+ externalStatusChecks: {
+ __typename: 'ExternalStatusCheckConnection',
+ nodes: [],
+ },
},
],
},