Commit f3ee45e5 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents db8eed05 070ae2bc
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,
});
};
......@@ -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')
......
# 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
8400d4497656a9f3f692528f9c0118e8898f2d4d5b0ebbaa55ebadea15628041
\ No newline at end of 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))
);
......@@ -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.
......
......@@ -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).
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',
);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment