Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
79ddf16358
commit
5bdbc604c8
39 changed files with 492 additions and 139 deletions
|
@ -35,6 +35,7 @@ import {
|
|||
UPDATE_NOTE_ERROR,
|
||||
designDeletionError,
|
||||
} from '../../utils/error_messages';
|
||||
import { trackDesignDetailView } from '../../utils/tracking';
|
||||
import { DESIGNS_ROUTE_NAME } from '../../router/constants';
|
||||
|
||||
export default {
|
||||
|
@ -257,8 +258,21 @@ export default {
|
|||
query: this.$route.query,
|
||||
});
|
||||
},
|
||||
trackEvent() {
|
||||
trackDesignDetailView(
|
||||
'issue-design-collection',
|
||||
this.$route.query.version || this.latestVersionId,
|
||||
this.isLatestVersion,
|
||||
);
|
||||
},
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
next(vm => {
|
||||
vm.trackEvent();
|
||||
});
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
this.trackEvent();
|
||||
this.closeCommentForm();
|
||||
next();
|
||||
},
|
||||
|
|
22
app/assets/javascripts/design_management/utils/tracking.js
Normal file
22
app/assets/javascripts/design_management/utils/tracking.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import Tracking from '~/tracking';
|
||||
|
||||
function assembleDesignPayload(payloadArr) {
|
||||
return {
|
||||
value: {
|
||||
'internal-object-refrerer': payloadArr[0],
|
||||
'version-number': payloadArr[1],
|
||||
'current-version': payloadArr[2],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Tracking Constants
|
||||
const DESIGN_TRACKING_PAGE_NAME = 'projects:issues:design';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function trackDesignDetailView(refrerer = '', designVersion = 1, latestVersion = false) {
|
||||
Tracking.event(DESIGN_TRACKING_PAGE_NAME, 'design_viewed', {
|
||||
label: 'design_viewed',
|
||||
...assembleDesignPayload([refrerer, designVersion, latestVersion]),
|
||||
});
|
||||
}
|
|
@ -86,11 +86,13 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
)
|
||||
build_params = issue_params.merge(
|
||||
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
|
||||
discussion_to_resolve: params[:discussion_to_resolve]
|
||||
discussion_to_resolve: params[:discussion_to_resolve],
|
||||
confidential: !!Gitlab::Utils.to_boolean(params[:issue][:confidential])
|
||||
)
|
||||
service = Issues::BuildService.new(project, current_user, build_params)
|
||||
|
||||
@issue = @noteable = service.execute
|
||||
|
||||
@merge_request_to_resolve_discussions_of = service.merge_request_to_resolve_discussions_of
|
||||
@discussion_to_resolve = service.discussions_to_resolve.first if params[:discussion_to_resolve]
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ module Projects::AlertManagementHelper
|
|||
'enable-alert-management-path' => edit_project_service_path(project, AlertsService),
|
||||
'empty-alert-svg-path' => image_path('illustrations/alert-management-empty-state.svg'),
|
||||
'user-can-enable-alert-management' => can?(current_user, :admin_project, project).to_s,
|
||||
'alert-management-enabled' => Feature.enabled?(:alert_management_minimal, project).to_s
|
||||
'alert-management-enabled' => (!!project.alerts_service_activated?).to_s
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -1436,20 +1436,12 @@ class Project < ApplicationRecord
|
|||
|
||||
# Expires various caches before a project is renamed.
|
||||
def expire_caches_before_rename(old_path)
|
||||
repo = Repository.new(old_path, self, shard: repository_storage)
|
||||
wiki = Repository.new("#{old_path}.wiki", self, shard: repository_storage, repo_type: Gitlab::GlRepository::WIKI)
|
||||
design = Repository.new("#{old_path}#{Gitlab::GlRepository::DESIGN.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::DESIGN)
|
||||
project_repo = Repository.new(old_path, self, shard: repository_storage)
|
||||
wiki_repo = Repository.new("#{old_path}#{Gitlab::GlRepository::WIKI.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::WIKI)
|
||||
design_repo = Repository.new("#{old_path}#{Gitlab::GlRepository::DESIGN.path_suffix}", self, shard: repository_storage, repo_type: Gitlab::GlRepository::DESIGN)
|
||||
|
||||
if repo.exists?
|
||||
repo.before_delete
|
||||
end
|
||||
|
||||
if wiki.exists?
|
||||
wiki.before_delete
|
||||
end
|
||||
|
||||
if design.exists?
|
||||
design.before_delete
|
||||
[project_repo, wiki_repo, design_repo].each do |repo|
|
||||
repo.before_delete if repo.exists?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ module Projects
|
|||
GENERIC_ALERT_SUMMARY_ANNOTATIONS = %w(monitoring_tool service hosts).freeze
|
||||
MARKDOWN_LINE_BREAK = " \n".freeze
|
||||
INCIDENT_LABEL_NAME = IncidentManagement::CreateIssueService::INCIDENT_LABEL[:title].freeze
|
||||
METRIC_TIME_WINDOW = 30.minutes
|
||||
|
||||
def full_title
|
||||
[environment_name, alert_title].compact.join(': ')
|
||||
|
@ -119,9 +120,63 @@ module Projects
|
|||
Array(hosts.value).join(' ')
|
||||
end
|
||||
|
||||
def metric_embed_for_alert; end
|
||||
def metric_embed_for_alert
|
||||
url = embed_url_for_gitlab_alert || embed_url_for_self_managed_alert
|
||||
|
||||
"\n[](#{url})" if url
|
||||
end
|
||||
|
||||
def embed_url_for_gitlab_alert
|
||||
return unless gitlab_alert
|
||||
|
||||
metrics_dashboard_project_prometheus_alert_url(
|
||||
project,
|
||||
gitlab_alert.prometheus_metric_id,
|
||||
environment_id: environment.id,
|
||||
**alert_embed_window_params(embed_time)
|
||||
)
|
||||
end
|
||||
|
||||
def embed_url_for_self_managed_alert
|
||||
return unless environment && full_query && title
|
||||
|
||||
metrics_dashboard_project_environment_url(
|
||||
project,
|
||||
environment,
|
||||
embed_json: dashboard_for_self_managed_alert.to_json,
|
||||
**alert_embed_window_params(embed_time)
|
||||
)
|
||||
end
|
||||
|
||||
def embed_time
|
||||
starts_at ? Time.rfc3339(starts_at) : Time.current
|
||||
end
|
||||
|
||||
def alert_embed_window_params(time)
|
||||
{
|
||||
start: format_embed_timestamp(time - METRIC_TIME_WINDOW),
|
||||
end: format_embed_timestamp(time + METRIC_TIME_WINDOW)
|
||||
}
|
||||
end
|
||||
|
||||
def format_embed_timestamp(time)
|
||||
time.utc.strftime('%FT%TZ')
|
||||
end
|
||||
|
||||
def dashboard_for_self_managed_alert
|
||||
{
|
||||
panel_groups: [{
|
||||
panels: [{
|
||||
type: 'line-graph',
|
||||
title: title,
|
||||
y_label: y_label,
|
||||
metrics: [{
|
||||
query_range: full_query
|
||||
}]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Projects::Prometheus::AlertPresenter.prepend_if_ee('EE::Projects::Prometheus::AlertPresenter')
|
||||
|
|
|
@ -65,15 +65,19 @@ module Issues
|
|||
private
|
||||
|
||||
def whitelisted_issue_params
|
||||
base_params = [:title, :description, :confidential]
|
||||
admin_params = [:milestone_id]
|
||||
|
||||
if can?(current_user, :admin_issue, project)
|
||||
params.slice(:title, :description, :milestone_id)
|
||||
params.slice(*(base_params + admin_params))
|
||||
else
|
||||
params.slice(:title, :description)
|
||||
params.slice(*base_params)
|
||||
end
|
||||
end
|
||||
|
||||
def build_issue_params
|
||||
issue_params_with_info_from_discussions.merge(whitelisted_issue_params)
|
||||
{ author: current_user }.merge(issue_params_with_info_from_discussions)
|
||||
.merge(whitelisted_issue_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Moves embedded metrics for Prometheus alerts to Core
|
||||
merge_request: 31203
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adds URL parameter for confidential new issue creation
|
||||
merge_request: 30250
|
||||
author:
|
||||
type: added
|
42
doc/.vale/gitlab/BadgeCapitalization.yml
Normal file
42
doc/.vale/gitlab/BadgeCapitalization.yml
Normal file
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
# Verifies that badges are not lower case, which won't render properly.
|
||||
#
|
||||
# For a list of all options, see https://errata-ai.github.io/vale/styles/
|
||||
extends: existence
|
||||
message: 'Badge "%s" must be capitalized.'
|
||||
link: https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges
|
||||
level: error
|
||||
scope: raw
|
||||
raw:
|
||||
- '(\*\*\(Core\)\*\*|'
|
||||
- '\*\*\(core\)\*\*|'
|
||||
- '\*\*\(Starter\)\*\*|'
|
||||
- '\*\*\(starter\)\*\*|'
|
||||
- '\*\*\(Premium\)\*\*|'
|
||||
- '\*\*\(premium\)\*\*|'
|
||||
- '\*\*\(Ultimate\)\*\*|'
|
||||
- '\*\*\(ultimate\)\*\*|'
|
||||
- '\*\*\(Core Only\)\*\*|'
|
||||
- '\*\*\(Core only\)\*\*|'
|
||||
- '\*\*\(core only\)\*\*|'
|
||||
- '\*\*\(Starter Only\)\*\*|'
|
||||
- '\*\*\(Starter only\)\*\*|'
|
||||
- '\*\*\(starter only\)\*\*|'
|
||||
- '\*\*\(Premium Only\)\*\*|'
|
||||
- '\*\*\(Premium only\)\*\*|'
|
||||
- '\*\*\(premium only\)\*\*|'
|
||||
- '\*\*\(Ultimate Only\)\*\*|'
|
||||
- '\*\*\(Ultimate only\)\*\*|'
|
||||
- '\*\*\(ultimate only\)\*\*|'
|
||||
- '\*\*\(Free Only\)\*\*|'
|
||||
- '\*\*\(Free only\)\*\*|'
|
||||
- '\*\*\(free only\)\*\*|'
|
||||
- '\*\*\(Bronze Only\)\*\*|'
|
||||
- '\*\*\(Bronze only\)\*\*|'
|
||||
- '\*\*\(bronze only\)\*\*|'
|
||||
- '\*\*\(Silver Only\)\*\*|'
|
||||
- '\*\*\(Silver only\)\*\*|'
|
||||
- '\*\*\(silver only\)\*\*|'
|
||||
- '\*\*\(Gold Only\)\*\*|'
|
||||
- '\*\*\(Gold only\)\*\*|'
|
||||
- '\*\*\(gold only\)\*\*)'
|
|
@ -140,7 +140,7 @@ The only changes to the site should be from the DAST scanner. Be aware that any
|
|||
changes that users, scheduled tasks, database changes, code changes, other pipelines, or other scanners make to
|
||||
the site during a scan could lead to inaccurate results.
|
||||
|
||||
### Authenticated scan
|
||||
### Authentication
|
||||
|
||||
It's also possible to authenticate the user before performing the DAST checks:
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 60 KiB |
BIN
doc/user/group/epics/img/epic_view_v13.0.png
Normal file
BIN
doc/user/group/epics/img/epic_view_v13.0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
|
@ -58,7 +58,7 @@ An epic's page contains the following tabs:
|
|||
- Hover over the total counts to see a breakdown of open and closed items.
|
||||
- **Roadmap**: a roadmap view of child epics which have start and due dates.
|
||||
|
||||
![epic view](img/epic_view_v12.3.png)
|
||||
![epic view](img/epic_view_v13.0.png)
|
||||
|
||||
## Adding an issue to an epic
|
||||
|
||||
|
@ -75,6 +75,7 @@ the issue is automatically unlinked from its current parent.
|
|||
|
||||
To add an issue to an epic:
|
||||
|
||||
1. Click the **Add** dropdown button.
|
||||
1. Click **Add an issue**.
|
||||
1. Identify the issue to be added, using either of the following methods:
|
||||
- Paste the link of the issue.
|
||||
|
@ -91,7 +92,7 @@ Creating an issue from an epic enables you to maintain focus on the broader cont
|
|||
|
||||
To create an issue from an epic:
|
||||
|
||||
1. On the epic's page, under **Epics and Issues**, click the arrow next to **Add an issue** and select **Create new issue**.
|
||||
1. On the epic's page, under **Epics and Issues**, click the **Add** dropdown button and select **Create new issue**.
|
||||
1. Under **Title**, enter the title for the new issue.
|
||||
1. From the **Project** dropdown, select the project in which the issue should be created.
|
||||
1. Click **Create issue**.
|
||||
|
@ -128,6 +129,7 @@ the maximum depth being 5.
|
|||
|
||||
To add a child epic to an epic:
|
||||
|
||||
1. Click the **Add** dropdown button.
|
||||
1. Click **Add an epic**.
|
||||
1. Identify the epic to be added, using either of the following methods:
|
||||
- Paste the link of the epic.
|
||||
|
|
|
@ -92,9 +92,17 @@ field values using query string parameters in a URL. This is useful for embeddin
|
|||
a URL in an external HTML page, and also certain scenarios where you want the user to
|
||||
create an issue with certain fields prefilled.
|
||||
|
||||
The title, description, and description template fields can be prefilled using
|
||||
this method. You cannot pre-fill both the description and description template fields
|
||||
in the same URL (since a description template also populates the description field).
|
||||
The title, description, description template, and confidential fields can be prefilled
|
||||
using this method. You cannot pre-fill both the description and description template
|
||||
fields in the same URL (since a description template also populates the description
|
||||
field).
|
||||
|
||||
| Field | URL Parameter Name | Notes |
|
||||
|----------------------|-----------------------|-------------------------------------------------------|
|
||||
| title | `issue[title]` | |
|
||||
| description | `issue[description]` | |
|
||||
| description template | `issuable_template` | |
|
||||
| confidential | `issue[confidential]` | Parameter value must be `true` to set to confidential |
|
||||
|
||||
Follow these examples to form your new issue URL with prefilled fields.
|
||||
|
||||
|
@ -102,6 +110,8 @@ Follow these examples to form your new issue URL with prefilled fields.
|
|||
and a pre-filled description, the URL would be `https://gitlab.com/gitlab-org/gitlab-foss/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea`
|
||||
- For a new issue in the GitLab Community Edition project with a pre-filled title
|
||||
and a pre-filled description template, the URL would be `https://gitlab.com/gitlab-org/gitlab-foss/issues/new?issue[title]=Validate%20new%20concept&issuable_template=Research%20proposal`
|
||||
- For a new issue in the GitLab Community Edition project with a pre-filled title,
|
||||
a pre-filled description, and the confidential flag set, the URL would be `https://gitlab.com/gitlab-org/gitlab-foss/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea&issue[confidential]=true`
|
||||
|
||||
## Moving Issues
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def parse_json(response_body)
|
||||
Gitlab::Json.parse(response_body)
|
||||
Gitlab::Json.parse(response_body, legacy_mode: true)
|
||||
rescue JSON::ParserError
|
||||
raise PrometheusClient::Error, 'Parsing response failed'
|
||||
end
|
||||
|
|
|
@ -49,7 +49,7 @@ module Mattermost
|
|||
end
|
||||
|
||||
def json_response(response)
|
||||
json_response = Gitlab::Json.parse(response.body)
|
||||
json_response = Gitlab::Json.parse(response.body, legacy_mode: true)
|
||||
|
||||
unless response.success?
|
||||
raise Mattermost::ClientError.new(json_response['message'] || 'Undefined error')
|
||||
|
|
|
@ -1191,6 +1191,9 @@ msgstr ""
|
|||
msgid "Add a link"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add a new issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add a numbered list"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1206,7 +1209,7 @@ msgstr ""
|
|||
msgid "Add an SSH key"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add an existing issue to the epic."
|
||||
msgid "Add an existing issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add an issue"
|
||||
|
@ -6153,9 +6156,6 @@ msgstr ""
|
|||
msgid "Create a new issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create a new issue and add it to the epic."
|
||||
msgstr ""
|
||||
|
||||
msgid "Create a new repository"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6165,9 +6165,6 @@ msgstr ""
|
|||
msgid "Create an account using:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create an issue"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create an issue. Issues are created for each alert triggered."
|
||||
msgstr ""
|
||||
|
||||
|
@ -8324,10 +8321,10 @@ msgstr ""
|
|||
msgid "Epics, Issues, and Merge Requests"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|Add an epic"
|
||||
msgid "Epics|Add a new epic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|Add an existing epic as a child epic."
|
||||
msgid "Epics|Add an existing epic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|An error occurred while saving the %{epicDateType} date"
|
||||
|
@ -8339,12 +8336,6 @@ msgstr ""
|
|||
msgid "Epics|Are you sure you want to remove %{bStart}%{targetIssueTitle}%{bEnd} from %{bStart}%{parentEpicTitle}%{bEnd}?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|Create an epic within this group and add it as a child epic."
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|Create new epic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epics|How can I solve this?"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -73,6 +73,12 @@ function rspec_paralellized_job() {
|
|||
export KNAPSACK_LOG_LEVEL="debug"
|
||||
export KNAPSACK_REPORT_PATH="knapsack/${report_name}_report.json"
|
||||
|
||||
# There's a bug where artifacts are sometimes not downloaded. Since specs can run without the Knapsack report, we can
|
||||
# handle the missing artifact gracefully here. See https://gitlab.com/gitlab-org/gitlab/-/issues/212349.
|
||||
if [[ ! -f "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" ]]; then
|
||||
echo "{}" > "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}"
|
||||
fi
|
||||
|
||||
cp "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}" "${KNAPSACK_REPORT_PATH}"
|
||||
|
||||
if [[ -z "${KNAPSACK_TEST_FILE_PATTERN}" ]]; then
|
||||
|
|
|
@ -187,6 +187,33 @@ describe Projects::IssuesController do
|
|||
expect(assigns(:issue)).to be_a_new(Issue)
|
||||
end
|
||||
|
||||
where(:conf_value, :conf_result) do
|
||||
[
|
||||
[true, true],
|
||||
['true', true],
|
||||
['TRUE', true],
|
||||
[false, false],
|
||||
['false', false],
|
||||
['FALSE', false]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'sets the confidential flag to the expected value' do
|
||||
get :new, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
issue: {
|
||||
confidential: conf_value
|
||||
}
|
||||
}
|
||||
|
||||
assigned_issue = assigns(:issue)
|
||||
expect(assigned_issue).to be_a_new(Issue)
|
||||
expect(assigned_issue.confidential).to eq conf_result
|
||||
end
|
||||
end
|
||||
|
||||
it 'fills in an issue for a merge request' do
|
||||
project_with_repository = create(:project, :repository)
|
||||
project_with_repository.add_developer(user)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import App from '~/design_management/components/app.vue';
|
||||
import Designs from '~/design_management/pages/index.vue';
|
||||
|
@ -11,74 +12,66 @@ import {
|
|||
} from '~/design_management/router/constants';
|
||||
import '~/commons/bootstrap';
|
||||
|
||||
function factory(routeArg) {
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueRouter);
|
||||
|
||||
window.gon = { sprite_icons: '' };
|
||||
|
||||
const router = createRouter('/');
|
||||
if (routeArg !== undefined) {
|
||||
router.push(routeArg);
|
||||
}
|
||||
|
||||
return mount(App, {
|
||||
localVue,
|
||||
router,
|
||||
mocks: {
|
||||
$apollo: {
|
||||
queries: {
|
||||
designs: { loading: true },
|
||||
design: { loading: true },
|
||||
permissions: { loading: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
jest.mock('mousetrap', () => ({
|
||||
bind: jest.fn(),
|
||||
unbind: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Design management router', () => {
|
||||
let vm;
|
||||
let router;
|
||||
|
||||
function factory() {
|
||||
const localVue = createLocalVue();
|
||||
|
||||
localVue.use(VueRouter);
|
||||
|
||||
window.gon = { sprite_icons: '' };
|
||||
|
||||
router = createRouter('/');
|
||||
|
||||
vm = mount(App, {
|
||||
localVue,
|
||||
router,
|
||||
mocks: {
|
||||
$apollo: {
|
||||
queries: {
|
||||
designs: { loading: true },
|
||||
design: { loading: true },
|
||||
permissions: { loading: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
factory();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.destroy();
|
||||
|
||||
router.app.$destroy();
|
||||
window.location.hash = '';
|
||||
});
|
||||
|
||||
describe.each([['/'], [{ name: ROOT_ROUTE_NAME }]])('root route', pushArg => {
|
||||
describe.each([['/'], [{ name: ROOT_ROUTE_NAME }]])('root route', routeArg => {
|
||||
it('pushes home component', () => {
|
||||
router.push(pushArg);
|
||||
const wrapper = factory(routeArg);
|
||||
|
||||
expect(vm.find(Designs).exists()).toBe(true);
|
||||
expect(wrapper.find(Designs).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([['/designs'], [{ name: DESIGNS_ROUTE_NAME }]])('designs route', pushArg => {
|
||||
describe.each([['/designs'], [{ name: DESIGNS_ROUTE_NAME }]])('designs route', routeArg => {
|
||||
it('pushes designs root component', () => {
|
||||
router.push(pushArg);
|
||||
const wrapper = factory(routeArg);
|
||||
|
||||
expect(vm.find(Designs).exists()).toBe(true);
|
||||
expect(wrapper.find(Designs).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([['/designs/1'], [{ name: DESIGN_ROUTE_NAME, params: { id: '1' } }]])(
|
||||
'designs detail route',
|
||||
pushArg => {
|
||||
routeArg => {
|
||||
it('pushes designs detail component', () => {
|
||||
router.push(pushArg);
|
||||
const wrapper = factory(routeArg);
|
||||
|
||||
return vm.vm.$nextTick().then(() => {
|
||||
const detail = vm.find(DesignDetail);
|
||||
return nextTick().then(() => {
|
||||
const detail = wrapper.find(DesignDetail);
|
||||
expect(detail.exists()).toBe(true);
|
||||
expect(detail.props('id')).toEqual('1');
|
||||
});
|
||||
|
|
51
spec/frontend/design_management/utils/tracking_spec.js
Normal file
51
spec/frontend/design_management/utils/tracking_spec.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
import { trackDesignDetailView } from '~/design_management/utils/tracking';
|
||||
|
||||
function getTrackingSpy(key) {
|
||||
return mockTracking(key, undefined, jest.spyOn);
|
||||
}
|
||||
|
||||
describe('Tracking Events', () => {
|
||||
describe('trackDesignDetailView', () => {
|
||||
const eventKey = 'projects:issues:design';
|
||||
const eventName = 'design_viewed';
|
||||
|
||||
it('trackDesignDetailView fires a tracking event when called', () => {
|
||||
const trackingSpy = getTrackingSpy(eventKey);
|
||||
|
||||
trackDesignDetailView();
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
eventKey,
|
||||
eventName,
|
||||
expect.objectContaining({
|
||||
label: eventName,
|
||||
value: {
|
||||
'internal-object-refrerer': '',
|
||||
'version-number': 1,
|
||||
'current-version': false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('trackDesignDetailView allows to customize the value payload', () => {
|
||||
const trackingSpy = getTrackingSpy(eventKey);
|
||||
|
||||
trackDesignDetailView('from-a-test', 100, true);
|
||||
|
||||
expect(trackingSpy).toHaveBeenCalledWith(
|
||||
eventKey,
|
||||
eventName,
|
||||
expect.objectContaining({
|
||||
label: eventName,
|
||||
value: {
|
||||
'internal-object-refrerer': 'from-a-test',
|
||||
'version-number': 100,
|
||||
'current-version': true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,7 +10,7 @@ import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
|
|||
import NoteForm from '~/notes/components/note_form.vue';
|
||||
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
|
||||
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
|
||||
import diffFileMockData from '../../../javascripts/diffs/mock_data/diff_file';
|
||||
import diffFileMockData from '../mock_data/diff_file';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
|
|
@ -25,7 +25,7 @@ describe('Code component', () => {
|
|||
beforeEach(done => {
|
||||
vm = setupComponent(json.cells[0]);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ describe('Code component', () => {
|
|||
beforeEach(done => {
|
||||
vm = setupComponent(json.cells[2]);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ describe('Code component', () => {
|
|||
|
||||
vm = setupComponent(cell);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -24,7 +24,7 @@ describe('Markdown component', () => {
|
|||
});
|
||||
vm.$mount();
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -25,7 +25,7 @@ describe('Output component', () => {
|
|||
beforeEach(done => {
|
||||
createComponent(json.cells[2].outputs[0]);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -43,7 +43,7 @@ describe('Output component', () => {
|
|||
beforeEach(done => {
|
||||
createComponent(json.cells[3].outputs[0]);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ describe('Output component', () => {
|
|||
beforeEach(done => {
|
||||
createComponent(json.cells[5].outputs[0]);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -87,7 +87,7 @@ describe('Output component', () => {
|
|||
beforeEach(done => {
|
||||
createComponent(json.cells[6].outputs[0]);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -104,7 +104,7 @@ describe('Output component', () => {
|
|||
it("renders as plain text when doesn't recognise other types", done => {
|
||||
createComponent(json.cells[7].outputs[0]);
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
expect(vm.$el.querySelector('pre')).not.toBeNull();
|
||||
expect(vm.$el.textContent.trim()).toContain('testing');
|
||||
|
|
@ -16,7 +16,7 @@ describe('Prompt component', () => {
|
|||
});
|
||||
vm.$mount();
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -40,7 +40,7 @@ describe('Prompt component', () => {
|
|||
});
|
||||
vm.$mount();
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -22,7 +22,7 @@ describe('Notebook component', () => {
|
|||
});
|
||||
vm.$mount();
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -42,7 +42,7 @@ describe('Notebook component', () => {
|
|||
});
|
||||
vm.$mount();
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -74,7 +74,7 @@ describe('Notebook component', () => {
|
|||
});
|
||||
vm.$mount();
|
||||
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
done();
|
||||
});
|
||||
});
|
|
@ -13,6 +13,8 @@ describe Projects::AlertManagementHelper do
|
|||
let(:user_can_enable_alert_management) { true }
|
||||
let(:setting_path) { edit_project_service_path(project, AlertsService) }
|
||||
|
||||
subject(:data) { helper.alert_management_data(current_user, project) }
|
||||
|
||||
before do
|
||||
allow(helper)
|
||||
.to receive(:can?)
|
||||
|
@ -27,11 +29,33 @@ describe Projects::AlertManagementHelper do
|
|||
'enable-alert-management-path' => setting_path,
|
||||
'empty-alert-svg-path' => match_asset_path('/assets/illustrations/alert-management-empty-state.svg'),
|
||||
'user-can-enable-alert-management' => 'true',
|
||||
'alert-management-enabled' => 'true'
|
||||
'alert-management-enabled' => 'false'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with alerts service' do
|
||||
let_it_be(:alerts_service) { create(:alerts_service, project: project) }
|
||||
|
||||
context 'when alerts service is active' do
|
||||
it 'enables alert management' do
|
||||
expect(data).to include(
|
||||
'alert-management-enabled' => 'true'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when alerts service is inactive' do
|
||||
it 'disables alert management' do
|
||||
alerts_service.update(active: false)
|
||||
|
||||
expect(data).to include(
|
||||
'alert-management-enabled' => 'false'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have requisite enablement permissions' do
|
||||
let(:user_can_enable_alert_management) { false }
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
// No new code should be added to this file. Instead, modify the
|
||||
// file this one re-exports from. For more detail about why, see:
|
||||
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
|
||||
|
||||
export { default } from '../../frontend/diffs/create_diffs_store';
|
|
@ -1,5 +0,0 @@
|
|||
// No new code should be added to this file. Instead, modify the
|
||||
// file this one re-exports from. For more detail about why, see:
|
||||
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
|
||||
|
||||
export { default } from '../../../frontend/diffs/mock_data/diff_discussions';
|
|
@ -1,5 +0,0 @@
|
|||
// No new code should be added to this file. Instead, modify the
|
||||
// file this one re-exports from. For more detail about why, see:
|
||||
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
|
||||
|
||||
export { default } from '../../../frontend/diffs/mock_data/diff_file';
|
|
@ -1,5 +0,0 @@
|
|||
// No new code should be added to this file. Instead, modify the
|
||||
// file this one re-exports from. For more detail about why, see:
|
||||
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
|
||||
|
||||
export { default } from '../../../frontend/diffs/mock_data/diff_file_unreadable';
|
|
@ -1,7 +0,0 @@
|
|||
// No new code should be added to this file. Instead, modify the
|
||||
// file this one re-exports from. For more detail about why, see:
|
||||
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
|
||||
|
||||
import getDiffWithCommit from '../../../frontend/diffs/mock_data/diff_with_commit';
|
||||
|
||||
export default getDiffWithCommit;
|
|
@ -1,7 +0,0 @@
|
|||
// No new code should be added to this file. Instead, modify the
|
||||
// file this one re-exports from. For more detail about why, see:
|
||||
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
|
||||
|
||||
import diffsMockData from '../../../frontend/diffs/mock_data/merge_request_diffs';
|
||||
|
||||
export default diffsMockData;
|
|
@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
|
|||
import initMRPage from '~/mr_notes/index';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { userDataMock, notesDataMock, noteableDataMock } from '../../frontend/notes/mock_data';
|
||||
import diffFileMockData from '../diffs/mock_data/diff_file';
|
||||
import diffFileMockData from '../../frontend/diffs/mock_data/diff_file';
|
||||
|
||||
export default function initVueMRPage() {
|
||||
const mrTestEl = document.createElement('div');
|
||||
|
|
|
@ -152,6 +152,148 @@ describe Projects::Prometheus::AlertPresenter do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with embedded metrics' do
|
||||
let(:starts_at) { '2018-03-12T09:06:00Z' }
|
||||
|
||||
shared_examples_for 'markdown with metrics embed' do
|
||||
let(:expected_markdown) do
|
||||
<<~MARKDOWN.chomp
|
||||
#### Summary
|
||||
|
||||
**Start time:** #{presenter.starts_at}#{markdown_line_break}
|
||||
**full_query:** `avg(metric) > 1.0`
|
||||
|
||||
[](#{url})
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
context 'without a starting time available' do
|
||||
around do |example|
|
||||
Timecop.freeze(starts_at) { example.run }
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected_markdown) }
|
||||
end
|
||||
|
||||
context 'with a starting time available' do
|
||||
before do
|
||||
payload['startsAt'] = starts_at
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected_markdown) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'for gitlab-managed prometheus alerts' do
|
||||
let(:gitlab_alert) { create(:prometheus_alert, project: project) }
|
||||
let(:metric_id) { gitlab_alert.prometheus_metric_id }
|
||||
let(:env_id) { gitlab_alert.environment_id }
|
||||
|
||||
before do
|
||||
payload['labels'] = { 'gitlab_alert_id' => metric_id }
|
||||
end
|
||||
|
||||
let(:url) { "http://localhost/#{project.full_path}/prometheus/alerts/#{metric_id}/metrics_dashboard?end=2018-03-12T09%3A36%3A00Z&environment_id=#{env_id}&start=2018-03-12T08%3A36%3A00Z" }
|
||||
|
||||
it_behaves_like 'markdown with metrics embed'
|
||||
end
|
||||
|
||||
context 'for alerts from a self-managed prometheus' do
|
||||
let!(:environment) { create(:environment, project: project, name: 'production') }
|
||||
let(:url) { "http://localhost/#{project.full_path}/-/environments/#{environment.id}/metrics_dashboard?embed_json=#{CGI.escape(embed_content.to_json)}&end=2018-03-12T09%3A36%3A00Z&start=2018-03-12T08%3A36%3A00Z" }
|
||||
|
||||
let(:title) { 'title' }
|
||||
let(:y_label) { 'y_label' }
|
||||
let(:query) { 'avg(metric) > 1.0' }
|
||||
let(:embed_content) do
|
||||
{
|
||||
panel_groups: [{
|
||||
panels: [{
|
||||
type: 'line-graph',
|
||||
title: title,
|
||||
y_label: y_label,
|
||||
metrics: [{ query_range: query }]
|
||||
}]
|
||||
}]
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
# Setup embed time range
|
||||
payload['startsAt'] = starts_at
|
||||
|
||||
# Setup query
|
||||
payload['generatorURL'] = "http://host?g0.expr=#{CGI.escape(query)}"
|
||||
|
||||
# Setup environment
|
||||
payload['labels'] ||= {}
|
||||
payload['labels']['gitlab_environment_name'] = 'production'
|
||||
|
||||
# Setup chart title & axis labels
|
||||
payload['annotations'] ||= {}
|
||||
payload['annotations']['title'] = 'title'
|
||||
payload['annotations']['gitlab_y_label'] = 'y_label'
|
||||
end
|
||||
|
||||
it_behaves_like 'markdown with metrics embed'
|
||||
|
||||
context 'without y_label' do
|
||||
let(:y_label) { title }
|
||||
|
||||
before do
|
||||
payload['annotations'].delete('gitlab_y_label')
|
||||
end
|
||||
|
||||
it_behaves_like 'markdown with metrics embed'
|
||||
end
|
||||
|
||||
context 'when not enough information is present for an embed' do
|
||||
let(:expected_markdown) do
|
||||
<<~MARKDOWN.chomp
|
||||
#### Summary
|
||||
|
||||
**Start time:** #{presenter.starts_at}#{markdown_line_break}
|
||||
**full_query:** `avg(metric) > 1.0`
|
||||
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
context 'without title' do
|
||||
before do
|
||||
payload['annotations'].delete('title')
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected_markdown) }
|
||||
end
|
||||
|
||||
context 'without environment' do
|
||||
before do
|
||||
payload['labels'].delete('gitlab_environment_name')
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected_markdown) }
|
||||
end
|
||||
|
||||
context 'without full_query' do
|
||||
let(:expected_markdown) do
|
||||
<<~MARKDOWN.chomp
|
||||
#### Summary
|
||||
|
||||
**Start time:** #{presenter.starts_at}
|
||||
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
before do
|
||||
payload.delete('generatorURL')
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected_markdown) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_performance_dashboard_link?' do
|
||||
|
|
Loading…
Reference in a new issue