Commit c019f485 authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 72875e4a
import $ from 'jquery';
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import Translate from '~/vue_shared/translate';
import { highCountTrim } from '~/lib/utils/text_utility';
import Tracking from '~/tracking';
......@@ -35,7 +34,6 @@ function initStatusTriggers() {
const statusModalElement = document.createElement('div');
setStatusModalWrapperEl.appendChild(statusModalElement);
Vue.use(GlToast);
Vue.use(Translate);
// eslint-disable-next-line no-new
......
......@@ -35,6 +35,7 @@ export default {
i18n: {
openedAgo: __('opened %{timeAgoString} by %{user}'),
openedAgoJira: __('opened %{timeAgoString} by %{user} in Jira'),
openedAgoServiceDesk: __('opened %{timeAgoString} by %{email} via %{user}'),
},
inject: ['scopedLabelsAvailable'],
components: {
......@@ -206,6 +207,11 @@ export default {
healthStatus() {
return convertToCamelCase(this.issuable.health_status);
},
openedMessage() {
if (this.isJiraIssue) return this.$options.i18n.openedAgoJira;
if (this.issuable.service_desk_reply_to) return this.$options.i18n.openedAgoServiceDesk;
return this.$options.i18n.openedAgo;
},
},
mounted() {
// TODO: Refactor user popover to use its own component instead of
......@@ -311,9 +317,7 @@ export default {
<span data-testid="openedByMessage" class="gl-display-none d-sm-inline-block gl-mr-4">
&middot;
<gl-sprintf
:message="isJiraIssue ? $options.i18n.openedAgoJira : $options.i18n.openedAgo"
>
<gl-sprintf :message="openedMessage">
<template #timeAgoString>
<span>{{ issuableCreatedAt }}</span>
</template>
......@@ -326,6 +330,9 @@ export default {
>{{ issuableAuthor.name }}</gl-link
>
</template>
<template #email>
<span>{{ issuable.service_desk_reply_to }}</span>
</template>
</gl-sprintf>
</span>
......
<script>
/* eslint-disable vue/no-v-html */
// We are forced to use `v-html` untill this gitlab-ui MR is merged: https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1869
// then we can re-write this to use gl-breadcrumb
import { initial, first, last } from 'lodash';
import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { sanitize } from '~/lib/dompurify';
export default {
directives: { SafeHtml },
props: {
crumbs: {
type: Array,
......@@ -11,6 +13,9 @@ export default {
},
},
computed: {
parsedCrumbs() {
return this.crumbs.map(c => ({ ...c, innerHTML: sanitize(c.innerHTML) }));
},
rootRoute() {
return this.$router.options.routes.find(r => r.meta.root);
},
......@@ -18,11 +23,11 @@ export default {
return this.$route.name === this.rootRoute.name;
},
rootCrumbs() {
return initial(this.crumbs);
return initial(this.parsedCrumbs);
},
divider() {
const { classList, tagName, innerHTML } = first(this.crumbs).querySelector('svg');
return { classList: [...classList], tagName, innerHTML };
return { classList: [...classList], tagName, innerHTML: sanitize(innerHTML) };
},
lastCrumb() {
const { children } = last(this.crumbs);
......@@ -43,14 +48,14 @@ export default {
<li
v-for="(crumb, index) in rootCrumbs"
:key="index"
v-safe-html="crumb.innerHTML"
:class="crumb.className"
v-html="crumb.innerHTML"
></li>
<li v-if="!isRootRoute">
<router-link ref="rootRouteLink" :to="rootRoute.path">
{{ rootRoute.meta.nameGenerator($store.state) }}
</router-link>
<component :is="divider.tagName" v-safe-html="divider.innerHTML" :class="divider.classList" />
<component :is="divider.tagName" :class="divider.classList" v-html="divider.innerHTML" />
</li>
<li>
<component :is="lastCrumb.tagName" ref="lastCrumb" :class="lastCrumb.className">
......
<script>
/* eslint-disable vue/no-v-html */
import $ from 'jquery';
import Vue from 'vue';
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import { GlModal, GlTooltipDirective, GlIcon, GlFormCheckbox } from '@gitlab/ui';
import { GlToast, GlModal, GlTooltipDirective, GlIcon, GlFormCheckbox } from '@gitlab/ui';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { __, s__ } from '~/locale';
import Api from '~/api';
......@@ -16,6 +17,8 @@ export const AVAILABILITY_STATUS = {
NOT_SET: 'not_set',
};
Vue.use(GlToast);
export default {
components: {
GlIcon,
......
......@@ -189,6 +189,10 @@ def issuable_meta(issuable, project, text)
output = []
output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
if issuable.is_a?(Issue) && issuable.service_desk_reply_to
output << "#{html_escape(issuable.service_desk_reply_to)} via "
end
output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline")
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-inline d-sm-none")
......
......@@ -118,7 +118,7 @@ def enqueue_jira_connect_sync_messages
commits_to_sync = limited_commits.select { |commit| Atlassian::JiraIssueKeyExtractor.has_keys?(commit.safe_message) }.map(&:sha)
if branch_to_sync || commits_to_sync.any?
JiraConnect::SyncBranchWorker.perform_async(project.id, branch_to_sync, commits_to_sync)
JiraConnect::SyncBranchWorker.perform_async(project.id, branch_to_sync, commits_to_sync, Atlassian::JiraConnect::Client.generate_update_sequence_id)
end
end
......
......@@ -58,7 +58,7 @@ def enqueue_jira_connect_messages_for(merge_request)
return unless project.jira_subscription_exists?
if Atlassian::JiraIssueKeyExtractor.has_keys?(merge_request.title, merge_request.description)
JiraConnect::SyncMergeRequestWorker.perform_async(merge_request.id)
JiraConnect::SyncMergeRequestWorker.perform_async(merge_request.id, Atlassian::JiraConnect::Client.generate_update_sequence_id)
end
end
......
......@@ -22,8 +22,10 @@
#{issuable_reference(issue)}
%span.issuable-authored.d-none.d-sm-inline-block
&middot;
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')}
by #{link_to_member(@project, issue.author, avatar: false)}
opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by
- if issue.service_desk_reply_to
#{issue.service_desk_reply_to} via
#{link_to_member(@project, issue.author, avatar: false)}
= render_if_exists 'shared/issuable/gitlab_team_member_badge', {author: issue.author}
- if issue.milestone
%span.issuable-milestone.d-none.d-sm-inline-block
......
......@@ -32,8 +32,7 @@
%ul
- if can_update_merge_request
%li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- if can_update_merge_request
- unless @merge_request.closed?
- if @merge_request.opened?
%li
= link_to @merge_request.work_in_progress? ? _('Mark as ready') : _('Mark as draft'), toggle_draft_issuable_path(@merge_request), method: :put, class: "js-draft-toggle-button"
%li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
......
......@@ -1609,7 +1609,7 @@
:urgency: :low
:resource_boundary: :cpu
:weight: 2
:idempotent:
:idempotent: true
:tags: []
- :name: invalid_gpg_signature_update
:feature_category: :source_code_management
......
......@@ -3,6 +3,7 @@
class ImportIssuesCsvWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
idempotent!
feature_category :issue_tracking
worker_resource_boundary :cpu
weight 2
......@@ -12,13 +13,15 @@ class ImportIssuesCsvWorker # rubocop:disable Scalability/IdempotentWorker
end
def perform(current_user_id, project_id, upload_id)
@user = User.find(current_user_id)
@project = Project.find(project_id)
@upload = Upload.find(upload_id)
user = User.find(current_user_id)
project = Project.find(project_id)
upload = Upload.find(upload_id)
importer = Issues::ImportCsvService.new(@user, @project, @upload.retrieve_uploader)
importer = Issues::ImportCsvService.new(user, project, upload.retrieve_uploader)
importer.execute
@upload.destroy
upload.destroy
rescue ActiveRecord::RecordNotFound
# Resources have been removed, job should not be retried
end
end
---
title: Add `service_desk_reply_to` to issues list and header
merge_request: 48089
author: Lee Tickett
type: added
---
title: Replace bootstrap alerts in ee/app/views/admin/licenses/new.html.haml
merge_request: 41275
author: Gilang Gumilar
type: changed
---
title: Convert shared runner limit alert to gl-alert
merge_request: 48063
author:
type: other
---
title: Hide Mark as draft button in a merged MR even on mobile
merge_request: 47678
author: Takuya Noguchi
type: fixed
---
title: Make ImportIssuesCsvWorker idempotent
merge_request: 47808
author:
type: changed
---
title: Add code coverage overall activity to group repository analytics
merge_request: 48155
author:
type: added
......@@ -9,7 +9,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
The GitLab UI polls for updates for different resources (issue notes, issue
titles, pipeline statuses, etc.) on a schedule appropriate to the resource.
In "Application settings -> Real-time features" you can configure "Polling
In **[Admin Area](../user/admin_area/index.md) > Settings > Preferences > Real-time features**,
you can configure "Polling
interval multiplier". This multiplier is applied to all resources at once,
and decimal values are supported. For the sake of the examples below, we will
say that issue notes poll every 2 seconds, and issue titles poll every 5
......
......@@ -8677,8 +8677,7 @@ type Group {
): BoardConnection
"""
Represents the code coverage activity for this group. Available only when
feature flag `group_coverage_data_report_graph` is enabled
Represents the code coverage activity for this group
"""
codeCoverageActivities(
"""
......
......@@ -24065,7 +24065,7 @@
},
{
"name": "codeCoverageActivities",
"description": "Represents the code coverage activity for this group. Available only when feature flag `group_coverage_data_report_graph` is enabled",
"description": "Represents the code coverage activity for this group",
"args": [
{
"name": "startDate",
......@@ -1432,7 +1432,7 @@ Autogenerated return type of EpicTreeReorder.
| `avatarUrl` | String | Avatar URL of the group |
| `board` | Board | A single board of the group |
| `boards` | BoardConnection | Boards of the group |
| `codeCoverageActivities` | CodeCoverageActivityConnection | Represents the code coverage activity for this group. Available only when feature flag `group_coverage_data_report_graph` is enabled |
| `codeCoverageActivities` | CodeCoverageActivityConnection | Represents the code coverage activity for this group |
| `containerRepositories` | ContainerRepositoryConnection | Container repositories of the project |
| `containsLockedProjects` | Boolean! | Includes at least one project where the repository size exceeds the limit |
| `customEmoji` | CustomEmojiConnection | Custom emoji within this namespace. Available only when feature flag `custom_emoji` is enabled |
......
......@@ -678,6 +678,7 @@ Example response:
},
"subscribed": true,
"moved_to_id": null,
"service_desk_reply_to": "service.desk@gitlab.com",
"epic_iid": null,
"epic": null
}
......
......@@ -90,7 +90,7 @@ choose one of these templates:
- [Rust (`Rust.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Rust.gitlab-ci.yml)
- [Scala (`Scala.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Scala.gitlab-ci.yml)
- [Swift (`Swift.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Swift.gitlab-ci.yml)
- [Terraform (`Terraform.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml)
- [Terraform (`Terraform.latest.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.latest.gitlab-ci.yml)
If a programming language or framework template is not in this list, you can contribute
one. To create a template, submit a merge request
......
......@@ -236,7 +236,7 @@ Table description links:
| [Certificate Management](#certificate-management) | TLS Settings, Let's Encrypt | ✅ | ✅ | ⚙ | ✅ | ⚙ | ⚙ | CE & EE |
| [Consul](#consul) | Database node discovery, failover | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
| [Database Migrations](#database-migrations) | Database migrations | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
| [Elasticsearch](#elasticsearch) | Improved search within GitLab | ⤓ | ⤓ | ⤓ | | ⤓ | ⤓ | EE Only |
| [Elasticsearch](#elasticsearch) | Improved search within GitLab | ⤓ | ⤓ | ⤓ | | ⤓ | ⤓ | EE Only |
| [Gitaly](#gitaly) | Git RPC service for handling all Git calls made by GitLab | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
| [GitLab Exporter](#gitlab-exporter) | Generates a variety of GitLab metrics | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | ⚙ | ⚙ | ❌ | ✅ | ❌ | ⚙ | EE Only |
......@@ -340,7 +340,7 @@ Consul is a tool for service discovery and configuration. Consul is distributed,
- [Source](../integration/elasticsearch.md)
- [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/elasticsearch.md)
- Layer: Core Service (Data)
- GitLab.com: [Get Advanced Search working on GitLab.com](https://gitlab.com/groups/gitlab-org/-/epics/153) epic.
- GitLab.com: [Get Advanced Search working on GitLab.com (Closed)](https://gitlab.com/groups/gitlab-org/-/epics/153) epic.
Elasticsearch is a distributed RESTful search engine built for the cloud.
......
......@@ -794,10 +794,21 @@ With [GitLab Issue Analytics](issues_analytics/index.md), you can see a bar char
## Repositories analytics **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215104) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263478) in GitLab 13.6.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/276003) in GitLab 13.7.
With [GitLab Repositories Analytics](repositories_analytics/index.md), you can download a CSV of the latest coverage data for all the projects in your group.
### Check code coverage for all projects
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/263478) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.7.
See the overall activity of all projects with code coverage with [GitLab Repositories Analytics](repositories_analytics/index.md).
It displays the current code coverage data available for your projects:
![Group repositories analytics](img/group_code_coverage_analytics_v13_7.png)
## Dependency Proxy
Use GitLab as a [dependency proxy](../packages/dependency_proxy/index.md) for upstream Docker images.
......
......@@ -43,6 +43,7 @@ class Issue < IssueBasic
end
expose :moved_to_id
expose :service_desk_reply_to
end
end
end
......
......@@ -180,6 +180,11 @@ msgid_plural "%d errors"
msgstr[0] ""
msgstr[1] ""
msgid "%d error found:"
msgid_plural "%d errors found:"
msgstr[0] ""
msgstr[1] ""
msgid "%d exporter"
msgid_plural "%d exporters"
msgstr[0] ""
......@@ -27091,6 +27096,9 @@ msgstr ""
msgid "The form contains the following error:"
msgstr ""
msgid "The form contains the following errors:"
msgstr ""
msgid "The form contains the following warning:"
msgstr ""
......@@ -32862,6 +32870,9 @@ msgstr ""
msgid "open issue"
msgstr ""
msgid "opened %{timeAgoString} by %{email} via %{user}"
msgstr ""
msgid "opened %{timeAgoString} by %{user}"
msgstr ""
......
......@@ -121,6 +121,18 @@
it_behaves_like 'an issuable close/reopen/report toggle'
context 'when the merge request is open' do
let(:issuable) { create(:merge_request, :opened, source_project: project) }
it 'shows the `Edit` and `Mark as draft` buttons' do
expect(container).to have_link('Edit')
expect(container).to have_link('Mark as draft')
expect(container).not_to have_button('Report abuse')
expect(container).not_to have_button('Close merge request')
expect(container).not_to have_link('Reopen merge request')
end
end
context 'when the merge request is closed' do
let(:issuable) { create(:merge_request, :closed, source_project: project) }
......
......@@ -4,7 +4,9 @@
RSpec.describe 'Service Desk Issue Tracker', :js do
let(:project) { create(:project, :private, service_desk_enabled: true) }
let(:user) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:support_bot) { User.support_bot }
before do
# The following two conditions equate to Gitlab::ServiceDesk.supported == true
......@@ -27,6 +29,16 @@
end
end
context 'issue page' do
let(:service_desk_issue) { create(:issue, project: project, author: support_bot, service_desk_reply_to: 'service.desk@example.com') }
it 'shows service_desk_reply_to in issue header' do
visit project_issue_path(project, service_desk_issue)
expect(page).to have_text('by service.desk@example.com via GitLab Support Bot')
end
end
describe 'issues list' do
context 'when service desk is supported' do
context 'when there are no issues' do
......@@ -66,10 +78,10 @@
end
context 'when there are issues' do
let(:support_bot) { User.support_bot }
let(:other_user) { create(:user) }
let!(:service_desk_issue) { create(:issue, project: project, author: support_bot) }
let!(:other_user_issue) { create(:issue, project: project, author: other_user) }
let_it_be(:project) { create(:project, :private, service_desk_enabled: true) }
let_it_be(:other_user) { create(:user) }
let_it_be(:service_desk_issue) { create(:issue, project: project, author: support_bot, service_desk_reply_to: 'service.desk@example.com') }
let_it_be(:other_user_issue) { create(:issue, project: project, author: other_user) }
describe 'service desk info content' do
before do
......@@ -94,6 +106,10 @@
it 'only displays issues created by support bot' do
expect(page).to have_selector('.issues-list .issue', count: 1)
end
it 'shows service_desk_reply_to in issues list' do
expect(page).to have_text('by service.desk@example.com via GitLab Support Bot')
end
end
describe 'search box' do
......
......@@ -718,10 +718,10 @@ def execute_service(project, user, change, push_options = {})
end
shared_examples 'enqueues Jira sync worker' do
specify do
specify :aggregate_failures do
Sidekiq::Testing.fake! do
expect(JiraConnect::SyncBranchWorker).to receive(:perform_async)
.with(project.id, branch_to_sync, commits_to_sync)
.with(project.id, branch_to_sync, commits_to_sync, kind_of(Numeric))
.and_call_original
expect { subject.execute }.to change(JiraConnect::SyncBranchWorker.jobs, :size).by(1)
......
......@@ -20,7 +20,8 @@
describe '#execute_hooks' do
shared_examples 'enqueues Jira sync worker' do
it do
specify :aggregate_failures do
expect(JiraConnect::SyncMergeRequestWorker).to receive(:perform_async).with(kind_of(Numeric), kind_of(Numeric)).and_call_original
Sidekiq::Testing.fake! do
expect { subject.execute }.to change(JiraConnect::SyncMergeRequestWorker.jobs, :size).by(1)
end
......
# frozen_string_literal: true
module Spec
module Support
module Helpers
module Features
module MergeRequestHelpers
def preload_view_requirements(merge_request, note)
# This will load the status fields of the author of the note and merge request
# to avoid queries in when rendering the view being tested.
merge_request.author.status
note.author.status
end
def serialize_issuable_sidebar(user, project, merge_request)
MergeRequestSerializer
.new(current_user: user, project: project)
.represent(merge_request, serializer: 'sidebar')
end
end
end
end
end
end
# frozen_string_literal: true
RSpec.shared_context 'merge request show action' do
RSpec.shared_context 'open merge request show action' do
include Spec::Support::Helpers::Features::MergeRequestHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
let(:note) { create(:note_on_merge_request, project: project, noteable: open_merge_request) }
let(:open_merge_request) do
create(:merge_request, :opened, source_project: project, author: user)
end
before do
assign(:project, project)
assign(:merge_request, open_merge_request)
assign(:note, note)
assign(:noteable, open_merge_request)
assign(:notes, [])
assign(:pipelines, Ci::Pipeline.none)
assign(:issuable_sidebar, serialize_issuable_sidebar(user, project, open_merge_request))
preload_view_requirements(open_merge_request, note)
sign_in(user)
end
end
RSpec.shared_context 'closed merge request show action' do
include Devise::Test::ControllerHelpers
include ProjectForksHelper