diff options
27 files changed, 374 insertions, 126 deletions
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_case_details.vue b/app/assets/javascripts/pipelines/components/test_reports/test_case_details.vue index 08f296bec12..2edc84e62cb 100644 --- a/app/assets/javascripts/pipelines/components/test_reports/test_case_details.vue +++ b/app/assets/javascripts/pipelines/components/test_reports/test_case_details.vue @@ -18,8 +18,6 @@ export default { testCase: { type: Object, required: true, - validator: ({ classname, formattedTime, name }) => - Boolean(classname) && Boolean(formattedTime) && Boolean(name), }, }, computed: { diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js index 1685b4a846c..03680de0fa9 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/getters.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js @@ -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) diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index c882e894e7a..80ca62a0e9b 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -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 diff --git a/app/finders/ci/testing/daily_build_group_report_results_finder.rb b/app/finders/ci/testing/daily_build_group_report_results_finder.rb index e12b42c24d2..70d9e55dc47 100644 --- a/app/finders/ci/testing/daily_build_group_report_results_finder.rb +++ b/app/finders/ci/testing/daily_build_group_report_results_finder.rb @@ -84,3 +84,5 @@ module Ci end end end + +Ci::Testing::DailyBuildGroupReportResultsFinder.prepend_if_ee('::EE::Ci::Testing::DailyBuildGroupReportResultsFinder') diff --git a/app/models/ci/daily_build_group_report_result.rb b/app/models/ci/daily_build_group_report_result.rb index 5a1862194f3..23c96e63724 100644 --- a/app/models/ci/daily_build_group_report_result.rb +++ b/app/models/ci/daily_build_group_report_result.rb @@ -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) } diff --git a/app/models/group.rb b/app/models/group.rb index ed8ce67015b..1eaa4499eb5 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -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 diff --git a/changelogs/unreleased/301236-fix-test-report-not-rendering.yml b/changelogs/unreleased/301236-fix-test-report-not-rendering.yml new file mode 100644 index 00000000000..66f4624dd86 --- /dev/null +++ b/changelogs/unreleased/301236-fix-test-report-not-rendering.yml @@ -0,0 +1,5 @@ +--- +title: Fix pipeline test report not rendering when missing properties +merge_request: 54363 +author: +type: fixed diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 5c36f1af960..17ecb324417 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -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. diff --git a/doc/development/fe_guide/dark_mode.md b/doc/development/fe_guide/dark_mode.md new file mode 100644 index 00000000000..dd7ffd1ee6c --- /dev/null +++ b/doc/development/fe_guide/dark_mode.md @@ -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. diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md index 772f1672293..bf1dae6e7bd 100644 --- a/doc/development/fe_guide/frontend_faq.md +++ b/doc/development/fe_guide/frontend_faq.md @@ -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) diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index 9127f5f697b..ad92cf9b4ed 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.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 diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md index 30a21dd7f66..fb9314f7504 100644 --- a/doc/user/project/integrations/bamboo.md +++ b/doc/user/project/integrations/bamboo.md @@ -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. diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md index 0d10f028cbf..2757642e458 100644 --- a/doc/user/project/issues/csv_import.md +++ b/doc/user/project/issues/csv_import.md @@ -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. diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md index f7b0cc3fa86..7aa7673366d 100644 --- a/doc/user/project/merge_requests/allow_collaboration.md +++ b/doc/user/project/merge_requests/allow_collaboration.md @@ -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. diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md index 4ad960413ef..8f3ce9186f1 100644 --- a/doc/user/project/merge_requests/versions.md +++ b/doc/user/project/merge_requests/versions.md @@ -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) diff --git a/doc/user/project/requirements/index.md b/doc/user/project/requirements/index.md index 385231097df..c7dda81685c 100644 --- a/doc/user/project/requirements/index.md +++ b/doc/user/project/requirements/index.md @@ -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 diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index 69f58dad1dc..07f46cb94f7 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -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 diff --git a/lib/gitlab/instrumentation/elasticsearch_transport.rb b/lib/gitlab/instrumentation/elasticsearch_transport.rb index 56179eda22d..4bef043ecb0 100644 --- a/lib/gitlab/instrumentation/elasticsearch_transport.rb +++ b/lib/gitlab/instrumentation/elasticsearch_transport.rb @@ -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? diff --git a/lib/gitlab/instrumentation_helper.rb b/lib/gitlab/instrumentation_helper.rb index 0d1831ebf9d..61de6b02453 100644 --- a/lib/gitlab/instrumentation_helper.rb +++ b/lib/gitlab/instrumentation_helper.rb @@ -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) diff --git a/spec/factories/ci/daily_build_group_report_results.rb b/spec/factories/ci/daily_build_group_report_results.rb index d836ee9567c..55f4f116c97 100644 --- a/spec/factories/ci/daily_build_group_report_results.rb +++ b/spec/factories/ci/daily_build_group_report_results.rb @@ -7,6 +7,7 @@ FactoryBot.define do project last_pipeline factory: :ci_pipeline group_name { 'rspec' } + group data do { 'coverage' => 77.0 } end diff --git a/spec/frontend/jobs/components/job_container_item_spec.js b/spec/frontend/jobs/components/job_container_item_spec.js index af7ce100d83..36038b69e64 100644 --- a/spec/frontend/jobs/components/job_container_item_spec.js +++ b/spec/frontend/jobs/components/job_container_item_spec.js @@ -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, - }); - - 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); + it('displays remaining time in tooltip', async () => { + await wrapper.vm.$nextTick(); + + const link = wrapper.findComponent(GlLink); + + expect(link.attributes('title')).toMatch('delayed job - delayed manual action (00:22:17)'); }); }); }); diff --git a/spec/frontend/pipelines/test_reports/stores/getters_spec.js b/spec/frontend/pipelines/test_reports/stores/getters_spec.js index aa541b6bca5..f8298fdaba5 100644 --- a/spec/frontend/pipelines/test_reports/stores/getters_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/getters_spec.js @@ -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', () => { diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js index 83a5a113d13..a87145cc557 100644 --- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js @@ -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); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/header_ci_component_spec.js b/spec/frontend/vue_shared/components/header_ci_component_spec.js index 5233a64ce5e..b54d120b55b 100644 --- a/spec/frontend/vue_shared/components/header_ci_component_spec.js +++ b/spec/frontend/vue_shared/components/header_ci_component_spec.js @@ -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; - - 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, - }; - }); + let wrapper; + + 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' } }); - - const buttons = findActionButtons(); + createComponent({}, { slots: { default: 'Test Actions' } }); - 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'); }); }); }); diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb index 5cc911accbb..a5c9cde4c37 100644 --- a/spec/lib/gitlab/instrumentation_helper_spec.rb +++ b/spec/lib/gitlab/instrumentation_helper_spec.rb @@ -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, diff --git a/spec/models/ci/daily_build_group_report_result_spec.rb b/spec/models/ci/daily_build_group_report_result_spec.rb index 692e3236822..f6e6a6a5e02 100644 --- a/spec/models/ci/daily_build_group_report_result_spec.rb +++ b/spec/models/ci/daily_build_group_report_result_spec.rb @@ -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) } diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 61e0e4f9053..e79b54b4674 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -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) } |