diff --git a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js index 246f290a90a..5ec5c391964 100644 --- a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js +++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js @@ -1,5 +1,5 @@ import dateFormat from 'dateformat'; -import { isString, mapValues, reduce } from 'lodash'; +import { isString, mapValues, reduce, isDate } from 'lodash'; import { s__, n__, __ } from '../../../locale'; /** @@ -258,3 +258,100 @@ export const parseSeconds = ( return periodCount; }); }; + +/** + * Pads given items with zeros to reach a length of 2 characters. + * + * @param {...any} args Items to be padded. + * @returns {Array} Padded items. + */ +export const padWithZeros = (...args) => args.map((arg) => `${arg}`.padStart(2, '0')); + +/** + * This removes the timezone from an ISO date string. + * This can be useful when populating date/time fields along with a distinct timezone selector, in + * which case we'd want to ignore the timezone's offset when populating the date and time. + * + * Examples: + * stripTimezoneFromISODate('2021-08-16T00:00:00.000-02:00') => '2021-08-16T00:00:00.000' + * stripTimezoneFromISODate('2021-08-16T00:00:00.000Z') => '2021-08-16T00:00:00.000' + * + * @param {String} date The ISO date string representation. + * @returns {String} The ISO date string without the timezone. + */ +export const stripTimezoneFromISODate = (date) => { + if (Number.isNaN(Date.parse(date))) { + return null; + } + return date.replace(/(Z|[+-]\d{2}:\d{2})$/, ''); +}; + +/** + * Extracts the year, month and day from a Date instance and returns them in an object. + * For example: + * dateToYearMonthDate(new Date('2021-08-16')) => { year: '2021', month: '08', day: '16' } + * + * @param {Date} date The date to be parsed + * @returns {Object} An object containing the extracted year, month and day. + */ +export const dateToYearMonthDate = (date) => { + if (!isDate(date)) { + // eslint-disable-next-line @gitlab/require-i18n-strings + throw new Error('Argument should be a Date instance'); + } + const [year, month, day] = date.toISOString().replace(/T.*$/, '').split('-'); + return { year, month, day }; +}; + +/** + * Extracts the hours and minutes from a string representing a time. + * For example: + * timeToHoursMinutes('12:46') => { hours: '12', minutes: '46' } + * + * @param {String} time The time to be parsed in the form HH:MM. + * @returns {Object} An object containing the hours and minutes. + */ +export const timeToHoursMinutes = (time = '') => { + if (!time || !time.match(/\d{1,2}:\d{1,2}/)) { + // eslint-disable-next-line @gitlab/require-i18n-strings + throw new Error('Invalid time provided'); + } + const [hours, minutes] = padWithZeros(...time.split(':')); + return { hours, minutes }; +}; + +/** + * This combines a date and a time and returns the computed Date's ISO string representation. + * + * @param {Date} date Date object representing the base date. + * @param {String} time String representing the time to be used, in the form HH:MM. + * @param {String} offset An optional Date-compatible offset. + * @returns {String} The combined Date's ISO string representation. + */ +export const dateAndTimeToUTCString = (date, time, offset = '') => { + const { year, month, day } = dateToYearMonthDate(date); + const { hours, minutes } = timeToHoursMinutes(time); + + return new Date( + `${year}-${month}-${day}T${hours}:${minutes}:00.000${offset || 'Z'}`, + ).toISOString(); +}; + +/** + * Converts a Date instance to time input-compatible value consisting in a 2-digits hours and + * minutes, separated by a semi-colon, in the 24-hours format. + * + * @param {Date} date Date to be converted + * @returns {String} time input-compatible string in the form HH:MM. + */ +export const dateToTimeInputValue = (date) => { + if (!isDate(date)) { + // eslint-disable-next-line @gitlab/require-i18n-strings + throw new Error('Argument should be a Date instance'); + } + return date.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); +}; diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 156540d455c..93db23978bc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1853,25 +1853,29 @@ class MergeRequest < ApplicationRecord override :ensure_metrics def ensure_metrics - # Backward compatibility: some merge request metrics records will not have target_project_id filled in. - # In that case the first `safe_find_or_create_by` will return false. - # The second finder call will be eliminated in https://gitlab.com/gitlab-org/gitlab/-/issues/233507 - metrics_record = MergeRequest::Metrics.safe_find_or_create_by(merge_request_id: id, target_project_id: target_project_id) || MergeRequest::Metrics.safe_find_or_create_by(merge_request_id: id) + if Feature.enabled?(:use_upsert_query_for_mr_metrics) + MergeRequest::Metrics.record!(self) + else + # Backward compatibility: some merge request metrics records will not have target_project_id filled in. + # In that case the first `safe_find_or_create_by` will return false. + # The second finder call will be eliminated in https://gitlab.com/gitlab-org/gitlab/-/issues/233507 + metrics_record = MergeRequest::Metrics.safe_find_or_create_by(merge_request_id: id, target_project_id: target_project_id) || MergeRequest::Metrics.safe_find_or_create_by(merge_request_id: id) - metrics_record.tap do |metrics_record| - # Make sure we refresh the loaded association object with the newly created/loaded item. - # This is needed in order to have the exact functionality than before. - # - # Example: - # - # merge_request.metrics.destroy - # merge_request.ensure_metrics - # merge_request.metrics # should return the metrics record and not nil - # merge_request.metrics.merge_request # should return the same MR record + metrics_record.tap do |metrics_record| + # Make sure we refresh the loaded association object with the newly created/loaded item. + # This is needed in order to have the exact functionality than before. + # + # Example: + # + # merge_request.metrics.destroy + # merge_request.ensure_metrics + # merge_request.metrics # should return the metrics record and not nil + # merge_request.metrics.merge_request # should return the same MR record - metrics_record.target_project_id = target_project_id - metrics_record.association(:merge_request).target = self - association(:metrics).target = metrics_record + metrics_record.target_project_id = target_project_id + metrics_record.association(:merge_request).target = self + association(:metrics).target = metrics_record + end end end diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb index b9460afa8e7..b984228eb13 100644 --- a/app/models/merge_request/metrics.rb +++ b/app/models/merge_request/metrics.rb @@ -14,8 +14,23 @@ class MergeRequest::Metrics < ApplicationRecord scope :with_valid_time_to_merge, -> { where(arel_table[:merged_at].gt(arel_table[:created_at])) } scope :by_target_project, ->(project) { where(target_project_id: project) } - def self.time_to_merge_expression - Arel.sql('EXTRACT(epoch FROM SUM(AGE(merge_request_metrics.merged_at, merge_request_metrics.created_at)))') + class << self + def time_to_merge_expression + Arel.sql('EXTRACT(epoch FROM SUM(AGE(merge_request_metrics.merged_at, merge_request_metrics.created_at)))') + end + + def record!(mr) + sql = <<~SQL + INSERT INTO #{self.table_name} (merge_request_id, target_project_id, updated_at, created_at) + VALUES (#{mr.id}, #{mr.target_project_id}, NOW(), NOW()) + ON CONFLICT (merge_request_id) + DO UPDATE SET + target_project_id = EXCLUDED.target_project_id, + updated_at = NOW() + SQL + + connection.execute(sql) + end end private diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml index 018dd4c424d..331cb31c626 100644 --- a/app/views/groups/settings/ci_cd/show.html.haml +++ b/app/views/groups/settings/ci_cd/show.html.haml @@ -9,7 +9,7 @@ - if can?(current_user, :update_max_artifacts_size, @group) %section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _("General pipelines") %button.btn.gl-button.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') @@ -26,7 +26,7 @@ %section.settings#runners-settings.no-animate{ class: ('expanded' if expanded) } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _('Runners') %button.btn.gl-button.btn-default.js-settings-toggle{ type: "button" } = expanded ? _('Collapse') : _('Expand') @@ -38,7 +38,7 @@ %section.settings#auto-devops-settings.no-animate{ class: ('expanded' if expanded) } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _('Auto DevOps') %button.btn.gl-button.btn-default.js-settings-toggle{ type: "button" } = expanded ? _('Collapse') : _('Expand') diff --git a/config/feature_flags/development/use_upsert_query_for_mr_metrics.yml b/config/feature_flags/development/use_upsert_query_for_mr_metrics.yml new file mode 100644 index 00000000000..14cc5d1a98c --- /dev/null +++ b/config/feature_flags/development/use_upsert_query_for_mr_metrics.yml @@ -0,0 +1,8 @@ +--- +name: use_upsert_query_for_mr_metrics +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69240 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339677 +milestone: '14.3' +type: development +group: group::optimize +default_enabled: false diff --git a/db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb b/db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb new file mode 100644 index 00000000000..b99a61e8e63 --- /dev/null +++ b/db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +class FinalizeEventsBigintConversion < ActiveRecord::Migration[6.1] + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + TABLE_NAME = 'events' + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: TABLE_NAME, + column_name: 'id', + job_arguments: [["id"], ["id_convert_to_bigint"]] + ) + + swap + end + + def down + swap + end + + private + + def swap + # This is to replace the existing "events_pkey" PRIMARY KEY, btree (id) + add_concurrent_index TABLE_NAME, :id_convert_to_bigint, unique: true, name: 'index_events_on_id_convert_to_bigint' + # This is to replace the existing "index_events_on_project_id_and_id" btree (project_id, id) + add_concurrent_index TABLE_NAME, [:project_id, :id_convert_to_bigint], name: 'index_events_on_project_id_and_id_convert_to_bigint' + # This is to replace the existing "index_events_on_project_id_and_id_desc_on_merged_action" btree (project_id, id DESC) WHERE action = 7 + add_concurrent_index TABLE_NAME, [:project_id, :id_convert_to_bigint], order: { id_convert_to_bigint: :desc }, + where: "action = 7", name: 'index_events_on_project_id_and_id_bigint_desc_on_merged_action' + + # Add a FK on `push_event_payloads(event_id)` to `id_convert_to_bigint`, the old FK (fk_36c74129da) + # will be removed when events_pkey constraint is droppped. + fk_event_id = concurrent_foreign_key_name(:push_event_payloads, :event_id) + fk_event_id_tmp = "#{fk_event_id}_tmp" + add_concurrent_foreign_key :push_event_payloads, TABLE_NAME, + column: :event_id, target_column: :id_convert_to_bigint, + name: fk_event_id_tmp, on_delete: :cascade, reverse_lock_order: true + + with_lock_retries(raise_on_exhaustion: true) do + # We'll need ACCESS EXCLUSIVE lock on the related tables, + # lets make sure it can be acquired from the start. + # Lock order should be + # 1. events + # 2. push_event_payloads + # in order to match the order in EventCreateService#create_push_event, + # and avoid deadlocks. + execute "LOCK TABLE #{TABLE_NAME}, push_event_payloads IN ACCESS EXCLUSIVE MODE" + + # Swap column names + temp_name = 'id_tmp' + execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(:id)} TO #{quote_column_name(temp_name)}" + execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(:id_convert_to_bigint)} TO #{quote_column_name(:id)}" + execute "ALTER TABLE #{quote_table_name(TABLE_NAME)} RENAME COLUMN #{quote_column_name(temp_name)} TO #{quote_column_name(:id_convert_to_bigint)}" + + # We need to update the trigger function in order to make PostgreSQL to + # regenerate the execution plan for it. This is to avoid type mismatch errors like + # "type of parameter 15 (bigint) does not match that when preparing the plan (integer)" + function_name = Gitlab::Database::UnidirectionalCopyTrigger.on_table(TABLE_NAME).name(:id, :id_convert_to_bigint) + execute "ALTER FUNCTION #{quote_table_name(function_name)} RESET ALL" + + # Swap defaults + execute "ALTER SEQUENCE events_id_seq OWNED BY #{TABLE_NAME}.id" + change_column_default TABLE_NAME, :id, -> { "nextval('events_id_seq'::regclass)" } + change_column_default TABLE_NAME, :id_convert_to_bigint, 0 + + # Swap PK constraint + execute "ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT events_pkey CASCADE" # this will drop fk_36c74129da + rename_index TABLE_NAME, 'index_events_on_id_convert_to_bigint', 'events_pkey' + execute "ALTER TABLE #{TABLE_NAME} ADD CONSTRAINT events_pkey PRIMARY KEY USING INDEX events_pkey" + + # Rename the rest of the indexes (we already hold an exclusive lock, so no need to use DROP INDEX CONCURRENTLY here + execute 'DROP INDEX index_events_on_project_id_and_id' + rename_index TABLE_NAME, 'index_events_on_project_id_and_id_convert_to_bigint', 'index_events_on_project_id_and_id' + execute 'DROP INDEX index_events_on_project_id_and_id_desc_on_merged_action' + rename_index TABLE_NAME, 'index_events_on_project_id_and_id_bigint_desc_on_merged_action', 'index_events_on_project_id_and_id_desc_on_merged_action' + + # Change the name of the temporary FK + rename_constraint(:push_event_payloads, fk_event_id_tmp, fk_event_id) + end + end +end diff --git a/db/schema_migrations/20210622045705 b/db/schema_migrations/20210622045705 new file mode 100644 index 00000000000..edf47a46a9d --- /dev/null +++ b/db/schema_migrations/20210622045705 @@ -0,0 +1 @@ +8400d4497656a9f3f692528f9c0118e8898f2d4d5b0ebbaa55ebadea15628041 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 762ee327d62..70fa133912b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12972,7 +12972,7 @@ CREATE SEQUENCE error_tracking_errors_id_seq ALTER SEQUENCE error_tracking_errors_id_seq OWNED BY error_tracking_errors.id; CREATE TABLE events ( - id integer NOT NULL, + id_convert_to_bigint integer DEFAULT 0 NOT NULL, project_id integer, author_id integer NOT NULL, target_id integer, @@ -12982,7 +12982,7 @@ CREATE TABLE events ( target_type character varying, group_id bigint, fingerprint bytea, - id_convert_to_bigint bigint DEFAULT 0 NOT NULL, + id bigint NOT NULL, CONSTRAINT check_97e06e05ad CHECK ((octet_length(fingerprint) <= 128)) ); diff --git a/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md b/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md index 3a016c0e95c..b6e92367f89 100644 --- a/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md +++ b/doc/development/testing_guide/end_to_end/rspec_metadata_tests.md @@ -44,3 +44,4 @@ This is a partial list of the [RSpec metadata](https://relishapp.com/rspec/rspec | `:smtp` | The test requires a GitLab instance to be configured to use an SMTP server. Tests SMTP notification email delivery from GitLab by using MailHog. | | `:testcase` | The link to the test case issue in the [Quality Test Cases project](https://gitlab.com/gitlab-org/quality/testcases/). | | `:transient` | The test tests transient bugs. It is excluded by default. | +| `:issue`, `:issue_${num}` | Optional links to issues which might be related to the spec. Helps keeping track of related issues and can also be used by tools that create test reports. Currently added automatically to `Allure` test report. Multiple tags can be used by adding optional number postfix like `issue_1`, `issue_2` etc. | diff --git a/doc/integration/slash_commands.md b/doc/integration/slash_commands.md index b9b5f394e3c..81c1749e39d 100644 --- a/doc/integration/slash_commands.md +++ b/doc/integration/slash_commands.md @@ -4,7 +4,7 @@ group: Integrations 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 --- -# Slash Commands **(FREE)** +# Slash commands in Mattermost and Slack **(FREE)** > - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24780) to GitLab Free in 11.9. diff --git a/doc/user/admin_area/settings/help_page.md b/doc/user/admin_area/settings/help_page.md index 3d638915496..0912f043712 100644 --- a/doc/user/admin_area/settings/help_page.md +++ b/doc/user/admin_area/settings/help_page.md @@ -68,8 +68,7 @@ You can specify a custom URL to which users are directed when they: ## Redirect `/help` pages -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43157) in GitLab 13.5. -> - Enabled on GitLab.com and is ready for production use. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43157) in GitLab 13.5. FLAG: On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md index dfebf9a1123..3da50524a4d 100644 --- a/doc/user/project/integrations/slack_slash_commands.md +++ b/doc/user/project/integrations/slack_slash_commands.md @@ -8,27 +8,35 @@ info: To determine the technical writer assigned to the Stage/Group associated w > Introduced in GitLab 8.15. -Slack slash commands allow you to control GitLab and view content right inside -Slack, without having to leave it. This requires configurations in both Slack and GitLab. +If you want to control and view GitLab content while you're +working in Slack, you can use Slack slash commands. +To use Slack slash commands, you must configure both Slack and GitLab. -GitLab can also send events (e.g., `issue created`) to Slack as notifications. -This is the separately configured [Slack Notifications Service](slack.md). +GitLab can also send events (for example, `issue created`) to Slack as notifications. +The [Slack notifications service](slack.md) is configured separately. NOTE: -For GitLab.com, use the [Slack app](gitlab_slack_application.md) instead. +For GitLab.com, use the [GitLab Slack app](gitlab_slack_application.md) instead. -## Configuration +## Configure GitLab and Slack -1. Slack slash commands are scoped to a project. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**. -1. Select the **Slack slash commands** integration to configure it. This page contains required information to complete the configuration in Slack. Leave this browser tab open. -1. Open a new browser tab and sign in to your Slack team. [Start a new Slash Commands integration](https://my.slack.com/services/new/slash-commands). -1. Enter a trigger term. We suggest you use the project name. Click **Add Slash Command Integration**. -1. Complete the rest of the fields in the Slack configuration page using information from the GitLab browser tab. In particular, the URL needs to be copied and pasted. Click **Save Integration** to complete the configuration in Slack. -1. While still on the Slack configuration page, copy the **token**. Go back to the GitLab browser tab and paste in the **token**. -1. Ensure that the **Active** toggle is enabled and click **Save changes** to complete the configuration in GitLab. +Slack slash command [integrations](overview.md#accessing-integrations) +are scoped to a project. + +1. In GitLab, on the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Settings > Integrations**. +1. Select **Slack slash commands**. Leave this browser tab open. +1. Open a new browser tab, sign in to your Slack team, and [start a new Slash Commands integration](https://my.slack.com/services/new/slash-commands). +1. Enter a trigger command. We suggest you use the project name. + Select **Add Slash Command Integration**. +1. Complete the rest of the fields in the Slack configuration page using information from the GitLab browser tab. + In particular, make sure you copy and paste the **URL**. +1. On the Slack configuration page, select **Save Integration** and copy the **Token**. +1. Go back to the GitLab configuration page and paste in the **Token**. +1. Ensure the **Active** checkbox is selected and select **Save changes**. ![Slack setup instructions](img/slack_setup.png) -## Usage +## Slash commands -You can now use the [Slack slash commands](../../../integration/slash_commands.md). +You can now use the available [Slack slash commands](../../../integration/slash_commands.md). diff --git a/doc/user/project/merge_requests/getting_started.md b/doc/user/project/merge_requests/getting_started.md index 46fc3ec141d..4f0b2c20769 100644 --- a/doc/user/project/merge_requests/getting_started.md +++ b/doc/user/project/merge_requests/getting_started.md @@ -166,10 +166,13 @@ is set for deletion, the merge request widget displays the ### Branch retargeting on merge **(FREE SELF)** > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/320902) in GitLab 13.9. -> - [Deployed behind a feature flag](../../feature_flags.md), disabled by default. -> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/320895) in GitLab 13.10. -> - Recommended for production use. -> - To use in GitLab self-managed instances, ask a GitLab administrator to [disable it](#enable-or-disable-branch-retargeting-on-merge). **(FREE SELF)** +> - [Disabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/320902) in GitLab 13.9. +> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/320895) GitLab 13.10. + +FLAG: +On self-managed GitLab, by default this feature is available. To hide the feature, +ask an administrator to +[disable the `retarget_merge_requests` flag](../../../administration/feature_flags.md). In specific circumstances, GitLab can retarget the destination branch of open merge request, if the destination branch merges while the merge request is @@ -203,22 +206,3 @@ This improvement is [tracked as a follow-up](https://gitlab.com/gitlab-org/gitla - Take one thing at a time and ship the smallest changes possible. By doing so, reviews are faster and your changes are less prone to errors. - Do not use capital letters nor special chars in branch names. - -### Enable or disable branch retargeting on merge **(FREE SELF)** - -Automatically retargeting merge requests is under development but ready for production use. -It is deployed behind a feature flag that is **enabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) -can opt to disable it. - -To enable it: - -```ruby -Feature.enable(:retarget_merge_requests) -``` - -To disable it: - -```ruby -Feature.disable(:retarget_merge_requests) -``` diff --git a/qa/Gemfile b/qa/Gemfile index 039eaddc166..d3d0fe95e49 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -4,7 +4,7 @@ source 'https://rubygems.org' gem 'gitlab-qa', require: 'gitlab/qa' gem 'activesupport', '~> 6.1.3.2' # This should stay in sync with the root's Gemfile -gem 'allure-rspec', '~> 2.14.1' +gem 'allure-rspec', '~> 2.14.5' gem 'capybara', '~> 3.35.0' gem 'capybara-screenshot', '~> 1.0.23' gem 'rake', '~> 12.3.3' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 940a91d7cc5..1df4550a2ba 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -19,10 +19,10 @@ GEM rack-test (>= 1.1.0, < 2.0) rest-client (>= 2.0.2, < 3.0) rspec (~> 3.8) - allure-rspec (2.14.2) - allure-ruby-commons (= 2.14.2) + allure-rspec (2.14.5) + allure-ruby-commons (= 2.14.5) rspec-core (>= 3.8, < 4) - allure-ruby-commons (2.14.2) + allure-ruby-commons (2.14.5) mime-types (>= 3.3, < 4) oj (>= 3.10, < 4) require_all (>= 2, < 4) @@ -111,7 +111,7 @@ GEM octokit (4.21.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) - oj (3.12.1) + oj (3.13.2) parallel (1.19.2) parallel_tests (2.29.0) parallel @@ -212,7 +212,7 @@ PLATFORMS DEPENDENCIES activesupport (~> 6.1.3.2) airborne (~> 0.3.4) - allure-rspec (~> 2.14.1) + allure-rspec (~> 2.14.5) capybara (~> 3.35.0) capybara-screenshot (~> 1.0.23) chemlab (~> 0.7) diff --git a/qa/qa/runtime/allure_report.rb b/qa/qa/runtime/allure_report.rb index 8ad2562301c..0630e9d333c 100644 --- a/qa/qa/runtime/allure_report.rb +++ b/qa/qa/runtime/allure_report.rb @@ -29,6 +29,13 @@ module QA AllureRspec.configure do |config| config.results_directory = 'tmp/allure-results' config.clean_results_directory = true + + # automatically attach links to testcases and issues + config.tms_tag = :testcase + config.link_tms_pattern = '{}' + config.issue_tag = :issue + config.link_issue_pattern = '{}' + config.environment_properties = environment_info if Env.running_in_ci? # Set custom environment name to separate same specs executed on different environments diff --git a/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb b/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb index 6bbb859b3ee..6737c1ef3b6 100644 --- a/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb +++ b/qa/qa/specs/features/api/1_manage/bulk_import_group_spec.rb @@ -71,9 +71,6 @@ module QA end end - # Non blocking issues: - # https://gitlab.com/gitlab-org/gitlab/-/issues/331252 - # https://gitlab.com/gitlab-org/gitlab/-/issues/333678 <- can cause 500 when creating user and group back to back it( 'imports group with subgroups and labels', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1871' diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb index fe17b5c34e1..842287f6e2e 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb @@ -54,10 +54,14 @@ module QA end end - # Non blocking issues: - # https://gitlab.com/gitlab-org/gitlab/-/issues/331252 - # https://gitlab.com/gitlab-org/gitlab/-/issues/333678 <- can cause 500 when creating user and group back to back - it 'imports group from UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785' do + it( + 'imports group from UI', + testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785', + issue_1: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331252', + issue_2: 'https://gitlab.com/gitlab-org/gitlab/-/issues/333678', + # mostly impacts testing as it makes small groups import slower + issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351' + ) do Page::Group::BulkImport.perform do |import_page| import_page.import_group(imported_group.path, imported_group.sandbox.path) diff --git a/qa/qa/support/allure_metadata_formatter.rb b/qa/qa/support/allure_metadata_formatter.rb index 8a18eeca839..98b7077b0ae 100644 --- a/qa/qa/support/allure_metadata_formatter.rb +++ b/qa/qa/support/allure_metadata_formatter.rb @@ -17,9 +17,6 @@ module QA def example_started(example_notification) example = example_notification.example - testcase = example.metadata[:testcase] - example.tms('Testcase', testcase) if testcase - quarantine_issue = example.metadata.dig(:quarantine, :issue) example.issue('Quarantine issue', quarantine_issue) if quarantine_issue diff --git a/qa/spec/support/allure_metadata_formatter_spec.rb b/qa/spec/support/allure_metadata_formatter_spec.rb index 52a40eff771..f01e5c9f5f8 100644 --- a/qa/spec/support/allure_metadata_formatter_spec.rb +++ b/qa/spec/support/allure_metadata_formatter_spec.rb @@ -34,7 +34,6 @@ describe QA::Support::AllureMetadataFormatter do formatter.example_started(rspec_example_notification) aggregate_failures do - expect(rspec_example).to have_received(:tms).with('Testcase', 'testcase') expect(rspec_example).to have_received(:issue).with('Quarantine issue', 'issue') expect(rspec_example).to have_received(:add_link).with(name: "Job(#{ci_job})", url: ci_job_url) expect(rspec_example).to have_received(:issue).with( diff --git a/spec/frontend/lib/utils/datetime/date_format_utility_spec.js b/spec/frontend/lib/utils/datetime/date_format_utility_spec.js new file mode 100644 index 00000000000..ec3574c1d86 --- /dev/null +++ b/spec/frontend/lib/utils/datetime/date_format_utility_spec.js @@ -0,0 +1,120 @@ +import * as utils from '~/lib/utils/datetime/date_format_utility'; + +describe('date_format_utility.js', () => { + describe('padWithZeros', () => { + it.each` + input | output + ${0} | ${'00'} + ${'1'} | ${'01'} + ${'10'} | ${'10'} + ${'100'} | ${'100'} + ${100} | ${'100'} + ${'a'} | ${'0a'} + ${'foo'} | ${'foo'} + `('properly pads $input to match $output', ({ input, output }) => { + expect(utils.padWithZeros(input)).toEqual([output]); + }); + + it('accepts multiple arguments', () => { + expect(utils.padWithZeros(1, '2', 3)).toEqual(['01', '02', '03']); + }); + + it('returns an empty array provided no argument', () => { + expect(utils.padWithZeros()).toEqual([]); + }); + }); + + describe('stripTimezoneFromISODate', () => { + it.each` + input | expectedOutput + ${'2021-08-16T00:00:00Z'} | ${'2021-08-16T00:00:00'} + ${'2021-08-16T10:30:00+02:00'} | ${'2021-08-16T10:30:00'} + ${'2021-08-16T10:30:00-05:30'} | ${'2021-08-16T10:30:00'} + `('returns $expectedOutput when given $input', ({ input, expectedOutput }) => { + expect(utils.stripTimezoneFromISODate(input)).toBe(expectedOutput); + }); + + it('returns null if date is invalid', () => { + expect(utils.stripTimezoneFromISODate('Invalid date')).toBe(null); + }); + }); + + describe('dateToYearMonthDate', () => { + it.each` + date | expectedOutput + ${new Date('2021-08-05')} | ${{ year: '2021', month: '08', day: '05' }} + ${new Date('2021-12-24')} | ${{ year: '2021', month: '12', day: '24' }} + `('returns $expectedOutput provided $date', ({ date, expectedOutput }) => { + expect(utils.dateToYearMonthDate(date)).toEqual(expectedOutput); + }); + + it('throws provided an invalid date', () => { + expect(() => utils.dateToYearMonthDate('Invalid date')).toThrow( + 'Argument should be a Date instance', + ); + }); + }); + + describe('timeToHoursMinutes', () => { + it.each` + time | expectedOutput + ${'23:12'} | ${{ hours: '23', minutes: '12' }} + ${'23:12'} | ${{ hours: '23', minutes: '12' }} + `('returns $expectedOutput provided $time', ({ time, expectedOutput }) => { + expect(utils.timeToHoursMinutes(time)).toEqual(expectedOutput); + }); + + it('throws provided an invalid time', () => { + expect(() => utils.timeToHoursMinutes('Invalid time')).toThrow('Invalid time provided'); + }); + }); + + describe('dateAndTimeToUTCString', () => { + it('computes the date properly', () => { + expect(utils.dateAndTimeToUTCString(new Date('2021-08-16'), '10:00')).toBe( + '2021-08-16T10:00:00.000Z', + ); + }); + + it('computes the date properly with an offset', () => { + expect(utils.dateAndTimeToUTCString(new Date('2021-08-16'), '10:00', '-04:00')).toBe( + '2021-08-16T14:00:00.000Z', + ); + }); + + it('throws if date in invalid', () => { + expect(() => utils.dateAndTimeToUTCString('Invalid date', '10:00')).toThrow( + 'Argument should be a Date instance', + ); + }); + + it('throws if time in invalid', () => { + expect(() => utils.dateAndTimeToUTCString(new Date('2021-08-16'), '')).toThrow( + 'Invalid time provided', + ); + }); + + it('throws if offset is invalid', () => { + expect(() => + utils.dateAndTimeToUTCString(new Date('2021-08-16'), '10:00', 'not an offset'), + ).toThrow('Invalid time value'); + }); + }); + + describe('dateToTimeInputValue', () => { + it.each` + input | expectedOutput + ${new Date('2021-08-16T10:00:00.000Z')} | ${'10:00'} + ${new Date('2021-08-16T22:30:00.000Z')} | ${'22:30'} + ${new Date('2021-08-16T22:30:00.000-03:00')} | ${'01:30'} + `('extracts $expectedOutput out of $input', ({ input, expectedOutput }) => { + expect(utils.dateToTimeInputValue(input)).toBe(expectedOutput); + }); + + it('throws if date is invalid', () => { + expect(() => utils.dateToTimeInputValue('Invalid date')).toThrow( + 'Argument should be a Date instance', + ); + }); + }); +});