Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-30 21:09:17 +00:00
parent 74cddf1b7c
commit ad7c2ae366
21 changed files with 410 additions and 83 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
8400d4497656a9f3f692528f9c0118e8898f2d4d5b0ebbaa55ebadea15628041

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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