diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-01 15:14:27 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-01 15:14:27 +0000 |
commit | afbaf78b0d819326741a01b093bdbc4702570417 (patch) | |
tree | a16a6f2a8fcc18e60f25ac72df6ab22cfe0eae79 | |
parent | 38b3003b67db3f2eadfa81fd28b13d168f665766 (diff) | |
download | gitlab-ce-afbaf78b0d819326741a01b093bdbc4702570417.tar.gz |
Add latest changes from gitlab-org/gitlab@13-10-stable-ee
28 files changed, 457 insertions, 86 deletions
diff --git a/app/assets/javascripts/emoji/components/category.vue b/app/assets/javascripts/emoji/components/category.vue index a11122d5403..db6ead3ff69 100644 --- a/app/assets/javascripts/emoji/components/category.vue +++ b/app/assets/javascripts/emoji/components/category.vue @@ -1,6 +1,6 @@ <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> diff --git a/app/assets/javascripts/emoji/components/picker.vue b/app/assets/javascripts/emoji/components/picker.vue index 7cd20d82329..37f3433b781 100644 --- a/app/assets/javascripts/emoji/components/picker.vue +++ b/app/assets/javascripts/emoji/components/picker.vue @@ -1,11 +1,12 @@ <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> diff --git a/app/assets/javascripts/emoji/components/utils.js b/app/assets/javascripts/emoji/components/utils.js index b95b56a1d6f..3465a8ae7e6 100644 --- a/app/assets/javascripts/emoji/components/utils.js +++ b/app/assets/javascripts/emoji/components/utils.js @@ -1,27 +1,68 @@ -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 || {}), ); }); diff --git a/app/assets/javascripts/emoji/constants.js b/app/assets/javascripts/emoji/constants.js index bf73d1ca5a9..e9f2272e759 100644 --- a/app/assets/javascripts/emoji/constants.js +++ b/app/assets/javascripts/emoji/constants.js @@ -1,4 +1,8 @@ +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', diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index d3b658a4020..7faf0fe5f08 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -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) => { diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue index a49eb7fd611..04ab0fd00aa 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue @@ -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() { diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 93776503dd6..42e4844cc8d 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -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, diff --git a/app/presenters/packages/composer/packages_presenter.rb b/app/presenters/packages/composer/packages_presenter.rb index 8e3f482126d..2eb9b8910a3 100644 --- a/app/presenters/packages/composer/packages_presenter.rb +++ b/app/presenters/packages/composer/packages_presenter.rb @@ -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 diff --git a/changelogs/unreleased/290288-disable-composer-shas-for-v2.yml b/changelogs/unreleased/290288-disable-composer-shas-for-v2.yml new file mode 100644 index 00000000000..45838d30160 --- /dev/null +++ b/changelogs/unreleased/290288-disable-composer-shas-for-v2.yml @@ -0,0 +1,5 @@ +--- +title: Improve performance for composer v2 clients +merge_request: 55169 +author: +type: added diff --git a/changelogs/unreleased/325864-fix-image-blob-rendering.yml b/changelogs/unreleased/325864-fix-image-blob-rendering.yml new file mode 100644 index 00000000000..fa07c4abfc0 --- /dev/null +++ b/changelogs/unreleased/325864-fix-image-blob-rendering.yml @@ -0,0 +1,5 @@ +--- +title: Fixed rendering of the image blobs +merge_request: 57479 +author: +type: fixed diff --git a/data/whats_new/202102180001_13_09.yml b/data/whats_new/202102180001_13_09.yml index 314ffc8dba3..48c8b547a0e 100644 --- a/data/whats_new/202102180001_13_09.yml +++ b/data/whats_new/202102180001_13_09.yml @@ -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] diff --git a/data/whats_new/202103220001_13_10.yml b/data/whats_new/202103220001_13_10.yml new file mode 100644 index 00000000000..6de61ecdb06 --- /dev/null +++ b/data/whats_new/202103220001_13_10.yml @@ -0,0 +1,91 @@ +- 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 diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index b71cefbc7fe..fffff4efba6 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -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: diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md index 9af339b20c6..77b9dbb1c7e 100644 --- a/doc/user/clusters/agent/index.md +++ b/doc/user/clusters/agent/index.md @@ -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 diff --git a/doc/user/group/epics/epic_boards.md b/doc/user/group/epics/epic_boards.md new file mode 100644 index 00000000000..343f7c496b1 --- /dev/null +++ b/doc/user/group/epics/epic_boards.md @@ -0,0 +1,68 @@ +--- +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) +``` diff --git a/doc/user/group/epics/img/epic_board_v13_10.png b/doc/user/group/epics/img/epic_board_v13_10.png Binary files differnew file mode 100644 index 00000000000..5148e6dd4ec --- /dev/null +++ b/doc/user/group/epics/img/epic_board_v13_10.png diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index ec7585363e2..bd8d9b68858 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -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 diff --git a/spec/fixtures/api/schemas/public_api/v4/packages/composer/index_v2.json b/spec/fixtures/api/schemas/public_api/v4/packages/composer/index_v2.json new file mode 100644 index 00000000000..6b77b758bfd --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/packages/composer/index_v2.json @@ -0,0 +1,14 @@ +{ + "type": "object", + "required": ["packages", "metadata-url"], + "properties": { + "packages": { + "type": "array", + "items": { "type": "integer" } + }, + "metadata-url": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/spec/frontend/__helpers__/test_constants.js b/spec/frontend/__helpers__/test_constants.js index 69b78f556aa..628b9b054d3 100644 --- a/spec/frontend/__helpers__/test_constants.js +++ b/spec/frontend/__helpers__/test_constants.js @@ -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, }; diff --git a/spec/frontend/emoji/components/utils_spec.js b/spec/frontend/emoji/components/utils_spec.js new file mode 100644 index 00000000000..36521eb1051 --- /dev/null +++ b/spec/frontend/emoji/components/utils_spec.js @@ -0,0 +1,56 @@ +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, + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js index af3b63ad7e5..974d06a6ed4 100644 --- a/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js +++ b/spec/frontend/vue_shared/components/content_viewer/viewers/image_viewer_spec.js @@ -1,12 +1,12 @@ -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); + }); }); }); diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb index 397751b07af..2a8e2e04947 100644 --- a/spec/helpers/commits_helper_spec.rb +++ b/spec/helpers/commits_helper_spec.rb @@ -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 diff --git a/spec/presenters/packages/composer/packages_presenter_spec.rb b/spec/presenters/packages/composer/packages_presenter_spec.rb index c4217b6e37c..d0e3b68fc9f 100644 --- a/spec/presenters/packages/composer/packages_presenter_spec.rb +++ b/spec/presenters/packages/composer/packages_presenter_spec.rb @@ -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 diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb index 4b5299cebec..b86c0529338 100644 --- a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb @@ -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 diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index 259148fa18f..259148fa18f 100644..100755 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore index a1c2a238a96..a1c2a238a96 100644..100755 --- a/vendor/gitignore/Java.gitignore +++ b/vendor/gitignore/Java.gitignore diff --git a/workhorse/config_test.go b/workhorse/config_test.go index cf33e5bb7ca..f9d12bd5e2b 100644 --- a/workhorse/config_test.go +++ b/workhorse/config_test.go @@ -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, } diff --git a/workhorse/main.go b/workhorse/main.go index 28162a00fae..47ab63a875a 100644 --- a/workhorse/main.go +++ b/workhorse/main.go @@ -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 { |