Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1eec6b22b2
commit
44e10d208a
27 changed files with 372 additions and 124 deletions
|
@ -18,8 +18,6 @@ export default {
|
|||
testCase: {
|
||||
type: Object,
|
||||
required: true,
|
||||
validator: ({ classname, formattedTime, name }) =>
|
||||
Boolean(classname) && Boolean(formattedTime) && Boolean(name),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -20,6 +20,8 @@ export const getSuiteTests = (state) => {
|
|||
return testCases
|
||||
.map((testCase) => ({
|
||||
...testCase,
|
||||
classname: testCase.classname || '',
|
||||
name: testCase.name || '',
|
||||
filePath: testCase.file ? `${state.blobPath}/${formatFilePath(testCase.file)}` : null,
|
||||
}))
|
||||
.map(addIconStatus)
|
||||
|
|
|
@ -105,7 +105,7 @@ export default {
|
|||
<section class="header-main-content">
|
||||
<ci-icon-badge :status="status" />
|
||||
|
||||
<strong> {{ itemName }} #{{ itemId }} </strong>
|
||||
<strong data-testid="ci-header-item-text"> {{ itemName }} #{{ itemId }} </strong>
|
||||
|
||||
<template v-if="shouldRenderTriggeredLabel">{{ __('triggered') }}</template>
|
||||
<template v-else>{{ __('created') }}</template>
|
||||
|
@ -142,7 +142,7 @@ export default {
|
|||
</template>
|
||||
</section>
|
||||
|
||||
<section v-if="$slots.default" data-testid="headerButtons" class="gl-display-flex">
|
||||
<section v-if="$slots.default" data-testid="ci-header-action-buttons" class="gl-display-flex">
|
||||
<slot></slot>
|
||||
</section>
|
||||
<gl-button
|
||||
|
|
|
@ -84,3 +84,5 @@ module Ci
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Ci::Testing::DailyBuildGroupReportResultsFinder.prepend_if_ee('::EE::Ci::Testing::DailyBuildGroupReportResultsFinder')
|
||||
|
|
|
@ -9,12 +9,14 @@ module Ci
|
|||
|
||||
belongs_to :last_pipeline, class_name: 'Ci::Pipeline', foreign_key: :last_pipeline_id
|
||||
belongs_to :project
|
||||
belongs_to :group
|
||||
|
||||
validates :data, json_schema: { filename: "daily_build_group_report_result_data" }
|
||||
|
||||
scope :with_included_projects, -> { includes(:project) }
|
||||
scope :by_ref_path, -> (ref_path) { where(ref_path: ref_path) }
|
||||
scope :by_projects, -> (ids) { where(project_id: ids) }
|
||||
scope :by_group, -> (group_id) { where(group_id: group_id) }
|
||||
scope :with_coverage, -> { where("(data->'coverage') IS NOT NULL") }
|
||||
scope :with_default_branch, -> { where(default_branch: true) }
|
||||
scope :by_date, -> (start_date) { where(date: report_window(start_date)..Date.current) }
|
||||
|
|
|
@ -48,6 +48,7 @@ class Group < Namespace
|
|||
|
||||
has_many :labels, class_name: 'GroupLabel'
|
||||
has_many :variables, class_name: 'Ci::GroupVariable'
|
||||
has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult'
|
||||
has_many :custom_attributes, class_name: 'GroupCustomAttribute'
|
||||
|
||||
has_many :boards
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix pipeline test report not rendering when missing properties
|
||||
merge_request: 54363
|
||||
author:
|
||||
type: fixed
|
|
@ -93,6 +93,8 @@ which correspond to:
|
|||
|
||||
1. `elasticsearch_calls`: total number of calls to Elasticsearch
|
||||
1. `elasticsearch_duration_s`: total time taken by Elasticsearch calls
|
||||
1. `elasticsearch_timed_out_count`: total number of calls to Elasticsearch that
|
||||
timed out and therefore returned partial results
|
||||
|
||||
ActionCable connection and subscription events are also logged to this file and they follow the same
|
||||
format above. The `method`, `path`, and `format` fields are not applicable, and are always empty.
|
||||
|
|
77
doc/development/fe_guide/dark_mode.md
Normal file
77
doc/development/fe_guide/dark_mode.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
type: reference, dev
|
||||
stage: none
|
||||
group: Development
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
This page is about developing dark mode for GitLab. We also have documentation on how
|
||||
[to enable dark mode](../../user/profile/preferences.md#dark-mode).
|
||||
|
||||
# How dark mode works
|
||||
|
||||
Short version: Reverse the color palette and override a few Bootstrap variables.
|
||||
|
||||
Note the following:
|
||||
|
||||
- The dark mode palette is defined in `app/assets/stylesheets/themes/_dark.scss`.
|
||||
This is loaded _before_ application.scss to generate `application_dark.css`
|
||||
- We define two types of variables in `_dark.scss`:
|
||||
- SCSS variables are used in framework, components, and utitlity classes.
|
||||
- CSS variables are used for any colors within the `app/assets/stylesheets/page_bundles` directory.
|
||||
- `app/views/layouts/_head.html.haml` then loads application or application_dark based on the user's theme preference.
|
||||
|
||||
As we do not want to generate separate `_dark.css` variants of every page_bundle file,
|
||||
we use CSS variables with SCSS variables as fallbacks. This is because when we generate the `page_bundles`
|
||||
CSS, we get the variable values from `_variables.scss`, so any SCSS variables have light mode values.
|
||||
|
||||
As the CSS variables defined in `_dark.scss` are available in the browser, they have the
|
||||
correct colors for dark mode.
|
||||
|
||||
```scss
|
||||
color: var(--gray-500, $gray-500);
|
||||
```
|
||||
|
||||
## Utility classes
|
||||
|
||||
We generate a separate `utilities_dark.css` file for utility classes containing the inverted values. So a class
|
||||
such as `gl-text-white` specifies a text color of `#333` in dark mode. This means you do not have to
|
||||
add multiple classes every time you want to add a color.
|
||||
|
||||
Currently, we cannot set up a utility class only in dark mode. We hope to address that
|
||||
[issue](https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1141) soon.
|
||||
|
||||
## Using different values in light and dark mode
|
||||
|
||||
In most cases, we can use the same values for light and dark mode. If that is not possible, you
|
||||
can add an override using the `.gl-dark` class that dark mode adds to `body`:
|
||||
|
||||
```scss
|
||||
color: $gray-700;
|
||||
.gl-dark & {
|
||||
color: var(--gray-500);
|
||||
}
|
||||
```
|
||||
|
||||
NOTE:
|
||||
Avoid using a different value for the SCSS fallback
|
||||
|
||||
```scss
|
||||
// avoid where possible
|
||||
// --gray-500 (#999) in dark mode
|
||||
// $gray-700 (#525252) in light mode
|
||||
color: var(--gray-500, $gray-700);
|
||||
```
|
||||
|
||||
We [plan to add](https://gitlab.com/gitlab-org/gitlab/-/issues/301147) the CSS variables to light mode. When that happens, different values for the SCSS fallback will no longer work.
|
||||
|
||||
## When to use SCSS variables
|
||||
|
||||
There are a few things we do in SCSS that we cannot (easily) do with CSS, such as the following
|
||||
functions:
|
||||
|
||||
- `lighten`
|
||||
- `darken`
|
||||
- `color-yiq` (color contrast)
|
||||
|
||||
If those are needed then SCSS variables should be used.
|
|
@ -198,3 +198,7 @@ To see what polyfills are being used:
|
|||
which polyfills are being loaded and where:
|
||||
|
||||
![Image of webpack report](img/webpack_report_v12_8.png)
|
||||
|
||||
### 9. Why is my page broken in dark mode?
|
||||
|
||||
See [dark mode docs](dark_mode.md)
|
||||
|
|
|
@ -417,6 +417,8 @@ You can check the recommended variables for each cluster type in the official do
|
|||
- [Google GKE](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-gke/#deploy-cilium)
|
||||
- [AWS EKS](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-eks/#deploy-cilium)
|
||||
|
||||
Do not use `clusterType` for sandbox environments like [Minikube](https://minikube.sigs.k8s.io/docs/).
|
||||
|
||||
You can customize Cilium's Helm variables by defining the
|
||||
`.gitlab/managed-apps/cilium/values.yaml` file in your cluster
|
||||
management project. Refer to the
|
||||
|
|
|
@ -50,7 +50,7 @@ service in GitLab.
|
|||
1. Enter the build key from your Bamboo build plan. Build keys are typically made
|
||||
up from the Project Key and Plan Key that are set on project/plan creation and
|
||||
separated with a dash (`-`), for example **PROJ-PLAN**. This is a short, all
|
||||
uppercase identifier that is unique. When viewing a plan within Bamboo, the
|
||||
uppercase identifier that is unique. When viewing a plan in Bamboo, the
|
||||
build key is also shown in the browser URL, for example `https://bamboo.example.com/browse/PROJ-PLAN`.
|
||||
1. If necessary, enter username and password for a Bamboo user that has
|
||||
access to trigger the build plan. Leave these fields blank if you do not require
|
||||
|
@ -60,8 +60,12 @@ service in GitLab.
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
### Builds not triggered
|
||||
|
||||
If builds are not triggered, ensure you entered the right GitLab IP address in
|
||||
Bamboo under 'Trigger IP addresses'. Also check [service hook logs](overview.md#troubleshooting-integrations) for request failures.
|
||||
|
||||
NOTE:
|
||||
Starting with GitLab 8.14.0, builds are triggered on push events.
|
||||
### Advanced Atlassian Bamboo features not available in GitLab UI
|
||||
|
||||
Advanced Atlassian Bamboo features are not compatible with GitLab. These features
|
||||
include, but are not limited to, the ability to watch the build logs from the GitLab UI.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Manage
|
||||
group: Import
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
|
@ -9,7 +9,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/23532) in GitLab 11.7.
|
||||
|
||||
Issues can be imported to a project by uploading a CSV file with the columns
|
||||
`title` and `description`.
|
||||
`title` and `description`. Other columns are **not** imported. If you want to
|
||||
retain columns such as labels and milestones, consider the [Move Issue feature](managing_issues.md#moving-issues).
|
||||
|
||||
The user uploading the CSV file is set as the author of the imported issues.
|
||||
|
||||
|
|
|
@ -78,8 +78,23 @@ Here's how the process would look like:
|
|||
local branch `thedude-awesome-project-update-docs` to the
|
||||
`update-docs` branch of the `git@gitlab.com:thedude/awesome-project.git` repository.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
## Troubleshooting
|
||||
|
||||
### Pipeline status unavailable from MR page of forked project
|
||||
|
||||
When a user forks a project, the permissions on the forked copy are not copied over
|
||||
from the original project. The creator of the fork must grant permissions to the
|
||||
forked copy before members in the upstream project can view or merge the changes
|
||||
in the merge request.
|
||||
|
||||
To see the pipeline status from the merge request page of a forked project
|
||||
going back to the original project:
|
||||
|
||||
1. Create a group containing all the upstream members.
|
||||
1. Go to the **Members** tab in the forked project and invite the newly-created
|
||||
group to the forked project.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
|
||||
one might have when setting this up, or when something is changed, or on upgrading, it's
|
||||
important to describe those, too. Think of things that may go wrong and include them here.
|
||||
|
|
|
@ -64,9 +64,10 @@ In GitLab 12.10, we added a comparison mode, which
|
|||
shows a diff calculated by simulating how it would look like once merged - a more accurate
|
||||
representation of the changes rather than using the base of the two
|
||||
branches. The new mode is available from the comparison target drop down
|
||||
by selecting **master (HEAD)**. In the future it will
|
||||
[replace](https://gitlab.com/gitlab-org/gitlab/-/issues/198458) the
|
||||
current default comparison.
|
||||
by selecting **master (HEAD)**. In GitLab 13.9, it
|
||||
[replaced](https://gitlab.com/gitlab-org/gitlab/-/issues/198458) the
|
||||
old default comparison. For technical details, additional information is available in the
|
||||
[developer documentation](../../../development/diffs.md#merge-request-diffs-against-the-head-of-the-target-branch).
|
||||
|
||||
![Merge request versions compare HEAD](img/versions_compare_head_v12_10.png)
|
||||
|
||||
|
|
|
@ -23,6 +23,10 @@ When a feature is no longer necessary, you can [archive the related requirement]
|
|||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For an overview, see [GitLab 12.10 Introduces Requirements Management](https://www.youtube.com/watch?v=uSS7oUNSEoU).
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
|
||||
For a more in-depth walkthrough using a [demonstration project](https://gitlab.com/gitlab-org/requiremeents-mgmt),
|
||||
see [GitLab Requirements Traceability Walkthrough](https://youtu.be/VIiuTQYFVa0) (Feb 2021).
|
||||
|
||||
![requirements list view](img/requirements_list_v13_5.png)
|
||||
|
||||
## Create a requirement
|
||||
|
|
|
@ -10,7 +10,7 @@ type: reference, how-to
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4539) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.4.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/44157) to GitLab Free in 10.7.
|
||||
|
||||
The Web IDE editor makes it faster and easier to contribute changes to your
|
||||
The Web Integrated Development Environment (IDE) editor makes it faster and easier to contribute changes to your
|
||||
projects by providing an advanced editor with commit staging.
|
||||
|
||||
## Open the Web IDE
|
||||
|
|
|
@ -9,12 +9,17 @@ module Gitlab
|
|||
start = Time.now
|
||||
headers = (headers || {})
|
||||
.reverse_merge({ 'X-Opaque-Id': Labkit::Correlation::CorrelationId.current_or_new_id })
|
||||
super
|
||||
response = super
|
||||
ensure
|
||||
if ::Gitlab::SafeRequestStore.active?
|
||||
duration = (Time.now - start)
|
||||
|
||||
::Gitlab::Instrumentation::ElasticsearchTransport.increment_request_count
|
||||
|
||||
if response&.body && response.body.is_a?(Hash) && response.body['timed_out']
|
||||
::Gitlab::Instrumentation::ElasticsearchTransport.increment_timed_out_count
|
||||
end
|
||||
|
||||
::Gitlab::Instrumentation::ElasticsearchTransport.add_duration(duration)
|
||||
::Gitlab::Instrumentation::ElasticsearchTransport.add_call_details(duration, method, path, params, body)
|
||||
end
|
||||
|
@ -25,6 +30,7 @@ module Gitlab
|
|||
ELASTICSEARCH_REQUEST_COUNT = :elasticsearch_request_count
|
||||
ELASTICSEARCH_CALL_DURATION = :elasticsearch_call_duration
|
||||
ELASTICSEARCH_CALL_DETAILS = :elasticsearch_call_details
|
||||
ELASTICSEARCH_TIMED_OUT_COUNT = :elasticsearch_timed_out_count
|
||||
|
||||
def self.get_request_count
|
||||
::Gitlab::SafeRequestStore[ELASTICSEARCH_REQUEST_COUNT] || 0
|
||||
|
@ -49,6 +55,15 @@ module Gitlab
|
|||
::Gitlab::SafeRequestStore[ELASTICSEARCH_CALL_DURATION] += duration
|
||||
end
|
||||
|
||||
def self.increment_timed_out_count
|
||||
::Gitlab::SafeRequestStore[ELASTICSEARCH_TIMED_OUT_COUNT] ||= 0
|
||||
::Gitlab::SafeRequestStore[ELASTICSEARCH_TIMED_OUT_COUNT] += 1
|
||||
end
|
||||
|
||||
def self.get_timed_out_count
|
||||
::Gitlab::SafeRequestStore[ELASTICSEARCH_TIMED_OUT_COUNT] || 0
|
||||
end
|
||||
|
||||
def self.add_call_details(duration, method, path, params, body)
|
||||
return unless Gitlab::PerformanceBar.enabled_for_request?
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ module Gitlab
|
|||
:rugged_duration_s,
|
||||
:elasticsearch_calls,
|
||||
:elasticsearch_duration_s,
|
||||
:elasticsearch_timed_out_count,
|
||||
*::Gitlab::Memory::Instrumentation::KEY_MAPPING.values,
|
||||
*::Gitlab::Instrumentation::Redis.known_payload_keys,
|
||||
*::Gitlab::Metrics::Subscribers::ActiveRecord::DB_COUNTERS,
|
||||
|
@ -79,6 +80,7 @@ module Gitlab
|
|||
|
||||
payload[:elasticsearch_calls] = elasticsearch_calls
|
||||
payload[:elasticsearch_duration_s] = Gitlab::Instrumentation::ElasticsearchTransport.query_time
|
||||
payload[:elasticsearch_timed_out_count] = Gitlab::Instrumentation::ElasticsearchTransport.get_timed_out_count
|
||||
end
|
||||
|
||||
def instrument_external_http(payload)
|
||||
|
|
|
@ -7,6 +7,7 @@ FactoryBot.define do
|
|||
project
|
||||
last_pipeline factory: :ci_pipeline
|
||||
group_name { 'rspec' }
|
||||
group
|
||||
data do
|
||||
{ 'coverage' => 77.0 }
|
||||
end
|
||||
|
|
|
@ -1,78 +1,80 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { GlIcon, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import JobContainerItem from '~/jobs/components/job_container_item.vue';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import job from '../mock_data';
|
||||
|
||||
describe('JobContainerItem', () => {
|
||||
let wrapper;
|
||||
const delayedJobFixture = getJSONFixture('jobs/delayed.json');
|
||||
const Component = Vue.extend(JobContainerItem);
|
||||
let vm;
|
||||
|
||||
const findCiIconComponent = () => wrapper.findComponent(CiIcon);
|
||||
const findGlIconComponent = () => wrapper.findComponent(GlIcon);
|
||||
|
||||
function createComponent(jobData = {}, props = { isActive: false, retried: false }) {
|
||||
wrapper = shallowMount(JobContainerItem, {
|
||||
propsData: {
|
||||
job: {
|
||||
...jobData,
|
||||
retried: props.retried,
|
||||
},
|
||||
isActive: props.isActive,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const sharedTests = () => {
|
||||
describe('when a job is not active and not retried', () => {
|
||||
beforeEach(() => {
|
||||
createComponent(job);
|
||||
});
|
||||
|
||||
it('displays a status icon', () => {
|
||||
expect(vm.$el).toHaveSpriteIcon(job.status.icon);
|
||||
const ciIcon = findCiIconComponent();
|
||||
|
||||
expect(ciIcon.props('status')).toBe(job.status);
|
||||
});
|
||||
|
||||
it('displays the job name', () => {
|
||||
expect(vm.$el.innerText).toContain(job.name);
|
||||
expect(wrapper.text()).toContain(job.name);
|
||||
});
|
||||
|
||||
it('displays a link to the job', () => {
|
||||
const link = vm.$el.querySelector('.js-job-link');
|
||||
const link = wrapper.findComponent(GlLink);
|
||||
|
||||
expect(link.href).toBe(job.status.details_path);
|
||||
expect(link.attributes('href')).toBe(job.status.details_path);
|
||||
});
|
||||
};
|
||||
|
||||
describe('when a job is not active and not retied', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
job,
|
||||
isActive: false,
|
||||
});
|
||||
});
|
||||
|
||||
sharedTests();
|
||||
});
|
||||
|
||||
describe('when a job is active', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
job,
|
||||
isActive: true,
|
||||
});
|
||||
createComponent(job, { isActive: true });
|
||||
});
|
||||
|
||||
sharedTests();
|
||||
it('displays an arrow sprite icon', () => {
|
||||
const icon = findGlIconComponent();
|
||||
|
||||
it('displays an arrow', () => {
|
||||
expect(vm.$el).toHaveSpriteIcon('arrow-right');
|
||||
expect(icon.props('name')).toBe('arrow-right');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a job is retried', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
job: {
|
||||
...job,
|
||||
retried: true,
|
||||
},
|
||||
isActive: false,
|
||||
});
|
||||
createComponent(job, { isActive: false, retried: true });
|
||||
});
|
||||
|
||||
sharedTests();
|
||||
it('displays a retry icon', () => {
|
||||
const icon = findGlIconComponent();
|
||||
|
||||
it('displays an icon', () => {
|
||||
expect(vm.$el).toHaveSpriteIcon('retry');
|
||||
expect(icon.props('name')).toBe('retry');
|
||||
});
|
||||
});
|
||||
|
||||
describe('for delayed job', () => {
|
||||
describe('for a delayed job', () => {
|
||||
beforeEach(() => {
|
||||
const remainingMilliseconds = 1337000;
|
||||
jest
|
||||
|
@ -80,22 +82,16 @@ describe('JobContainerItem', () => {
|
|||
.mockImplementation(
|
||||
() => new Date(delayedJobFixture.scheduled_at).getTime() - remainingMilliseconds,
|
||||
);
|
||||
|
||||
createComponent(delayedJobFixture);
|
||||
});
|
||||
|
||||
it('displays remaining time in tooltip', (done) => {
|
||||
vm = mountComponent(Component, {
|
||||
job: delayedJobFixture,
|
||||
isActive: false,
|
||||
});
|
||||
it('displays remaining time in tooltip', async () => {
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vm.$el.querySelector('.js-job-link').getAttribute('title')).toEqual(
|
||||
'delayed job - delayed manual action (00:22:17)',
|
||||
);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
const link = wrapper.findComponent(GlLink);
|
||||
|
||||
expect(link.attributes('title')).toMatch('delayed job - delayed manual action (00:22:17)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -94,6 +94,70 @@ describe('Getters TestReports Store', () => {
|
|||
|
||||
expect(getters.getSuiteTests(state)).toEqual([]);
|
||||
});
|
||||
|
||||
describe('when a test case classname property is null', () => {
|
||||
it('should return an empty string value for the classname property', () => {
|
||||
const testCases = testReports.test_suites[0].test_cases;
|
||||
setupState({
|
||||
...defaultState,
|
||||
testReports: {
|
||||
...testReports,
|
||||
test_suites: [
|
||||
{
|
||||
test_cases: testCases.map((testCase) => ({
|
||||
...testCase,
|
||||
classname: null,
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const expected = testCases
|
||||
.map((x) => ({
|
||||
...x,
|
||||
classname: '',
|
||||
filePath: `${state.blobPath}/${formatFilePath(x.file)}`,
|
||||
formattedTime: formattedTime(x.execution_time),
|
||||
icon: iconForTestStatus(x.status),
|
||||
}))
|
||||
.slice(0, state.pageInfo.perPage);
|
||||
|
||||
expect(getters.getSuiteTests(state)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a test case name property is null', () => {
|
||||
it('should return an empty string value for the name property', () => {
|
||||
const testCases = testReports.test_suites[0].test_cases;
|
||||
setupState({
|
||||
...defaultState,
|
||||
testReports: {
|
||||
...testReports,
|
||||
test_suites: [
|
||||
{
|
||||
test_cases: testCases.map((testCase) => ({
|
||||
...testCase,
|
||||
name: null,
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const expected = testCases
|
||||
.map((x) => ({
|
||||
...x,
|
||||
name: '',
|
||||
filePath: `${state.blobPath}/${formatFilePath(x.file)}`,
|
||||
formattedTime: formattedTime(x.execution_time),
|
||||
icon: iconForTestStatus(x.status),
|
||||
}))
|
||||
.slice(0, state.pageInfo.perPage);
|
||||
|
||||
expect(getters.getSuiteTests(state)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSuiteTestCount', () => {
|
||||
|
|
|
@ -68,7 +68,7 @@ describe('Test reports suite table', () => {
|
|||
beforeEach(() => createComponent());
|
||||
|
||||
it('renders the correct number of rows', () => {
|
||||
expect(allCaseRows().length).toBe(testCases.length);
|
||||
expect(allCaseRows()).toHaveLength(testCases.length);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
@ -114,4 +114,32 @@ describe('Test reports suite table', () => {
|
|||
expect(wrapper.find(GlPagination).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a test case classname property is null', () => {
|
||||
it('still renders all test cases', () => {
|
||||
createComponent({
|
||||
...testSuite,
|
||||
test_cases: testSuite.test_cases.map((testCase) => ({
|
||||
...testCase,
|
||||
classname: null,
|
||||
})),
|
||||
});
|
||||
|
||||
expect(allCaseRows()).toHaveLength(testCases.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a test case name property is null', () => {
|
||||
it('still renders all test cases', () => {
|
||||
createComponent({
|
||||
...testSuite,
|
||||
test_cases: testSuite.test_cases.map((testCase) => ({
|
||||
...testCase,
|
||||
name: null,
|
||||
})),
|
||||
});
|
||||
|
||||
expect(allCaseRows()).toHaveLength(testCases.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,93 +1,103 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
|
||||
import headerCi from '~/vue_shared/components/header_ci_component.vue';
|
||||
import { GlButton, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import CiIconBadge from '~/vue_shared/components/ci_badge_link.vue';
|
||||
import HeaderCi from '~/vue_shared/components/header_ci_component.vue';
|
||||
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
||||
describe('Header CI Component', () => {
|
||||
let HeaderCi;
|
||||
let vm;
|
||||
let props;
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
HeaderCi = Vue.extend(headerCi);
|
||||
props = {
|
||||
status: {
|
||||
group: 'failed',
|
||||
icon: 'status_failed',
|
||||
label: 'failed',
|
||||
text: 'failed',
|
||||
details_path: 'path',
|
||||
},
|
||||
itemName: 'job',
|
||||
itemId: 123,
|
||||
time: '2017-05-08T14:57:39.781Z',
|
||||
user: {
|
||||
web_url: 'path',
|
||||
name: 'Foo',
|
||||
username: 'foobar',
|
||||
email: 'foo@bar.com',
|
||||
avatar_url: 'link',
|
||||
},
|
||||
hasSidebarButton: true,
|
||||
};
|
||||
});
|
||||
const defaultProps = {
|
||||
status: {
|
||||
group: 'failed',
|
||||
icon: 'status_failed',
|
||||
label: 'failed',
|
||||
text: 'failed',
|
||||
details_path: 'path',
|
||||
},
|
||||
itemName: 'job',
|
||||
itemId: 123,
|
||||
time: '2017-05-08T14:57:39.781Z',
|
||||
user: {
|
||||
web_url: 'path',
|
||||
name: 'Foo',
|
||||
username: 'foobar',
|
||||
email: 'foo@bar.com',
|
||||
avatar_url: 'link',
|
||||
},
|
||||
hasSidebarButton: true,
|
||||
};
|
||||
|
||||
const findIconBadge = () => wrapper.findComponent(CiIconBadge);
|
||||
const findTimeAgo = () => wrapper.findComponent(TimeagoTooltip);
|
||||
const findUserLink = () => wrapper.findComponent(GlLink);
|
||||
const findSidebarToggleBtn = () => wrapper.findComponent(GlButton);
|
||||
const findActionButtons = () => wrapper.findByTestId('ci-header-action-buttons');
|
||||
const findHeaderItemText = () => wrapper.findByTestId('ci-header-item-text');
|
||||
|
||||
const createComponent = (props, slots) => {
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(HeaderCi, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
...slots,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const findActionButtons = () => vm.$el.querySelector('[data-testid="headerButtons"]');
|
||||
|
||||
describe('render', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(HeaderCi, props);
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should render status badge', () => {
|
||||
expect(vm.$el.querySelector('.ci-failed')).toBeDefined();
|
||||
expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined();
|
||||
expect(vm.$el.querySelector('.ci-failed').getAttribute('href')).toEqual(
|
||||
props.status.details_path,
|
||||
);
|
||||
expect(findIconBadge().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render item name and id', () => {
|
||||
expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123');
|
||||
expect(findHeaderItemText().text()).toBe('job #123');
|
||||
});
|
||||
|
||||
it('should render timeago date', () => {
|
||||
expect(vm.$el.querySelector('time')).toBeDefined();
|
||||
expect(findTimeAgo().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render user icon and name', () => {
|
||||
expect(vm.$el.querySelector('.js-user-link').innerText.trim()).toContain(props.user.name);
|
||||
expect(findUserLink().text()).toContain(defaultProps.user.name);
|
||||
});
|
||||
|
||||
it('should render sidebar toggle button', () => {
|
||||
expect(vm.$el.querySelector('.js-sidebar-build-toggle')).not.toBeNull();
|
||||
expect(findSidebarToggleBtn().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should not render header action buttons when empty', () => {
|
||||
expect(findActionButtons()).toBeNull();
|
||||
it('should not render header action buttons when slot is empty', () => {
|
||||
expect(findActionButtons().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('slot', () => {
|
||||
it('should render header action buttons', () => {
|
||||
vm = mountComponentWithSlots(HeaderCi, { props, slots: { default: 'Test Actions' } });
|
||||
createComponent({}, { slots: { default: 'Test Actions' } });
|
||||
|
||||
const buttons = findActionButtons();
|
||||
|
||||
expect(buttons).not.toBeNull();
|
||||
expect(buttons.textContent).toEqual('Test Actions');
|
||||
expect(findActionButtons().exists()).toBe(true);
|
||||
expect(findActionButtons().text()).toBe('Test Actions');
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldRenderTriggeredLabel', () => {
|
||||
it('should rendered created keyword when the shouldRenderTriggeredLabel is false', () => {
|
||||
vm = mountComponent(HeaderCi, { ...props, shouldRenderTriggeredLabel: false });
|
||||
it('should render created keyword when the shouldRenderTriggeredLabel is false', () => {
|
||||
createComponent({ shouldRenderTriggeredLabel: false });
|
||||
|
||||
expect(vm.$el.textContent).toContain('created');
|
||||
expect(vm.$el.textContent).not.toContain('triggered');
|
||||
expect(wrapper.text()).toContain('created');
|
||||
expect(wrapper.text()).not.toContain('triggered');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ RSpec.describe Gitlab::InstrumentationHelper do
|
|||
:rugged_duration_s,
|
||||
:elasticsearch_calls,
|
||||
:elasticsearch_duration_s,
|
||||
:elasticsearch_timed_out_count,
|
||||
:mem_objects,
|
||||
:mem_bytes,
|
||||
:mem_mallocs,
|
||||
|
|
|
@ -8,6 +8,7 @@ RSpec.describe Ci::DailyBuildGroupReportResult do
|
|||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:last_pipeline) }
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to belong_to(:group) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
|
@ -83,8 +84,9 @@ RSpec.describe Ci::DailyBuildGroupReportResult do
|
|||
end
|
||||
|
||||
describe 'scopes' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:recent_build_group_report_result) { create(:ci_daily_build_group_report_result, project: project) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let(:recent_build_group_report_result) { create(:ci_daily_build_group_report_result, project: project, group: group) }
|
||||
let(:old_build_group_report_result) do
|
||||
create(:ci_daily_build_group_report_result, date: 1.week.ago, project: project)
|
||||
end
|
||||
|
@ -97,6 +99,14 @@ RSpec.describe Ci::DailyBuildGroupReportResult do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.by_group' do
|
||||
subject { described_class.by_group(group) }
|
||||
|
||||
it 'returns records by group' do
|
||||
expect(subject).to contain_exactly(recent_build_group_report_result)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.by_ref_path' do
|
||||
subject(:coverages) { described_class.by_ref_path(recent_build_group_report_result.ref_path) }
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ RSpec.describe Group do
|
|||
it { is_expected.to have_many(:dependency_proxy_blobs) }
|
||||
it { is_expected.to have_many(:dependency_proxy_manifests) }
|
||||
it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::GroupDistribution').dependent(:destroy) }
|
||||
it { is_expected.to have_many(:daily_build_group_report_results).class_name('Ci::DailyBuildGroupReportResult') }
|
||||
|
||||
describe '#members & #requesters' do
|
||||
let(:requester) { create(:user) }
|
||||
|
|
Loading…
Reference in a new issue