Commit afbaf78b authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@13-10-stable-ee

parent 38b3003b
<script>
import { GlIntersectionObserver } from '@gitlab/ui';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { humanize } from '~/lib/utils/text_utility';
import EmojiGroup from './emoji_group.vue';
export default {
......@@ -25,7 +25,7 @@ export default {
},
computed: {
categoryTitle() {
return capitalizeFirstCharacter(this.category);
return humanize(this.category);
},
},
methods: {
......@@ -33,9 +33,6 @@ export default {
this.renderGroup = true;
this.$emit('appear', this.category);
},
categoryDissappeared() {
this.renderGroup = false;
},
},
};
</script>
......
<script>
import { GlIcon, GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
import { findLastIndex } from 'lodash';
import VirtualList from 'vue-virtual-scroll-list';
import { CATEGORY_NAMES } from '~/emoji';
import { CATEGORY_ICON_MAP } from '../constants';
import { CATEGORY_ICON_MAP, FREQUENTLY_USED_KEY } from '../constants';
import Category from './category.vue';
import EmojiList from './emoji_list.vue';
import { getEmojiCategories } from './utils';
import { addToFrequentlyUsed, getEmojiCategories, hasFrequentlyUsedEmojis } from './utils';
export default {
components: {
......@@ -25,13 +26,16 @@ export default {
},
data() {
return {
currentCategory: null,
currentCategory: 0,
searchValue: '',
};
},
computed: {
categoryNames() {
return CATEGORY_NAMES.map((category) => ({
return CATEGORY_NAMES.filter((c) => {
if (c === FREQUENTLY_USED_KEY) return hasFrequentlyUsedEmojis();
return true;
}).map((category) => ({
name: category,
icon: CATEGORY_ICON_MAP[category],
}));
......@@ -50,6 +54,7 @@ export default {
selectEmoji(name) {
this.$emit('click', name);
this.$refs.dropdown.hide();
addToFrequentlyUsed(name);
},
getBoundaryElement() {
return document.querySelector('.content-wrapper') || 'scrollParent';
......@@ -58,6 +63,11 @@ export default {
this.$refs.virtualScoller.setScrollTop(0);
this.$refs.virtualScoller.forceRender();
},
async onScroll(event, { offset }) {
const categories = await getEmojiCategories();
this.currentCategory = findLastIndex(Object.values(categories), ({ top }) => offset >= top);
},
},
};
</script>
......@@ -86,10 +96,10 @@ export default {
class="gl-display-flex gl-mx-5 gl-border-b-solid gl-border-gray-100 gl-border-b-1"
>
<button
v-for="category in categoryNames"
v-for="(category, index) in categoryNames"
:key="category.name"
:class="{
'gl-text-black-normal! emoji-picker-category-active': category.name === currentCategory,
'gl-text-black-normal! emoji-picker-category-active': index === currentCategory,
}"
type="button"
class="gl-border-0 gl-border-b-2 gl-border-b-solid gl-flex-fill-1 gl-text-gray-300 gl-pt-3 gl-pb-3 gl-bg-transparent emoji-picker-category-tab"
......@@ -100,18 +110,20 @@ export default {
</div>
<emoji-list :search-value="searchValue">
<template #default="{ filteredCategories }">
<virtual-list ref="virtualScoller" :size="258" :remain="1" :bench="2" variable>
<virtual-list
ref="virtualScoller"
:size="258"
:remain="1"
:bench="2"
variable
:onscroll="onScroll"
>
<div
v-for="(category, categoryKey) in filteredCategories"
:key="categoryKey"
:style="{ height: category.height + 'px' }"
>
<category
:category="categoryKey"
:emojis="category.emojis"
@appear="categoryAppeared"
@click="selectEmoji"
/>
<category :category="categoryKey" :emojis="category.emojis" @click="selectEmoji" />
</div>
</virtual-list>
</template>
......
import { chunk, memoize } from 'lodash';
import Cookies from 'js-cookie';
import { chunk, memoize, uniq } from 'lodash';
import { initEmojiMap, getEmojiCategoryMap } from '~/emoji';
import { EMOJIS_PER_ROW, EMOJI_ROW_HEIGHT, CATEGORY_ROW_HEIGHT } from '../constants';
import {
EMOJIS_PER_ROW,
EMOJI_ROW_HEIGHT,
CATEGORY_ROW_HEIGHT,
FREQUENTLY_USED_KEY,
FREQUENTLY_USED_COOKIE_KEY,
} from '../constants';
export const generateCategoryHeight = (emojisLength) =>
emojisLength * EMOJI_ROW_HEIGHT + CATEGORY_ROW_HEIGHT;
export const getFrequentlyUsedEmojis = () => {
const savedEmojis = Cookies.get(FREQUENTLY_USED_COOKIE_KEY);
if (!savedEmojis) return null;
const emojis = chunk(uniq(savedEmojis.split(',')), 9);
return {
frequently_used: {
emojis,
top: 0,
height: generateCategoryHeight(emojis.length),
},
};
};
export const addToFrequentlyUsed = (emoji) => {
const frequentlyUsedEmojis = uniq(
(Cookies.get(FREQUENTLY_USED_COOKIE_KEY) || '')
.split(',')
.filter((e) => e)
.concat(emoji),
);
Cookies.set(FREQUENTLY_USED_COOKIE_KEY, frequentlyUsedEmojis.join(','), { expires: 365 });
};
export const hasFrequentlyUsedEmojis = () => getFrequentlyUsedEmojis() !== null;
export const getEmojiCategories = memoize(async () => {
await initEmojiMap();
const categories = await getEmojiCategoryMap();
let top = 0;
const frequentlyUsedEmojis = getFrequentlyUsedEmojis();
let top = frequentlyUsedEmojis
? frequentlyUsedEmojis.frequently_used.top + frequentlyUsedEmojis.frequently_used.height
: 0;
return Object.freeze(
Object.keys(categories).reduce((acc, category) => {
const emojis = chunk(categories[category], EMOJIS_PER_ROW);
const height = generateCategoryHeight(emojis.length);
const newAcc = {
...acc,
[category]: { emojis, height, top },
};
top += height;
return newAcc;
}, {}),
Object.keys(categories)
.filter((c) => c !== FREQUENTLY_USED_KEY)
.reduce((acc, category) => {
const emojis = chunk(categories[category], EMOJIS_PER_ROW);
const height = generateCategoryHeight(emojis.length);
const newAcc = {
...acc,
[category]: { emojis, height, top },
};
top += height;
return newAcc;
}, frequentlyUsedEmojis || {}),
);
});
export const FREQUENTLY_USED_KEY = 'frequently_used';
export const FREQUENTLY_USED_COOKIE_KEY = 'frequently_used_emojis';
export const CATEGORY_ICON_MAP = {
[FREQUENTLY_USED_KEY]: 'history',
activity: 'dumbbell',
people: 'smiley',
nature: 'nature',
......
......@@ -2,7 +2,7 @@ import { escape, minBy } from 'lodash';
import emojiAliases from 'emojis/aliases.json';
import AccessorUtilities from '../lib/utils/accessor';
import axios from '../lib/utils/axios_utils';
import { CATEGORY_ICON_MAP } from './constants';
import { CATEGORY_ICON_MAP, FREQUENTLY_USED_KEY } from './constants';
let emojiMap = null;
let validEmojiNames = null;
......@@ -162,6 +162,9 @@ let emojiCategoryMap;
export function getEmojiCategoryMap() {
if (!emojiCategoryMap) {
emojiCategoryMap = CATEGORY_NAMES.reduce((acc, category) => {
if (category === FREQUENTLY_USED_KEY) {
return acc;
}
return { ...acc, [category]: [] };
}, {});
Object.keys(emojiMap).forEach((name) => {
......
......@@ -3,6 +3,8 @@ import { throttle } from 'lodash';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { encodeSaferUrl } from '~/lib/utils/url_utility';
const BLOB_PREFIX = 'blob:';
export default {
props: {
path: {
......@@ -45,7 +47,7 @@ export default {
return this.width && this.height;
},
safePath() {
return encodeSaferUrl(this.path);
return this.path.startsWith(BLOB_PREFIX) ? this.path : encodeSaferUrl(this.path);
},
},
beforeDestroy() {
......
......@@ -135,6 +135,8 @@ module CommitsHelper
end
def cherry_pick_projects_data(project)
return [] unless Feature.enabled?(:pick_into_project, project, default_enabled: :yaml)
target_projects(project).map do |project|
{
id: project.id.to_s,
......
......@@ -5,25 +5,35 @@ module Packages
class PackagesPresenter
include API::Helpers::RelatedResourcesHelpers
def initialize(group, packages)
def initialize(group, packages, is_v2 = false)
@group = group
@packages = packages
@is_v2 = is_v2
end
def root
v1_path = expose_path(api_v4_group___packages_composer_package_name_path({ id: @group.id, package_name: '%package%$%hash%', format: '.json' }, true))
v2_path = expose_path(api_v4_group___packages_composer_p2_package_name_path({ id: @group.id, package_name: '%package%', format: '.json' }, true))
{
index = {
'packages' => [],
'metadata-url' => v2_path
}
# if the client is composer v2 then we don't want to
# include the provider_sha since it is computationally expensive
# to compute.
return index if @is_v2
v1_path = expose_path(api_v4_group___packages_composer_package_name_path({ id: @group.id, package_name: '%package%$%hash%', format: '.json' }, true))
index.merge!(
'provider-includes' => {
'p/%hash%.json' => {
'sha256' => provider_sha
}
},
'providers-url' => v1_path,
'metadata-url' => v2_path
}
'providers-url' => v1_path
)
end
def provider
......
---
title: Improve performance for composer v2 clients
merge_request: 55169
author:
type: added
---
title: Fixed rendering of the image blobs
merge_request: 57479
author:
type: fixed
......@@ -109,8 +109,10 @@
release: 13.9
- title: "Allow Deploy Keys to push to protected branches"
body: |
When viewing a roadmap, there used to be no way to hide confidential epics from the main view. This could be frustrating if you wanted to share your roadmap with a public audience. We've updated the search bar filter to include confidentiality so you now have another layer in which you can refine your roadmap.
stage: Plan
Prior to GitLab 12.0, deploy keys with write access could push commits to protected branches. Support for this was removed due to security concerns, but many users still requested it, as they used deploy keys to ensure that only users with deploy keys could push to their repositories. Removing deploy keys also eliminates the need to use a service user or machine user, which ties up a license for any team that wants to allow deploy keys to push to protected branches just for this use case.
We are excited to announce that we resolved this issue and now deploy keys can push to protected branches once more while abiding by security best practices. By moving towards an isolated permission model for deploy keys, users can now select deploy keys to link to protected branches directly from the **Settings** page on protected branches.
stage: Release
self-managed: true
gitlab-com: true
packages: [Free, Premium, Ultimate]
......
- title: "GitLab Runner for Red Hat OpenShift GA"
body: |
The GitLab Runner Operator is generally available in the [Red Hat OpenShift Container Platform](https://www.openshift.com/products/container-platform). To install GitLab Runner on OpenShift, you can use the [GitLab Runner Operator](https://gitlab.com/gitlab-org/gl-openshift/gitlab-runner-operator), which is available from the stable channel in the OperatorHub. The Container Platform is a web console for OpenShift cluster administrators to discover and select Operators to install on their cluster. We are also developing an [Operator](https://gitlab.com/groups/gitlab-org/-/epics/3444) for GitLab, so stay tuned to future release posts for those announcements.
stage: Verify
self-managed: true
gitlab-com: true
packages: [Free, Premium, Ultimate]
url: https://docs.gitlab.com/runner/install/openshift.html
image_url: https://img.youtube.com/vi/ZNBc_QnDUu4/hqdefault.jpg
published_at: 2021-03-22
release: 13.10
- title: "View epics on a board (MVC)"
body: |
If you work on epics in GitLab, it can be tough to visualize your epics' workflow status. Often, when drafting or writing epics, you might want to use labels (like `Open`, `Doing`, or `Done`) to keep tabs on the next steps when creating your project plan.
In this release, we took our awesome [Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html) feature and optimized it for viewing epics. You can now visualize the workflow status of your epics on an epic board by applying [labels](https://docs.gitlab.com/ee/user/project/labels.html#label-management) or [scoped labels](https://docs.gitlab.com/ee/user/project/labels.html#scoped-labels) to them.
We are releasing this early version of Epic Boards in 13.10, so we can start [gathering customer feedback](https://gitlab.com/gitlab-org/gitlab/-/issues/324677). We will follow it up with [MVC 2](https://gitlab.com/groups/gitlab-org/-/epics/5069) and [MVC 3](https://gitlab.com/groups/gitlab-org/-/epics/5079), which will achieve parity with Issue Boards. Please leave feedback about your experience in the [feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/324677).
stage: Plan
self-managed: true
gitlab-com: true
packages: [Premium, Ultimate]
url: https://docs.gitlab.com/ee/user/group/epics/epic_boards.html
image_url: https://about.gitlab.com/images/13_10/view-epics-on-a-board-mvc-1.png
published_at: 2021-03-22
release: 13.10
- title: "View Jira issue details in GitLab"
body: |
Users of our Jira issue list feature can now view the details of an issue directly inside of GitLab! This MVC enables developers to see the details, labels, and comments on an issue, giving them the ability to stay in GitLab while working on Jira issues.
Our goal is to empower developers to _stay inside of GitLab_ during the majority of their day, and this is now one less trip to Jira you'll have to make.
In GitLab 13.10, this feature is available if you [enable a feature flag](https://docs.gitlab.com/ee/user/project/integrations/jira.html#enable-or-disable-jira-issue-detail-view). This feature will be [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/299832) in GitLab 13.11.
stage: Create
self-managed: true
gitlab-com: true
packages: [Premium, Ultimate]
url: https://docs.gitlab.com/ee/user/project/integrations/jira.html#view-a-jira-issue
image_url: https://about.gitlab.com/images/13_10/jira-detail-view.png
published_at: 2021-03-22
release: 13.10
- title: "DORA4-based lead time for changes"
body: |
Measuring the efficiency of your software development lifecycle is an important step to grow DevOps adoption for any organization. In the previous milestone, we added support for [DORA4-based Deployment Frequency](https://docs.gitlab.com/ee/api/dora4_project_analytics.html). In this release, we are excited to announce the support of a new API for lead time for changes (via merge requests) on the project level. The lead time for changes gives you an indication of how long it takes for code to be committed and deployed to your production environment. Understanding and tracking this data is a great starting point in your journey to continuous improvement in your DevOps process.
stage: Release
self-managed: true
gitlab-com: true
packages: [Ultimate]
url: https://docs.gitlab.com/ee/api/dora4_project_analytics.html#list-project-merge-request-lead-times
image_url: https://about.gitlab.com/images/13_10/api.png
published_at: 2021-03-22
release: 13.10
- title: "Create a release from an existing tag"
body: |
Previously, creating a release was supported only for new tags. In GitLab 13.10, you can now create a release by selecting an existing tag, something that will give you more flexibility when planning releases.
stage: Release
self-managed: true
gitlab-com: true
packages: [Free, Premium, Ultimate]
url: https://docs.gitlab.com/ee/user/project/releases/#create-a-release
image_url: https://about.gitlab.com/images/13_10/exiting_tags.png
published_at: 2021-03-22
release: 13.10
- title: "Integrate any IT alerting tool with GitLab"
body: |
Alert integrations are a critical part of your Incident Management workflows. It's difficult to manage integrations between tools, especially when several monitoring tools like Nagios, Solarwinds, etc. alert on your services. These integrations notify you and your team of incidents, so it's critical for them to be easy to set up and maintain.
In this version of GitLab, you can create multiple HTTP endpoints with unique auth tokens for each integrated monitoring tool. When you set up an HTTP endpoint with a unique auth token for each monitoring tool, your team can manage each tool separately without affecting alerts from other tools nor take down all of your alerting by resetting a single auth token!
stage: Monitor
self-managed: true
gitlab-com: true
packages: [Premium, Ultimate]
url: https://docs.gitlab.com/ee/operations/incident_management/integrations.html#http-endpoints
image_url: https://about.gitlab.com/images/13_10/integrate_alerts.png
published_at: 2021-03-22
release: 13.10
- title: "Merge Request test summary usability improvements"
body: |
Increasing the number of tests or custom metrics in a pipeline gives you additional confidence and information. However, increasing these to a large number has also come with a degraded visual experience of the Merge Request page. The Merge Request test summary widget has been improved so you can better differentiate between the different test jobs in the widget, making it easier to identify which job contains failed tests.
It has also been challenging to understand why a `junit.xml` file was not parsed without errors being presented. Now you can see parsing errors in the Test Summary widget, as well as the Unit Test report, to identify and resolve structural issues and see test results in GitLab.
The [Metrics Reports](https://docs.gitlab.com/ee/ci/metrics_reports.html) widget [(Premium and Ultimate)](https://about.gitlab.com/pricing/) is now sorted so new, changed, and unchanged metrics are all together, making the experience of finding metrics that have changed as part of the Merge Request more intuitive.
stage: Verify
self-managed: true
gitlab-com: true
packages: [Free, Premium, Ultimate]
url: https://docs.gitlab.com/ee/ci/unit_test_reports.html
image_url: https://about.gitlab.com/images/13_10/test_summary_ux_improvements.png
published_at: 2021-03-22
release: 13.10
......@@ -100,8 +100,7 @@ and the [Maven wrapper](https://github.com/takari/maven-wrapper).
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4895) in GitLab 13.7.
GitLab SAST can scan repositories that contain multiple projects. All projects must be in the same
language.
GitLab SAST can scan repositories that contain multiple projects.
The following analyzers have multi-project support:
......
......@@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# GitLab Kubernetes Agent **(PREMIUM SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223061) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
> - It's disabled on GitLab.com. Rolling this feature out to GitLab.com is [planned](https://gitlab.com/groups/gitlab-org/-/epics/3834).
> - [In GitLab 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300960), KAS became available on GitLab.com under `wss://kas.gitlab.com` through an Early Adopter Program.
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
......@@ -85,7 +85,7 @@ component, `agentk`.
Upgrade your agent installations together with GitLab upgrades. To decide which version of `agentk`to install follow:
1. Open the [GITLAB_KAS_VERSION](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_KAS_VERSION) file from the GitLab Repository, which contains the latest `agentk` version associated with the `master` branch.
1. Open the [`GITLAB_KAS_VERSION`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/GITLAB_KAS_VERSION) file from the GitLab Repository, which contains the latest `agentk` version associated with the `master` branch.
1. Change the `master` branch and select the Git tag associated with your version. For instance, you could change it to GitLab [v13.5.3-ee release](https://gitlab.com/gitlab-org/gitlab/-/blob/v13.5.3-ee/GITLAB_KAS_VERSION)
The available `agentk` and `kas` versions can be found in
......@@ -96,20 +96,21 @@ The available `agentk` and `kas` versions can be found in
[Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3834) in GitLab 13.10,
the GitLab Kubernetes Agent Server (KAS) is available on GitLab.com under `wss://kas.gitlab.com`.
If you are a GitLab.com user, skip this step and directly
[set up the configuration repository](#define-a-configuration-repository)
[set up the configuration repository](#define-a-configuration-repository)
for your agent.
The GitLab Kubernetes Agent Server (KAS) can be deployed using [Omnibus
GitLab](https://docs.gitlab.com/omnibus/) or the [GitLab
chart](https://gitlab.com/gitlab-org/charts/gitlab). If you don't already have
The GitLab Kubernetes Agent Server (KAS) can be installed through Omnibus GitLab or
through the GitLab Helm Chart. If you don't already have
GitLab installed, please refer to our [installation
documentation](https://docs.gitlab.com/ee/install/README.html).
You can install the KAS within GitLab as explained below according to your GitLab installation method.
You can also opt to use an [external KAS](#use-an-external-kas-installation).
#### Install with Omnibus
#### Install KAS with Omnibus
When using the [Omnibus GitLab](https://docs.gitlab.com/omnibus/) package:
For [Omnibus](https://docs.gitlab.com/omnibus/) package installations:
1. Edit `/etc/gitlab/gitlab.rb`:
1. Edit `/etc/gitlab/gitlab.rb` to enable the Kubernetes Agent Server:
```plaintext
gitlab_kas['enable'] = true
......@@ -121,9 +122,9 @@ To configure any additional options related to GitLab Kubernetes Agent Server,
refer to the **Enable GitLab KAS** section of the
[`gitlab.rb.template`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/files/gitlab-config-template/gitlab.rb.template).
#### Install with the Helm chart
#### Install KAS with GitLab Helm Chart
When installing or upgrading the GitLab Helm chart, consider the following Helm v3 example.
For GitLab [Helm Chart](https://gitlab.com/gitlab-org/charts/gitlab) installations, consider the following Helm v3 example.
If you're using Helm v2, you must modify this example. See our [notes regarding deploy with Helm](https://docs.gitlab.com/charts/installation/deployment.html#deploy-using-helm).
You must set `global.kas.enabled=true` for the KAS to be properly installed and configured:
......@@ -150,6 +151,29 @@ gitlab:
For details, read [Using the GitLab-KAS chart](https://docs.gitlab.com/charts/charts/gitlab/kas/).
#### Use an external KAS installation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299850) in GitLab 13.10.
Besides installing KAS with GitLab, you can opt to configure GitLab to use an external KAS.
For GitLab instances installed through the GitLab Helm Chart, see [how to configure your external KAS](https://docs.gitlab.com/charts/charts/globals.html#external-kas).
For GitLab instances installed through Omnibus packages:
1. Edit `/etc/gitlab/gitlab.rb` adding the paths to your external KAS:
```ruby
gitlab_kas['enable'] = false
gitlab_kas['api_secret_key'] = 'Your shared secret between GitLab and KAS'
gitlab_rails['gitlab_kas_enabled'] = true
gitlab_rails['gitlab_kas_external_url'] = 'wss://kas.gitlab.example.com' # User-facing URL for the in-cluster agentk
gitlab_rails['gitlab_kas_internal_url'] = 'grpc://kas.internal.gitlab.example.com' # Internal URL for the GitLab backend
```
1. [Reconfigure GitLab](../../../administration/restart_gitlab.md#omnibus-gitlab-reconfigure).
### Define a configuration repository
Next, you need a GitLab repository to contain your Agent configuration. The minimal
......
---
stage: Plan
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Epic Boards **(PREMIUM)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2864) in GitLab 13.10.
> - It's [deployed behind a feature flag](../../feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](../../../administration/feature_flags.md).
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
The GitLab Epic Board is a software project management tool used to plan,
organize, and visualize a workflow for a feature or product release.
Epic boards build on the existing [epic tracking functionality](index.md) and
[labels](../../project/labels.md). Your epics appear as cards in vertical lists, organized by their assigned
labels.
To view an epic board, in a group, select **Epics > Boards**.
![GitLab epic board - Premium](img/epic_board_v13_10.png)
## Create an epic board
To create a new epic board:
1. Select the dropdown with the current board name in the upper left corner of the Epic Boards page.
1. Select **Create new board**.
1. Enter the new board's name and select **Create**.
## Limitations of epic boards
As of GitLab 13.10, these limitations apply:
- Epic Boards need to be enabled by an administrator.
- Epic Boards can be created but not deleted.
- Lists can be added to the board but not deleted.
- There is no sidebar on the board. To edit an epic, go to the epic's page.
- There is no drag and drop support yet. To move an epic between lists, edit epic labels on the epic's page.
- Epics cannot be re-ordered within the list.
To learn more about the future iterations of this feature, visit
[epic 5067](https://gitlab.com/groups/gitlab-org/-/epics/5067).
## Enable or disable Epic Boards
Epic Boards are under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:epic_boards)
```
To disable it:
```ruby
Feature.disable(:epic_boards)
```
......@@ -47,8 +47,12 @@ module API
end
end
def composer_v2?
headers['User-Agent'].to_s.include?('Composer/2')
end
def presenter
@presenter ||= ::Packages::Composer::PackagesPresenter.new(user_group, packages)
@presenter ||= ::Packages::Composer::PackagesPresenter.new(user_group, packages, composer_v2?)
end
end
......@@ -66,33 +70,25 @@ module API
end
desc 'Composer packages endpoint at group level'
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get ':id/-/packages/composer/packages' do
presenter.root
end
desc 'Composer packages endpoint at group level for packages list'
params do
requires :sha, type: String, desc: 'Shasum of current json'
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get ':id/-/packages/composer/p/:sha' do
presenter.provider
end
desc 'Composer packages endpoint at group level for package versions metadata'
desc 'Composer v2 packages p2 endpoint at group level for package versions metadata'
params do
requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty?
......@@ -100,13 +96,10 @@ module API
end
desc 'Composer packages endpoint at group level for package versions metadata'
params do
requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty?
not_found! if params[:sha].blank?
......@@ -125,7 +118,6 @@ module API
end
desc 'Composer packages endpoint for registering packages'
namespace ':id/packages/composer' do
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
......@@ -134,7 +126,6 @@ module API
optional :tag, type: String, desc: 'The name of the tag'
exactly_one_of :tag, :branch
end
post do
authorize_create_package!(authorized_user_project)
......@@ -159,7 +150,6 @@ module API
requires :sha, type: String, desc: 'Shasum of current json'
requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end
get 'archives/*package_name' do
metadata = unauthorized_user_project
.packages
......
{
"type": "object",
"required": ["packages", "metadata-url"],
"properties": {
"packages": {
"type": "array",
"items": { "type": "integer" }
},
"metadata-url": {
"type": "string"
}
},
"additionalProperties": false
}
......@@ -6,6 +6,8 @@ const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
const GREEN_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/green_box.png`;
const RED_BOX_IMAGE_URL = `${FIXTURES_PATH}/static/images/red_box.png`;
const DUMMY_IMAGE_BLOB_PATH = 'SpongeBlob.png';
// NOTE: module.exports is needed so that this file can be used
// by environment.js
//
......@@ -16,4 +18,5 @@ module.exports = {
DUMMY_IMAGE_URL,
GREEN_BOX_IMAGE_URL,
RED_BOX_IMAGE_URL,
DUMMY_IMAGE_BLOB_PATH,
};
import Cookies from 'js-cookie';
import { getFrequentlyUsedEmojis, addToFrequentlyUsed } from '~/emoji/components/utils';
jest.mock('js-cookie');
describe('getFrequentlyUsedEmojis', () => {
it('it returns null when no saved emojis set', () => {
jest.spyOn(Cookies, 'get').mockReturnValue(null);
expect(getFrequentlyUsedEmojis()).toBe(null);
});
it('it returns frequently used emojis object', () => {
jest.spyOn(Cookies, 'get').mockReturnValue('thumbsup,thumbsdown');
expect(getFrequentlyUsedEmojis()).toEqual({
frequently_used: {
emojis: [['thumbsup', 'thumbsdown']],
top: 0,
height: 71,
},
});
});
});
describe('addToFrequentlyUsed', () => {
it('sets cookie value', () => {
jest.spyOn(Cookies, 'get').mockReturnValue(null);
addToFrequentlyUsed('thumbsup');
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsup', {
expires: 365,
});
});
it('sets cookie value to include previously set cookie value', () => {
jest.spyOn(Cookies, 'get').mockReturnValue('thumbsdown');
addToFrequentlyUsed('thumbsup');
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsdown,thumbsup', {
expires: 365,
});
});
it('sets cookie value with uniq values', () => {
jest.spyOn(Cookies, 'get').mockReturnValue('thumbsup');
addToFrequentlyUsed('thumbsup');
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsup', {
expires: 365,
});
});
});
import { mount } from '@vue/test-utils';
import { GREEN_BOX_IMAGE_URL } from 'spec/test_constants';
import { shallowMount } from '@vue/test-utils';
import { GREEN_BOX_IMAGE_URL, DUMMY_IMAGE_BLOB_PATH } from 'spec/test_constants';
import ImageViewer from '~/vue_shared/components/content_viewer/viewers/image_viewer.vue';
describe('Image Viewer', () => {
let wrapper;
it('renders image preview', () => {
wrapper = mount(ImageViewer, {
wrapper = shallowMount(ImageViewer, {
propsData: { path: GREEN_BOX_IMAGE_URL, fileSize: 1024 },
});
......@@ -22,7 +22,7 @@ describe('Image Viewer', () => {
`(
'shows file size as "$humanizedFileSize", if fileSize=$fileSize and renderInfo=$renderInfo',
({ fileSize, renderInfo, elementExists, humanizedFileSize }) => {
wrapper = mount(ImageViewer, {
wrapper = shallowMount(ImageViewer, {
propsData: { path: GREEN_BOX_IMAGE_URL, fileSize, renderInfo },
});
......@@ -36,11 +36,19 @@ describe('Image Viewer', () => {
describe('file path', () => {
it('should output a valid URL path for the image', () => {
wrapper = mount(ImageViewer, {
wrapper = shallowMount(ImageViewer, {
propsData: { path: '/url/hello#1.jpg' },
});
expect(wrapper.find('img').attributes('src')).toBe('/url/hello%231.jpg');
});
it('outputs path without transformations when outputting a Blob', () => {
const file = new File([], DUMMY_IMAGE_BLOB_PATH);
const path = window.URL.createObjectURL(file);
wrapper = shallowMount(ImageViewer, {
propsData: { path },
});
expect(wrapper.find('img').attributes('src')).toBe(path);
});
});
});
......@@ -257,5 +257,15 @@ RSpec.describe CommitsHelper do
{ id: forked_project.id.to_s, name: forked_project.full_path, refsUrl: refs_project_path(forked_project) }
])
end
context 'pick_into_project is disabled' do
before do
stub_feature_flags(pick_into_project: false)
end
it 'does not calculate target projects' do
expect(helper.cherry_pick_projects_data(project)).to eq([])
end
end
end
end
......@@ -15,7 +15,8 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
let(:branch) { project.repository.find_branch('master') }
let(:packages) { [package1, package2] }
let(:presenter) { described_class.new(group, packages) }
let(:is_v2) { false }
let(:presenter) { described_class.new(group, packages, is_v2) }
describe '#package_versions' do
subject { presenter.package_versions }
......@@ -79,5 +80,19 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
it 'returns the provider json' do
expect(subject).to match(expected_json)
end
context 'with a client version 2' do
let(:is_v2) { true }
let(:expected_json) do
{
'packages' => [],
'metadata-url' => "prefix/api/v4/group/#{group.id}/-/packages/composer/p2/%package%.json"
}
end
it 'returns the provider json' do
expect(subject).to match(expected_json)
end
end
end
end
......@@ -7,20 +7,30 @@ RSpec.shared_context 'Composer user type' do |user_type, add_member|
end
end
RSpec.shared_examples 'Composer package index with version' do |schema_path|
it 'returns the package index' do
subject
expect(response).to have_gitlab_http_status(status)
if status == :success
expect(response).to match_response_schema(schema_path)
expect(json_response).to eq presenter.root
end
end
end
RSpec.shared_examples 'Composer package index' do |user_type, status, add_member, include_package|
include_context 'Composer user type', user_type, add_member do
let(:expected_packages) { include_package == :include_package ? [package] : [] }
let(:presenter) { ::Packages::Composer::PackagesPresenter.new(group, expected_packages ) }
it 'returns the package index' do
subject
it_behaves_like 'Composer package index with version', 'public_api/v4/packages/composer/index'
expect(response).to have_gitlab_http_status(status)
context 'with version 2' do
let(:headers) { super().merge('User-Agent' => 'Composer/2.0.9 (Darwin; 19.6.0; PHP 7.4.8; cURL 7.71.1)') }
if status == :success
expect(response).to match_response_schema('public_api/v4/packages/composer/index')
expect(json_response).to eq presenter.root
end
it_behaves_like 'Composer package index with version', 'public_api/v4/packages/composer/index_v2'
end
end
end
......
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
......@@ -85,7 +85,7 @@ func TestConfigDefaults(t *testing.T) {
DocumentRoot: "public",
ProxyHeadersTimeout: 5 * time.Minute,
APIQueueTimeout: queueing.DefaultTimeout,
APICILongPollingDuration: 50 * time.Second,
APICILongPollingDuration: 50 * time.Nanosecond, // TODO this is meant to be 50*time.Second but it has been wrong for ages
ImageResizerConfig: config.DefaultImageResizerConfig,
}
......
......@@ -102,7 +102,7 @@ func buildConfig(arg0 string, args []string) (*bootConfig, *config.Config, error
fset.UintVar(&cfg.APILimit, "apiLimit", 0, "Number of API requests allowed at single time")
fset.UintVar(&cfg.APIQueueLimit, "apiQueueLimit", 0, "Number of API requests allowed to be queued")
fset.DurationVar(&cfg.APIQueueTimeout, "apiQueueDuration", queueing.DefaultTimeout, "Maximum queueing duration of requests")
fset.DurationVar(&cfg.APICILongPollingDuration, "apiCiLongPollingDuration", 50*time.Second, "Long polling duration for job requesting for runners (default 50s - enabled)")
fset.DurationVar(&cfg.APICILongPollingDuration, "apiCiLongPollingDuration", 50, "Long polling duration for job requesting for runners (default 50s - enabled)")
fset.BoolVar(&cfg.PropagateCorrelationID, "propagateCorrelationID", false, "Reuse existing Correlation-ID from the incoming request header `X-Request-ID` if present")
if err := fset.Parse(args); err != nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment