Skip to content
Snippets Groups Projects
Commit c2367afb authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 51a95129
No related branches found
No related tags found
No related merge requests found
Showing
with 601 additions and 436 deletions
---
title: Fix pipeline details page initialisation on invalid pipeline
merge_request: 25302
author: Fabio Huser
type: fixed
Loading
@@ -2770,6 +2770,16 @@ type Group {
Loading
@@ -2770,6 +2770,16 @@ type Group {
""" """
avatarUrl: String avatarUrl: String
   
"""
A single board of the group
"""
board(
"""
Find a board by its ID
"""
id: ID
): Board
""" """
Boards of the group Boards of the group
""" """
Loading
@@ -2789,6 +2799,11 @@ type Group {
Loading
@@ -2789,6 +2799,11 @@ type Group {
""" """
first: Int first: Int
   
"""
Find a board by its ID
"""
id: ID
""" """
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
Loading
@@ -5254,6 +5269,16 @@ type Project {
Loading
@@ -5254,6 +5269,16 @@ type Project {
""" """
avatarUrl: String avatarUrl: String
   
"""
A single board of the project
"""
board(
"""
Find a board by its ID
"""
id: ID
): Board
""" """
Boards of the project Boards of the project
""" """
Loading
@@ -5273,6 +5298,11 @@ type Project {
Loading
@@ -5273,6 +5298,11 @@ type Project {
""" """
first: Int first: Int
   
"""
Find a board by its ID
"""
id: ID
""" """
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
Loading
Loading
Loading
@@ -368,10 +368,43 @@
Loading
@@ -368,10 +368,43 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "board",
"description": "A single board of the project",
"args": [
{
"name": "id",
"description": "Find a board by its ID",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Board",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "boards", "name": "boards",
"description": "Boards of the project", "description": "Boards of the project",
"args": [ "args": [
{
"name": "id",
"description": "Find a board by its ID",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
Loading
@@ -3175,10 +3208,43 @@
Loading
@@ -3175,10 +3208,43 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "board",
"description": "A single board of the group",
"args": [
{
"name": "id",
"description": "Find a board by its ID",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Board",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "boards", "name": "boards",
"description": "Boards of the group", "description": "Boards of the group",
"args": [ "args": [
{
"name": "id",
"description": "Find a board by its ID",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
Loading
Loading
Loading
@@ -426,6 +426,7 @@ Autogenerated return type of EpicTreeReorder
Loading
@@ -426,6 +426,7 @@ Autogenerated return type of EpicTreeReorder
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `autoDevopsEnabled` | Boolean | Indicates whether Auto DevOps is enabled for all projects within this group | | `autoDevopsEnabled` | Boolean | Indicates whether Auto DevOps is enabled for all projects within this group |
| `avatarUrl` | String | Avatar URL of the group | | `avatarUrl` | String | Avatar URL of the group |
| `board` | Board | A single board of the group |
| `description` | String | Description of the namespace | | `description` | String | Description of the namespace |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `emailsDisabled` | Boolean | Indicates if a group has email notifications disabled | | `emailsDisabled` | Boolean | Indicates if a group has email notifications disabled |
Loading
@@ -801,6 +802,7 @@ Information about pagination in a connection.
Loading
@@ -801,6 +802,7 @@ Information about pagination in a connection.
| `archived` | Boolean | Indicates the archived status of the project | | `archived` | Boolean | Indicates the archived status of the project |
| `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically | | `autocloseReferencedIssues` | Boolean | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically |
| `avatarUrl` | String | URL to avatar image file of the project | | `avatarUrl` | String | URL to avatar image file of the project |
| `board` | Board | A single board of the project |
| `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry | | `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry |
| `createdAt` | Time | Timestamp of the project creation | | `createdAt` | Time | Timestamp of the project creation |
| `description` | String | Short description of the project | | `description` | String | Short description of the project |
Loading
Loading
Loading
@@ -2,7 +2,7 @@
Loading
@@ -2,7 +2,7 @@
   
## Issue tracker guidelines ## Issue tracker guidelines
   
**[Search the issue tracker](https://gitlab.com/gitlab-org/gitlab-foss/issues)** for similar entries before **[Search the issue tracker](https://gitlab.com/gitlab-org/gitlab/issues)** for similar entries before
submitting your own, there's a good chance somebody else had the same issue or submitting your own, there's a good chance somebody else had the same issue or
feature proposal. Show your support with an award emoji and/or join the feature proposal. Show your support with an award emoji and/or join the
discussion. discussion.
Loading
@@ -35,7 +35,7 @@ project.
Loading
@@ -35,7 +35,7 @@ project.
## Labels ## Labels
   
To allow for asynchronous issue handling, we use [milestones](https://gitlab.com/groups/gitlab-org/-/milestones) To allow for asynchronous issue handling, we use [milestones](https://gitlab.com/groups/gitlab-org/-/milestones)
and [labels](https://gitlab.com/gitlab-org/gitlab-foss/-/labels). Leads and product managers handle most of the and [labels](https://gitlab.com/gitlab-org/gitlab/-/labels). Leads and product managers handle most of the
scheduling into milestones. Labelling is a task for everyone. scheduling into milestones. Labelling is a task for everyone.
   
Most issues will have labels for at least one of the following: Most issues will have labels for at least one of the following:
Loading
@@ -53,7 +53,7 @@ Most issues will have labels for at least one of the following:
Loading
@@ -53,7 +53,7 @@ Most issues will have labels for at least one of the following:
- Severity: ~`S1`, `~S2`, `~S3`, `~S4` - Severity: ~`S1`, `~S2`, `~S3`, `~S4`
   
All labels, their meaning and priority are defined on the All labels, their meaning and priority are defined on the
[labels page](https://gitlab.com/gitlab-org/gitlab-foss/-/labels). [labels page](https://gitlab.com/gitlab-org/gitlab/-/labels).
   
If you come across an issue that has none of these, and you're allowed to set If you come across an issue that has none of these, and you're allowed to set
labels, you can _always_ add the team and type, and often also the subject. labels, you can _always_ add the team and type, and often also the subject.
Loading
@@ -372,14 +372,11 @@ A recent example of this was the issue for
Loading
@@ -372,14 +372,11 @@ A recent example of this was the issue for
   
## Feature proposals ## Feature proposals
   
To create a feature proposal for CE, open an issue on the To create a feature proposal, open an issue on the
[issue tracker of CE](https://gitlab.com/gitlab-org/gitlab-foss/issues). [issue tracker](https://gitlab.com/gitlab-org/gitlab/issues).
For feature proposals for EE, open an issue on the
[issue tracker of EE](https://gitlab.com/gitlab-org/gitlab/issues).
   
In order to help track the feature proposals, we have created a In order to help track the feature proposals, we have created a
[`feature`](https://gitlab.com/gitlab-org/gitlab-foss/issues?label_name=feature) label. For the time being, users that are not members [`feature`](https://gitlab.com/gitlab-org/gitlab/issues?label_name=feature) label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team](https://about.gitlab.com/community/core-team/) of the project cannot add labels. You can instead ask one of the [core team](https://about.gitlab.com/community/core-team/)
members to add the label ~feature to the issue or add the following members to add the label ~feature to the issue or add the following
code snippet right after your description in a new line: `~feature`. code snippet right after your description in a new line: `~feature`.
Loading
@@ -441,7 +438,7 @@ addressed.
Loading
@@ -441,7 +438,7 @@ addressed.
## Technical and UX debt ## Technical and UX debt
   
In order to track things that can be improved in GitLab's codebase, In order to track things that can be improved in GitLab's codebase,
we use the ~"technical debt" label in [GitLab's issue tracker](https://gitlab.com/gitlab-org/gitlab-foss/issues). we use the ~"technical debt" label in [GitLab's issue tracker](https://gitlab.com/gitlab-org/gitlab/issues).
For missed user experience requirements, we use the ~"UX debt" label. For missed user experience requirements, we use the ~"UX debt" label.
   
These labels should be added to issues that describe things that can be improved, These labels should be added to issues that describe things that can be improved,
Loading
Loading
Loading
@@ -472,7 +472,7 @@ end
Loading
@@ -472,7 +472,7 @@ end
``` ```
   
If a computed update is needed, the value can be wrapped in `Arel.sql`, so Arel If a computed update is needed, the value can be wrapped in `Arel.sql`, so Arel
treats it as an SQL literal. It's also a required deprecation for [Rails 6](https://gitlab.com/gitlab-org/gitlab-foss/issues/61451). treats it as an SQL literal. It's also a required deprecation for [Rails 6](https://gitlab.com/gitlab-org/gitlab/issues/28497).
   
The below example is the same as the one above, but The below example is the same as the one above, but
the value is set to the product of the `bar` and `baz` columns: the value is set to the product of the `bar` and `baz` columns:
Loading
Loading
Loading
@@ -30,11 +30,11 @@ People are saying multiple inheritance is bad. Mixing multiple modules with
Loading
@@ -30,11 +30,11 @@ People are saying multiple inheritance is bad. Mixing multiple modules with
multiple instance variables scattering everywhere suffer from the same issue. multiple instance variables scattering everywhere suffer from the same issue.
The same applies to `ActiveSupport::Concern`. See: The same applies to `ActiveSupport::Concern`. See:
[Consider replacing concerns with dedicated classes & composition]( [Consider replacing concerns with dedicated classes & composition](
https://gitlab.com/gitlab-org/gitlab-foss/issues/23786) https://gitlab.com/gitlab-org/gitlab/issues/16270)
   
There's also a similar idea: There's also a similar idea:
[Use decorators and interface segregation to solve overgrowing models problem]( [Use decorators and interface segregation to solve overgrowing models problem](
https://gitlab.com/gitlab-org/gitlab-foss/issues/13484) https://gitlab.com/gitlab-org/gitlab/issues/14235)
   
Note that `included` doesn't solve the whole issue. They define the Note that `included` doesn't solve the whole issue. They define the
dependencies, but they still allow each modules to talk implicitly via the dependencies, but they still allow each modules to talk implicitly via the
Loading
Loading
Loading
@@ -25,7 +25,7 @@ by [`Namespaces#with_statistics`](https://gitlab.com/gitlab-org/gitlab/blob/4ab5
Loading
@@ -25,7 +25,7 @@ by [`Namespaces#with_statistics`](https://gitlab.com/gitlab-org/gitlab/blob/4ab5
   
Additionally, the pattern that is currently used to update the project statistics Additionally, the pattern that is currently used to update the project statistics
(the callback) doesn't scale adequately. It is currently one of the largest (the callback) doesn't scale adequately. It is currently one of the largest
[database queries transactions on production](https://gitlab.com/gitlab-org/gitlab-foss/issues/62488) [database queries transactions on production](https://gitlab.com/gitlab-org/gitlab/issues/29070)
that takes the most time overall. We can't add one more query to it as that takes the most time overall. We can't add one more query to it as
it will increase the transaction's length. it will increase the transaction's length.
   
Loading
@@ -142,7 +142,7 @@ but we refresh them through Sidekiq jobs and in different transactions:
Loading
@@ -142,7 +142,7 @@ but we refresh them through Sidekiq jobs and in different transactions:
1. Create a second table (`namespace_aggregation_schedules`) with two columns `id` and `namespace_id`. 1. Create a second table (`namespace_aggregation_schedules`) with two columns `id` and `namespace_id`.
1. Whenever the statistics of a project changes, insert a row into `namespace_aggregation_schedules` 1. Whenever the statistics of a project changes, insert a row into `namespace_aggregation_schedules`
- We don't insert a new row if there's already one related to the root namespace. - We don't insert a new row if there's already one related to the root namespace.
- Keeping in mind the length of the transaction that involves updating `project_statistics`(<https://gitlab.com/gitlab-org/gitlab-foss/issues/62488>), the insertion should be done in a different transaction and through a Sidekiq Job. - Keeping in mind the length of the transaction that involves updating `project_statistics`(<https://gitlab.com/gitlab-org/gitlab/issues/29070>), the insertion should be done in a different transaction and through a Sidekiq Job.
1. After inserting the row, we schedule another worker to be executed async at two different moments: 1. After inserting the row, we schedule another worker to be executed async at two different moments:
- One enqueued for immediate execution and another one scheduled in `1.5h` hours. - One enqueued for immediate execution and another one scheduled in `1.5h` hours.
- We only schedule the jobs, if we can obtain a `1.5h` lease on Redis on a key based on the root namespace ID. - We only schedule the jobs, if we can obtain a `1.5h` lease on Redis on a key based on the root namespace ID.
Loading
@@ -162,7 +162,7 @@ This implementation has the following benefits:
Loading
@@ -162,7 +162,7 @@ This implementation has the following benefits:
   
The only downside of this approach is that namespaces' statistics are updated up to `1.5` hours after the change is done, The only downside of this approach is that namespaces' statistics are updated up to `1.5` hours after the change is done,
which means there's a time window in which the statistics are inaccurate. Because we're still not which means there's a time window in which the statistics are inaccurate. Because we're still not
[enforcing storage limits](https://gitlab.com/gitlab-org/gitlab-foss/issues/30421), this is not a major problem. [enforcing storage limits](https://gitlab.com/gitlab-org/gitlab/issues/17664), this is not a major problem.
   
## Conclusion ## Conclusion
   
Loading
Loading
Loading
@@ -45,7 +45,7 @@ They are available **per project** for GitLab Community Edition,
Loading
@@ -45,7 +45,7 @@ They are available **per project** for GitLab Community Edition,
and **per project and per group** for **GitLab Enterprise Edition**. and **per project and per group** for **GitLab Enterprise Edition**.
   
Navigate to the webhooks page by going to your project's Navigate to the webhooks page by going to your project's
**Settings ➔ Integrations**. **Settings ➔ Webhooks**.
   
## Maximum number of webhooks (per tier) ## Maximum number of webhooks (per tier)
   
Loading
Loading
Loading
@@ -6,8 +6,8 @@ type: reference
Loading
@@ -6,8 +6,8 @@ type: reference
   
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15643) in GitLab 11.7. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15643) in GitLab 11.7.
   
GitLab supports using [Git push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt) GitLab supports using client-side [Git push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt)
to perform various actions at the same time as pushing changes. to perform various actions at the same time as pushing changes. Additionally, [Push Rules](https://docs.gitlab.com/ee/push_rules/push_rules.html) offer server-side control and enforcement options.
   
Currently, there are push options available for: Currently, there are push options available for:
   
Loading
Loading
Loading
@@ -6997,9 +6997,6 @@ msgstr ""
Loading
@@ -6997,9 +6997,6 @@ msgstr ""
msgid "Edit Pipeline Schedule %{id}" msgid "Edit Pipeline Schedule %{id}"
msgstr "" msgstr ""
   
msgid "Edit Project Hook"
msgstr ""
msgid "Edit Release" msgid "Edit Release"
msgstr "" msgstr ""
   
Loading
@@ -9451,6 +9448,9 @@ msgstr ""
Loading
@@ -9451,6 +9448,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}." msgid "Go to %{link_to_google_takeout}."
msgstr "" msgstr ""
   
msgid "Go to Webhooks"
msgstr ""
msgid "Go to commits" msgid "Go to commits"
msgstr "" msgstr ""
   
Loading
@@ -10607,10 +10607,13 @@ msgstr ""
Loading
@@ -10607,10 +10607,13 @@ msgstr ""
msgid "Instance license" msgid "Instance license"
msgstr "" msgstr ""
   
msgid "Integration Settings"
msgstr ""
msgid "Integrations" msgid "Integrations"
msgstr "" msgstr ""
   
msgid "Integrations Settings" msgid "Integrations allow you to integrate GitLab with other applications"
msgstr "" msgstr ""
   
msgid "Interested parties can even contribute by pushing commits if they want to." msgid "Interested parties can even contribute by pushing commits if they want to."
Loading
@@ -14780,9 +14783,6 @@ msgstr ""
Loading
@@ -14780,9 +14783,6 @@ msgstr ""
msgid "Project Files" msgid "Project Files"
msgstr "" msgstr ""
   
msgid "Project Hooks"
msgstr ""
msgid "Project ID" msgid "Project ID"
msgstr "" msgstr ""
   
Loading
@@ -14945,30 +14945,18 @@ msgstr ""
Loading
@@ -14945,30 +14945,18 @@ msgstr ""
msgid "ProjectService|Comment will be posted on each event" msgid "ProjectService|Comment will be posted on each event"
msgstr "" msgstr ""
   
msgid "ProjectService|Integrations"
msgstr ""
msgid "ProjectService|Last edit" msgid "ProjectService|Last edit"
msgstr "" msgstr ""
   
msgid "ProjectService|Perform common operations on GitLab project: %{project_name}" msgid "ProjectService|Perform common operations on GitLab project: %{project_name}"
msgstr "" msgstr ""
   
msgid "ProjectService|Project services"
msgstr ""
msgid "ProjectService|Project services allow you to integrate GitLab with other applications"
msgstr ""
msgid "ProjectService|Service" msgid "ProjectService|Service"
msgstr "" msgstr ""
   
msgid "ProjectService|Services" msgid "ProjectService|Services"
msgstr "" msgstr ""
   
msgid "ProjectService|Settings"
msgstr ""
msgid "ProjectService|To set up this service:" msgid "ProjectService|To set up this service:"
msgstr "" msgstr ""
   
Loading
@@ -21811,6 +21799,15 @@ msgstr ""
Loading
@@ -21811,6 +21799,15 @@ msgstr ""
msgid "WebIDE|Merge request" msgid "WebIDE|Merge request"
msgstr "" msgstr ""
   
msgid "Webhook"
msgstr ""
msgid "Webhook Logs"
msgstr ""
msgid "Webhook Settings"
msgstr ""
msgid "Webhooks" msgid "Webhooks"
msgstr "" msgstr ""
   
Loading
@@ -21820,6 +21817,9 @@ msgstr ""
Loading
@@ -21820,6 +21817,9 @@ msgstr ""
msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group."
msgstr "" msgstr ""
   
msgid "Webhooks have moved. They can now be found under the Settings menu."
msgstr ""
msgid "Wednesday" msgid "Wednesday"
msgstr "" msgstr ""
   
Loading
Loading
Loading
@@ -12,12 +12,11 @@ describe Projects::HooksController do
Loading
@@ -12,12 +12,11 @@ describe Projects::HooksController do
end end
   
describe '#index' do describe '#index' do
it 'redirects to settings/integrations page' do it 'renders index with 200 status code' do
get(:index, params: { namespace_id: project.namespace, project_id: project }) get :index, params: { namespace_id: project.namespace, project_id: project }
   
expect(response).to redirect_to( expect(response).to have_gitlab_http_status(:ok)
project_settings_integrations_path(project) expect(response).to render_template(:index)
)
end end
end end
   
Loading
Loading
Loading
@@ -22,6 +22,7 @@ FactoryBot.define do
Loading
@@ -22,6 +22,7 @@ FactoryBot.define do
   
factory :ci_pipeline do factory :ci_pipeline do
trait :invalid do trait :invalid do
status { :failed }
yaml_errors { 'invalid YAML' } yaml_errors { 'invalid YAML' }
failure_reason { :config_error } failure_reason { :config_error }
end end
Loading
Loading
Loading
@@ -88,6 +88,7 @@ describe 'Project navbar' do
Loading
@@ -88,6 +88,7 @@ describe 'Project navbar' do
_('General'), _('General'),
_('Members'), _('Members'),
_('Integrations'), _('Integrations'),
_('Webhooks'),
_('Repository'), _('Repository'),
_('CI / CD'), _('CI / CD'),
_('Operations'), _('Operations'),
Loading
Loading
Loading
@@ -1077,8 +1077,6 @@ describe 'Pipeline', :js do
Loading
@@ -1077,8 +1077,6 @@ describe 'Pipeline', :js do
end end
   
context 'when pipeline has configuration errors' do context 'when pipeline has configuration errors' do
include_context 'pipeline builds'
let(:pipeline) do let(:pipeline) do
create(:ci_pipeline, create(:ci_pipeline,
:invalid, :invalid,
Loading
@@ -1119,6 +1117,10 @@ describe 'Pipeline', :js do
Loading
@@ -1119,6 +1117,10 @@ describe 'Pipeline', :js do
%Q{span[title="#{pipeline.present.failure_reason}"]}) %Q{span[title="#{pipeline.present.failure_reason}"]})
end end
end end
it 'contains a pipeline header with title' do
expect(page).to have_content "Pipeline ##{pipeline.id}"
end
end end
   
context 'when pipeline is stuck' do context 'when pipeline is stuck' do
Loading
Loading
Loading
@@ -14,7 +14,7 @@ describe 'User views services' do
Loading
@@ -14,7 +14,7 @@ describe 'User views services' do
end end
   
it 'shows the list of available services' do it 'shows the list of available services' do
expect(page).to have_content('Project services') expect(page).to have_content('Integrations')
expect(page).to have_content('Campfire') expect(page).to have_content('Campfire')
expect(page).to have_content('HipChat') expect(page).to have_content('HipChat')
expect(page).to have_content('Assembla') expect(page).to have_content('Assembla')
Loading
Loading
Loading
@@ -35,7 +35,7 @@ describe 'Projects > Settings > For a forked project', :js do
Loading
@@ -35,7 +35,7 @@ describe 'Projects > Settings > For a forked project', :js do
end end
   
it 'renders form for incident management' do it 'renders form for incident management' do
expect(page).to have_selector('h4', text: 'Incidents') expect(page).to have_selector('h3', text: 'Incidents')
end end
   
it 'sets correct default values' do it 'sets correct default values' do
Loading
Loading
Loading
@@ -2,11 +2,10 @@
Loading
@@ -2,11 +2,10 @@
   
require 'spec_helper' require 'spec_helper'
   
describe 'Projects > Settings > Integration settings' do describe 'Projects > Settings > Webhook Settings' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:role) { :developer } let(:webhooks_path) { project_hooks_path(project) }
let(:integrations_path) { project_settings_integrations_path(project) }
   
before do before do
sign_in(user) sign_in(user)
Loading
@@ -17,7 +16,7 @@ describe 'Projects > Settings > Integration settings' do
Loading
@@ -17,7 +16,7 @@ describe 'Projects > Settings > Integration settings' do
let(:role) { :developer } let(:role) { :developer }
   
it 'to be disallowed to view' do it 'to be disallowed to view' do
visit integrations_path visit webhooks_path
   
expect(page.status_code).to eq(404) expect(page.status_code).to eq(404)
end end
Loading
@@ -33,7 +32,7 @@ describe 'Projects > Settings > Integration settings' do
Loading
@@ -33,7 +32,7 @@ describe 'Projects > Settings > Integration settings' do
it 'show list of webhooks' do it 'show list of webhooks' do
hook hook
   
visit integrations_path visit webhooks_path
   
expect(page.status_code).to eq(200) expect(page.status_code).to eq(200)
expect(page).to have_content(hook.url) expect(page).to have_content(hook.url)
Loading
@@ -49,7 +48,7 @@ describe 'Projects > Settings > Integration settings' do
Loading
@@ -49,7 +48,7 @@ describe 'Projects > Settings > Integration settings' do
end end
   
it 'create webhook' do it 'create webhook' do
visit integrations_path visit webhooks_path
   
fill_in 'hook_url', with: url fill_in 'hook_url', with: url
check 'Tag push events' check 'Tag push events'
Loading
@@ -68,7 +67,7 @@ describe 'Projects > Settings > Integration settings' do
Loading
@@ -68,7 +67,7 @@ describe 'Projects > Settings > Integration settings' do
   
it 'edit existing webhook' do it 'edit existing webhook' do
hook hook
visit integrations_path visit webhooks_path
   
click_link 'Edit' click_link 'Edit'
fill_in 'hook_url', with: url fill_in 'hook_url', with: url
Loading
@@ -81,25 +80,25 @@ describe 'Projects > Settings > Integration settings' do
Loading
@@ -81,25 +80,25 @@ describe 'Projects > Settings > Integration settings' do
   
it 'test existing webhook', :js do it 'test existing webhook', :js do
WebMock.stub_request(:post, hook.url) WebMock.stub_request(:post, hook.url)
visit integrations_path visit webhooks_path
   
find('.hook-test-button.dropdown').click find('.hook-test-button.dropdown').click
click_link 'Push events' click_link 'Push events'
   
expect(current_path).to eq(integrations_path) expect(current_path).to eq(webhooks_path)
end end
   
context 'delete existing webhook' do context 'delete existing webhook' do
it 'from webhooks list page' do it 'from webhooks list page' do
hook hook
visit integrations_path visit webhooks_path
   
expect { click_link 'Delete' }.to change(ProjectHook, :count).by(-1) expect { click_link 'Delete' }.to change(ProjectHook, :count).by(-1)
end end
   
it 'from webhook edit page' do it 'from webhook edit page' do
hook hook
visit integrations_path visit webhooks_path
click_link 'Edit' click_link 'Edit'
   
expect { click_link 'Delete' }.to change(ProjectHook, :count).by(-1) expect { click_link 'Delete' }.to change(ProjectHook, :count).by(-1)
Loading
Loading
Loading
@@ -8,13 +8,13 @@ exports[`grafana integration component default state to match the default snapsh
Loading
@@ -8,13 +8,13 @@ exports[`grafana integration component default state to match the default snapsh
<div <div
class="settings-header" class="settings-header"
> >
<h4 <h3
class="js-section-header" class="js-section-header h4"
> >
Grafana Authentication Grafana Authentication
</h4> </h3>
<gl-button-stub <gl-button-stub
class="js-settings-toggle" class="js-settings-toggle"
Loading
Loading
Loading
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
Loading
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { setTestTimeout } from 'helpers/timeout'; import { setTestTimeout } from 'helpers/timeout';
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { cloneDeep } from 'lodash';
import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper'; import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
import { chartColorValues } from '~/monitoring/constants'; import { chartColorValues } from '~/monitoring/constants';
import { createStore } from '~/monitoring/stores'; import { createStore } from '~/monitoring/stores';
Loading
@@ -32,501 +33,563 @@ jest.mock('~/lib/utils/icon_utils', () => ({
Loading
@@ -32,501 +33,563 @@ jest.mock('~/lib/utils/icon_utils', () => ({
   
describe('Time series component', () => { describe('Time series component', () => {
let mockGraphData; let mockGraphData;
let makeTimeSeriesChart;
let store; let store;
   
beforeEach(() => { const makeTimeSeriesChart = (graphData, type) =>
setTestTimeout(1000); shallowMount(TimeSeries, {
propsData: {
store = createStore(); graphData: { ...graphData, type },
deploymentData: store.state.monitoringDashboard.deploymentData,
store.commit( projectPath: `${mockHost}${mockProjectDir}`,
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, },
metricsDashboardPayload, store,
); });
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
   
// Mock data contains 2 panel groups, with 1 and 2 panels respectively describe('With a single time series', () => {
store.commit( beforeEach(() => {
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, setTestTimeout(1000);
mockedQueryResultPayload,
);
   
// Pick the second panel group and the first panel in it store = createStore();
[mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels;
   
makeTimeSeriesChart = (graphData, type) => store.commit(
shallowMount(TimeSeries, { `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
propsData: { metricsDashboardPayload,
graphData: { ...graphData, type }, );
deploymentData: store.state.monitoringDashboard.deploymentData,
projectPath: `${mockHost}${mockProjectDir}`,
},
store,
});
});
   
describe('general functions', () => { store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
let timeSeriesChart;
   
const findChart = () => timeSeriesChart.find({ ref: 'chart' }); // Mock data contains 2 panel groups, with 1 and 2 panels respectively
store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedQueryResultPayload,
);
   
beforeEach(done => { // Pick the second panel group and the first panel in it
timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); [mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels;
timeSeriesChart.vm.$nextTick(done);
}); });
   
it('allows user to override max value label text using prop', () => { describe('general functions', () => {
timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' }); let timeSeriesChart;
return timeSeriesChart.vm.$nextTick().then(() => {
expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText');
});
});
   
it('allows user to override average value label text using prop', () => { const findChart = () => timeSeriesChart.find({ ref: 'chart' });
timeSeriesChart.setProps({ legendAverageText: 'averageText' });
   
return timeSeriesChart.vm.$nextTick().then(() => { beforeEach(done => {
expect(timeSeriesChart.props().legendAverageText).toBe('averageText'); timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart');
timeSeriesChart.vm.$nextTick(done);
}); });
});
   
describe('events', () => { it('allows user to override max value label text using prop', () => {
describe('datazoom', () => { timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' });
let eChartMock;
let startValue;
let endValue;
beforeEach(done => {
eChartMock = {
handlers: {},
getOption: () => ({
dataZoom: [
{
startValue,
endValue,
},
],
}),
off: jest.fn(eChartEvent => {
delete eChartMock.handlers[eChartEvent];
}),
on: jest.fn((eChartEvent, fn) => {
eChartMock.handlers[eChartEvent] = fn;
}),
};
timeSeriesChart = makeTimeSeriesChart(mockGraphData);
timeSeriesChart.vm.$nextTick(() => {
findChart().vm.$emit('created', eChartMock);
done();
});
});
   
it('handles datazoom event from chart', () => { return timeSeriesChart.vm.$nextTick().then(() => {
startValue = 1577836800000; // 2020-01-01T00:00:00.000Z expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText');
endValue = 1577840400000; // 2020-01-01T01:00:00.000Z
eChartMock.handlers.datazoom();
expect(timeSeriesChart.emitted('datazoom')).toHaveLength(1);
expect(timeSeriesChart.emitted('datazoom')[0]).toEqual([
{
start: new Date(startValue).toISOString(),
end: new Date(endValue).toISOString(),
},
]);
}); });
}); });
});
   
describe('methods', () => { it('allows user to override average value label text using prop', () => {
describe('formatTooltipText', () => { timeSeriesChart.setProps({ legendAverageText: 'averageText' });
let mockDate;
let mockCommitUrl;
let generateSeriesData;
   
beforeEach(() => { return timeSeriesChart.vm.$nextTick().then(() => {
mockDate = deploymentData[0].created_at; expect(timeSeriesChart.props().legendAverageText).toBe('averageText');
mockCommitUrl = deploymentData[0].commitUrl;
generateSeriesData = type => ({
seriesData: [
{
seriesName: timeSeriesChart.vm.chartData[0].name,
componentSubType: type,
value: [mockDate, 5.55555],
dataIndex: 0,
},
],
value: mockDate,
});
}); });
});
   
it('does not throw error if data point is outside the zoom range', () => { describe('events', () => {
const seriesDataWithoutValue = generateSeriesData('line'); describe('datazoom', () => {
expect( let eChartMock;
timeSeriesChart.vm.formatTooltipText({ let startValue;
...seriesDataWithoutValue, let endValue;
seriesData: seriesDataWithoutValue.seriesData.map(data => ({
...data,
value: undefined,
})),
}),
).toBeUndefined();
});
   
describe('when series is of line type', () => {
beforeEach(done => { beforeEach(done => {
timeSeriesChart.vm.formatTooltipText(generateSeriesData('line')); eChartMock = {
timeSeriesChart.vm.$nextTick(done); handlers: {},
}); getOption: () => ({
dataZoom: [
{
startValue,
endValue,
},
],
}),
off: jest.fn(eChartEvent => {
delete eChartMock.handlers[eChartEvent];
}),
on: jest.fn((eChartEvent, fn) => {
eChartMock.handlers[eChartEvent] = fn;
}),
};
   
it('formats tooltip title', () => { timeSeriesChart = makeTimeSeriesChart(mockGraphData);
expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); timeSeriesChart.vm.$nextTick(() => {
findChart().vm.$emit('created', eChartMock);
done();
});
}); });
   
it('formats tooltip content', () => { it('handles datazoom event from chart', () => {
const name = 'Pod average'; startValue = 1577836800000; // 2020-01-01T00:00:00.000Z
const value = '5.556'; endValue = 1577840400000; // 2020-01-01T01:00:00.000Z
const dataIndex = 0; eChartMock.handlers.datazoom();
const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel);
   
expect(seriesLabel.vm.color).toBe(''); expect(timeSeriesChart.emitted('datazoom')).toHaveLength(1);
expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); expect(timeSeriesChart.emitted('datazoom')[0]).toEqual([
expect(timeSeriesChart.vm.tooltip.content).toEqual([ {
{ name, value, dataIndex, color: undefined }, start: new Date(startValue).toISOString(),
end: new Date(endValue).toISOString(),
},
]); ]);
expect(
shallowWrapperContainsSlotText(
timeSeriesChart.find(GlAreaChart),
'tooltipContent',
value,
),
).toBe(true);
}); });
}); });
});
describe('methods', () => {
describe('formatTooltipText', () => {
let mockDate;
let mockCommitUrl;
let generateSeriesData;
   
describe('when series is of scatter type, for deployments', () => {
beforeEach(() => { beforeEach(() => {
timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter')); mockDate = deploymentData[0].created_at;
mockCommitUrl = deploymentData[0].commitUrl;
generateSeriesData = type => ({
seriesData: [
{
seriesName: timeSeriesChart.vm.chartData[0].name,
componentSubType: type,
value: [mockDate, 5.55555],
dataIndex: 0,
},
],
value: mockDate,
});
}); });
   
it('formats tooltip title', () => { it('does not throw error if data point is outside the zoom range', () => {
expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); const seriesDataWithoutValue = generateSeriesData('line');
expect(
timeSeriesChart.vm.formatTooltipText({
...seriesDataWithoutValue,
seriesData: seriesDataWithoutValue.seriesData.map(data => ({
...data,
value: undefined,
})),
}),
).toBeUndefined();
}); });
   
it('formats tooltip sha', () => { describe('when series is of line type', () => {
expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9'); beforeEach(done => {
timeSeriesChart.vm.formatTooltipText(generateSeriesData('line'));
timeSeriesChart.vm.$nextTick(done);
});
it('formats tooltip title', () => {
expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM');
});
it('formats tooltip content', () => {
const name = 'Pod average';
const value = '5.556';
const dataIndex = 0;
const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel);
expect(seriesLabel.vm.color).toBe('');
expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
expect(timeSeriesChart.vm.tooltip.content).toEqual([
{ name, value, dataIndex, color: undefined },
]);
expect(
shallowWrapperContainsSlotText(
timeSeriesChart.find(GlAreaChart),
'tooltipContent',
value,
),
).toBe(true);
});
}); });
   
it('formats tooltip commit url', () => { describe('when series is of scatter type, for deployments', () => {
expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl); beforeEach(() => {
timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter'));
});
it('formats tooltip title', () => {
expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM');
});
it('formats tooltip sha', () => {
expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9');
});
it('formats tooltip commit url', () => {
expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl);
});
}); });
}); });
});
   
describe('setSvg', () => { describe('setSvg', () => {
const mockSvgName = 'mockSvgName'; const mockSvgName = 'mockSvgName';
   
beforeEach(done => { beforeEach(done => {
timeSeriesChart.vm.setSvg(mockSvgName); timeSeriesChart.vm.setSvg(mockSvgName);
timeSeriesChart.vm.$nextTick(done); timeSeriesChart.vm.$nextTick(done);
}); });
   
it('gets svg path content', () => { it('gets svg path content', () => {
expect(iconUtils.getSvgIconPathContent).toHaveBeenCalledWith(mockSvgName); expect(iconUtils.getSvgIconPathContent).toHaveBeenCalledWith(mockSvgName);
}); });
   
it('sets svg path content', () => { it('sets svg path content', () => {
timeSeriesChart.vm.$nextTick(() => { timeSeriesChart.vm.$nextTick(() => {
expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
});
}); });
});
   
it('contains an svg object within an array to properly render icon', () => { it('contains an svg object within an array to properly render icon', () => {
timeSeriesChart.vm.$nextTick(() => { timeSeriesChart.vm.$nextTick(() => {
expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([ expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([
{ {
handleIcon: `path://${mockSvgPathContent}`, handleIcon: `path://${mockSvgPathContent}`,
}, },
]); ]);
});
}); });
}); });
});
   
describe('onResize', () => { describe('onResize', () => {
const mockWidth = 233; const mockWidth = 233;
   
beforeEach(() => { beforeEach(() => {
jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({
width: mockWidth, width: mockWidth,
})); }));
timeSeriesChart.vm.onResize(); timeSeriesChart.vm.onResize();
}); });
   
it('sets area chart width', () => { it('sets area chart width', () => {
expect(timeSeriesChart.vm.width).toBe(mockWidth); expect(timeSeriesChart.vm.width).toBe(mockWidth);
});
}); });
}); });
});
   
describe('computed', () => { describe('computed', () => {
const getChartOptions = () => findChart().props('option'); const getChartOptions = () => findChart().props('option');
   
describe('chartData', () => { describe('chartData', () => {
let chartData; let chartData;
const seriesData = () => chartData[0]; const seriesData = () => chartData[0];
   
beforeEach(() => { beforeEach(() => {
({ chartData } = timeSeriesChart.vm); ({ chartData } = timeSeriesChart.vm);
}); });
   
it('utilizes all data points', () => { it('utilizes all data points', () => {
const { values } = mockGraphData.metrics[0].result[0]; const { values } = mockGraphData.metrics[0].result[0];
   
expect(chartData.length).toBe(1); expect(chartData.length).toBe(1);
expect(seriesData().data.length).toBe(values.length); expect(seriesData().data.length).toBe(values.length);
}); });
   
it('creates valid data', () => { it('creates valid data', () => {
const { data } = seriesData(); const { data } = seriesData();
   
expect( expect(
data.filter( data.filter(
([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number', ([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number',
).length, ).length,
).toBe(data.length); ).toBe(data.length);
}); });
   
it('formats line width correctly', () => { it('formats line width correctly', () => {
expect(chartData[0].lineStyle.width).toBe(2); expect(chartData[0].lineStyle.width).toBe(2);
}); });
   
it('formats line color correctly', () => { it('formats line color correctly', () => {
expect(chartData[0].lineStyle.color).toBe(chartColorValues[0]); expect(chartData[0].lineStyle.color).toBe(chartColorValues[0]);
});
}); });
});
   
describe('chartOptions', () => { describe('chartOptions', () => {
describe('are extended by `option`', () => { describe('are extended by `option`', () => {
const mockSeriesName = 'Extra series 1'; const mockSeriesName = 'Extra series 1';
const mockOption = { const mockOption = {
option1: 'option1', option1: 'option1',
option2: 'option2', option2: 'option2',
}; };
it('arbitrary options', () => {
timeSeriesChart.setProps({
option: mockOption,
});
   
return timeSeriesChart.vm.$nextTick().then(() => { it('arbitrary options', () => {
expect(getChartOptions()).toEqual(expect.objectContaining(mockOption)); timeSeriesChart.setProps({
}); option: mockOption,
}); });
   
it('additional series', () => { return timeSeriesChart.vm.$nextTick().then(() => {
timeSeriesChart.setProps({ expect(getChartOptions()).toEqual(expect.objectContaining(mockOption));
option: { });
series: [
{
name: mockSeriesName,
},
],
},
}); });
   
return timeSeriesChart.vm.$nextTick().then(() => { it('additional series', () => {
const optionSeries = getChartOptions().series; timeSeriesChart.setProps({
option: {
series: [
{
name: mockSeriesName,
},
],
},
});
return timeSeriesChart.vm.$nextTick().then(() => {
const optionSeries = getChartOptions().series;
   
expect(optionSeries.length).toEqual(2); expect(optionSeries.length).toEqual(2);
expect(optionSeries[0].name).toEqual(mockSeriesName); expect(optionSeries[0].name).toEqual(mockSeriesName);
});
}); });
});
   
it('additional y axis data', () => { it('additional y axis data', () => {
const mockCustomYAxisOption = { const mockCustomYAxisOption = {
name: 'Custom y axis label', name: 'Custom y axis label',
axisLabel: { axisLabel: {
formatter: jest.fn(), formatter: jest.fn(),
}, },
}; };
   
timeSeriesChart.setProps({ timeSeriesChart.setProps({
option: { option: {
yAxis: mockCustomYAxisOption, yAxis: mockCustomYAxisOption,
}, },
});
return timeSeriesChart.vm.$nextTick().then(() => {
const { yAxis } = getChartOptions();
expect(yAxis[0]).toMatchObject(mockCustomYAxisOption);
});
}); });
   
return timeSeriesChart.vm.$nextTick().then(() => { it('additional x axis data', () => {
const { yAxis } = getChartOptions(); const mockCustomXAxisOption = {
name: 'Custom x axis label',
};
timeSeriesChart.setProps({
option: {
xAxis: mockCustomXAxisOption,
},
});
return timeSeriesChart.vm.$nextTick().then(() => {
const { xAxis } = getChartOptions();
   
expect(yAxis[0]).toMatchObject(mockCustomYAxisOption); expect(xAxis).toMatchObject(mockCustomXAxisOption);
});
}); });
}); });
   
it('additional x axis data', () => { describe('yAxis formatter', () => {
const mockCustomXAxisOption = { let dataFormatter;
name: 'Custom x axis label', let deploymentFormatter;
};
   
timeSeriesChart.setProps({ beforeEach(() => {
option: { dataFormatter = getChartOptions().yAxis[0].axisLabel.formatter;
xAxis: mockCustomXAxisOption, deploymentFormatter = getChartOptions().yAxis[1].axisLabel.formatter;
},
}); });
   
return timeSeriesChart.vm.$nextTick().then(() => { it('rounds to 3 decimal places', () => {
const { xAxis } = getChartOptions(); expect(dataFormatter(0.88888)).toBe('0.889');
});
   
expect(xAxis).toMatchObject(mockCustomXAxisOption); it('deployment formatter is set as is required to display a tooltip', () => {
expect(deploymentFormatter).toEqual(expect.any(Function));
}); });
}); });
}); });
   
describe('yAxis formatter', () => { describe('deploymentSeries', () => {
let dataFormatter; it('utilizes deployment data', () => {
let deploymentFormatter; expect(timeSeriesChart.vm.deploymentSeries.yAxisIndex).toBe(1); // same as deployment y axis
expect(timeSeriesChart.vm.deploymentSeries.data).toEqual([
['2019-07-16T10:14:25.589Z', expect.any(Number)],
['2019-07-16T11:14:25.589Z', expect.any(Number)],
['2019-07-16T12:14:25.589Z', expect.any(Number)],
]);
   
beforeEach(() => { expect(timeSeriesChart.vm.deploymentSeries.symbolSize).toBe(14);
dataFormatter = getChartOptions().yAxis[0].axisLabel.formatter;
deploymentFormatter = getChartOptions().yAxis[1].axisLabel.formatter;
}); });
});
   
it('rounds to 3 decimal places', () => { describe('yAxisLabel', () => {
expect(dataFormatter(0.88888)).toBe('0.889'); it('y axis is configured correctly', () => {
const { yAxis } = getChartOptions();
expect(yAxis).toHaveLength(2);
const [dataAxis, deploymentAxis] = yAxis;
expect(dataAxis.boundaryGap).toHaveLength(2);
expect(dataAxis.scale).toBe(true);
expect(deploymentAxis.show).toBe(false);
expect(deploymentAxis.min).toEqual(expect.any(Number));
expect(deploymentAxis.max).toEqual(expect.any(Number));
expect(deploymentAxis.min).toBeLessThan(deploymentAxis.max);
}); });
   
it('deployment formatter is set as is required to display a tooltip', () => { it('constructs a label for the chart y-axis', () => {
expect(deploymentFormatter).toEqual(expect.any(Function)); const { yAxis } = getChartOptions();
expect(yAxis[0].name).toBe('Memory Used per Pod');
}); });
}); });
}); });
   
describe('deploymentSeries', () => { afterEach(() => {
it('utilizes deployment data', () => { timeSeriesChart.destroy();
expect(timeSeriesChart.vm.deploymentSeries.yAxisIndex).toBe(1); // same as deployment y axis
expect(timeSeriesChart.vm.deploymentSeries.data).toEqual([
['2019-07-16T10:14:25.589Z', expect.any(Number)],
['2019-07-16T11:14:25.589Z', expect.any(Number)],
['2019-07-16T12:14:25.589Z', expect.any(Number)],
]);
expect(timeSeriesChart.vm.deploymentSeries.symbolSize).toBe(14);
});
}); });
});
   
describe('yAxisLabel', () => { describe('wrapped components', () => {
it('y axis is configured correctly', () => { const glChartComponents = [
const { yAxis } = getChartOptions(); {
chartType: 'area-chart',
component: GlAreaChart,
},
{
chartType: 'line-chart',
component: GlLineChart,
},
];
   
expect(yAxis).toHaveLength(2); glChartComponents.forEach(dynamicComponent => {
describe(`GitLab UI: ${dynamicComponent.chartType}`, () => {
let timeSeriesAreaChart;
const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component);
   
const [dataAxis, deploymentAxis] = yAxis; beforeEach(done => {
timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType);
timeSeriesAreaChart.vm.$nextTick(done);
});
   
expect(dataAxis.boundaryGap).toHaveLength(2); afterEach(() => {
expect(dataAxis.scale).toBe(true); timeSeriesAreaChart.destroy();
});
   
expect(deploymentAxis.show).toBe(false); it('is a Vue instance', () => {
expect(deploymentAxis.min).toEqual(expect.any(Number)); expect(findChartComponent().exists()).toBe(true);
expect(deploymentAxis.max).toEqual(expect.any(Number)); expect(findChartComponent().isVueInstance()).toBe(true);
expect(deploymentAxis.min).toBeLessThan(deploymentAxis.max); });
});
   
it('constructs a label for the chart y-axis', () => { it('receives data properties needed for proper chart render', () => {
const { yAxis } = getChartOptions(); const props = findChartComponent().props();
   
expect(yAxis[0].name).toBe('Memory Used per Pod'); expect(props.data).toBe(timeSeriesAreaChart.vm.chartData);
}); expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions);
}); expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText);
}); expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds);
});
   
afterEach(() => { it('recieves a tooltip title', done => {
timeSeriesChart.destroy(); const mockTitle = 'mockTitle';
}); timeSeriesAreaChart.vm.tooltip.title = mockTitle;
});
   
describe('wrapped components', () => { timeSeriesAreaChart.vm.$nextTick(() => {
const glChartComponents = [ expect(
{ shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', mockTitle),
chartType: 'area-chart', ).toBe(true);
component: GlAreaChart, done();
}, });
{ });
chartType: 'line-chart',
component: GlLineChart,
},
];
   
glChartComponents.forEach(dynamicComponent => { describe('when tooltip is showing deployment data', () => {
describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { const mockSha = 'mockSha';
let timeSeriesAreaChart; const commitUrl = `${mockProjectDir}/-/commit/${mockSha}`;
const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component);
   
beforeEach(done => { beforeEach(done => {
timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); timeSeriesAreaChart.vm.tooltip.isDeployment = true;
timeSeriesAreaChart.vm.$nextTick(done); timeSeriesAreaChart.vm.$nextTick(done);
}); });
   
afterEach(() => { it('uses deployment title', () => {
timeSeriesAreaChart.destroy(); expect(
}); shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', 'Deployed'),
).toBe(true);
});
   
it('is a Vue instance', () => { it('renders clickable commit sha in tooltip content', done => {
expect(findChartComponent().exists()).toBe(true); timeSeriesAreaChart.vm.tooltip.sha = mockSha;
expect(findChartComponent().isVueInstance()).toBe(true); timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl;
});
   
it('receives data properties needed for proper chart render', () => { timeSeriesAreaChart.vm.$nextTick(() => {
const props = findChartComponent().props(); const commitLink = timeSeriesAreaChart.find(GlLink);
   
expect(props.data).toBe(timeSeriesAreaChart.vm.chartData); expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions); expect(commitLink.attributes('href')).toEqual(commitUrl);
expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText); done();
expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds); });
});
});
}); });
});
});
});
   
it('recieves a tooltip title', done => { describe('with multiple time series', () => {
const mockTitle = 'mockTitle'; const mockedResultMultipleSeries = [];
timeSeriesAreaChart.vm.tooltip.title = mockTitle; const [, , panelData] = metricsDashboardPayload.panel_groups[1].panels;
   
timeSeriesAreaChart.vm.$nextTick(() => { for (let i = 0; i < panelData.metrics.length; i += 1) {
expect( mockedResultMultipleSeries.push(cloneDeep(mockedQueryResultPayload));
shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', mockTitle), mockedResultMultipleSeries[
).toBe(true); i
done(); ].metricId = `${panelData.metrics[i].metric_id}_${panelData.metrics[i].id}`;
}); }
});
   
describe('when tooltip is showing deployment data', () => { beforeEach(() => {
const mockSha = 'mockSha'; setTestTimeout(1000);
const commitUrl = `${mockProjectDir}/-/commit/${mockSha}`;
   
beforeEach(done => { store = createStore();
timeSeriesAreaChart.vm.tooltip.isDeployment = true;
timeSeriesAreaChart.vm.$nextTick(done);
});
   
it('uses deployment title', () => { store.commit(
expect( `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', 'Deployed'), metricsDashboardPayload,
).toBe(true); );
});
   
it('renders clickable commit sha in tooltip content', done => { store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
timeSeriesAreaChart.vm.tooltip.sha = mockSha;
timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl;
   
timeSeriesAreaChart.vm.$nextTick(() => { // Mock data contains the metric_id for a multiple time series panel
const commitLink = timeSeriesAreaChart.find(GlLink); for (let i = 0; i < panelData.metrics.length; i += 1) {
store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedResultMultipleSeries[i],
);
}
   
expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); // Pick the second panel group and the second panel in it
expect(commitLink.attributes('href')).toEqual(commitUrl); [, , mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels;
done(); });
});
}); describe('General functions', () => {
let timeSeriesChart;
beforeEach(done => {
timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart');
timeSeriesChart.vm.$nextTick(done);
});
describe('computed', () => {
let chartData;
beforeEach(() => {
({ chartData } = timeSeriesChart.vm);
});
it('should contain different colors for each time series', () => {
expect(chartData[0].lineStyle.color).toBe('#1f78d1');
expect(chartData[1].lineStyle.color).toBe('#1aaa55');
expect(chartData[2].lineStyle.color).toBe('#fc9403');
expect(chartData[3].lineStyle.color).toBe('#6d49cb');
expect(chartData[4].lineStyle.color).toBe('#1f78d1');
}); });
}); });
}); });
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment