Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-02-16 21:09:23 +00:00
parent 1eec6b22b2
commit 44e10d208a
27 changed files with 372 additions and 124 deletions

View file

@ -18,8 +18,6 @@ export default {
testCase: {
type: Object,
required: true,
validator: ({ classname, formattedTime, name }) =>
Boolean(classname) && Boolean(formattedTime) && Boolean(name),
},
},
computed: {

View file

@ -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)

View file

@ -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

View file

@ -84,3 +84,5 @@ module Ci
end
end
end
Ci::Testing::DailyBuildGroupReportResultsFinder.prepend_if_ee('::EE::Ci::Testing::DailyBuildGroupReportResultsFinder')

View file

@ -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) }

View file

@ -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

View file

@ -0,0 +1,5 @@
---
title: Fix pipeline test report not rendering when missing properties
merge_request: 54363
author:
type: fixed

View file

@ -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.

View 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.

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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?

View file

@ -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)

View file

@ -7,6 +7,7 @@ FactoryBot.define do
project
last_pipeline factory: :ci_pipeline
group_name { 'rspec' }
group
data do
{ 'coverage' => 77.0 }
end

View file

@ -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)');
});
});
});

View file

@ -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', () => {

View file

@ -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);
});
});
});

View file

@ -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');
});
});
});

View file

@ -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,

View file

@ -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) }

View file

@ -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) }