-
-import { s__ } from '~/locale';
-import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
-import RunnerSingleStat from './runner_single_stat.vue';
-
-export default {
- components: {
- RunnerSingleStat,
- },
- props: {
- scope: {
- type: String,
- required: true,
- },
- variables: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- status: {
- type: String,
- required: true,
- },
- },
- computed: {
- countVariables() {
- return { ...this.variables, status: this.status };
- },
- skip() {
- // Status are mutually exclusive, skip displaying this total
- // when filtering by an status different to this one
- const { status } = this.variables;
- return status && status !== this.status;
- },
- statProps() {
- switch (this.status) {
- case STATUS_ONLINE:
- return {
- variant: 'success',
- title: s__('Runners|Online runners'),
- metaText: s__('Runners|online'),
- };
- case STATUS_OFFLINE:
- return {
- variant: 'muted',
- title: s__('Runners|Offline runners'),
- metaText: s__('Runners|offline'),
- };
- case STATUS_STALE:
- return {
- variant: 'warning',
- title: s__('Runners|Stale runners'),
- metaText: s__('Runners|stale'),
- };
- default:
- return {
- title: s__('Runners|Runners'),
- };
- }
- },
- },
-};
-
-
-
-
diff --git a/app/models/users/group_callout.rb b/app/models/users/group_callout.rb
index 12121bb6748..70498ae83e0 100644
--- a/app/models/users/group_callout.rb
+++ b/app/models/users/group_callout.rb
@@ -22,7 +22,8 @@ module Users
namespace_storage_limit_banner_warning_threshold: 11, # EE-only
namespace_storage_limit_banner_alert_threshold: 12, # EE-only
namespace_storage_limit_banner_error_threshold: 13, # EE-only
- usage_quota_trial_alert: 14 # EE-only
+ usage_quota_trial_alert: 14, # EE-only
+ preview_usage_quota_free_plan_alert: 15 # EE-only
}
validates :group, presence: true
diff --git a/app/presenters/project_hook_presenter.rb b/app/presenters/project_hook_presenter.rb
index a696e9fd0ec..76a3a187924 100644
--- a/app/presenters/project_hook_presenter.rb
+++ b/app/presenters/project_hook_presenter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ProjectHookPresenter < Gitlab::View::Presenter::Delegated
- presents ::ProjectHook, as: :project_hook
+ presents ::ProjectHook
def logs_details_path(log)
project_hook_hook_log_path(project, self, log)
diff --git a/app/presenters/service_hook_presenter.rb b/app/presenters/service_hook_presenter.rb
index b34679c85cf..7ec3d7c5b5c 100644
--- a/app/presenters/service_hook_presenter.rb
+++ b/app/presenters/service_hook_presenter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ServiceHookPresenter < Gitlab::View::Presenter::Delegated
- presents ::ServiceHook, as: :service_hook
+ presents ::ServiceHook
def logs_details_path(log)
project_settings_integration_hook_log_path(integration.project, integration, log)
diff --git a/app/presenters/web_hook_log_presenter.rb b/app/presenters/web_hook_log_presenter.rb
index a5166589073..30941076913 100644
--- a/app/presenters/web_hook_log_presenter.rb
+++ b/app/presenters/web_hook_log_presenter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class WebHookLogPresenter < Gitlab::View::Presenter::Delegated
- presents ::WebHookLog, as: :web_hook_log
+ presents ::WebHookLog
def details_path
web_hook.present.logs_details_path(self)
diff --git a/doc/api/groups.md b/doc/api/groups.md
index a841bad90c2..588ab2a5821 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -1422,7 +1422,7 @@ To delete the LDAP group link, provide either a `cn` or a `filter`, but not both
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/290367) in GitLab 15.3.
-List, add, and delete SAML group links.
+List, get, add, and delete SAML group links.
### List SAML group links
@@ -1432,9 +1432,78 @@ Lists SAML group links.
GET /groups/:id/saml_group_links
```
-| Attribute | Type | Required | Description |
-| --------- | -------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+|:----------|:---------------|:---------|:-------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+
+If successful, returns [`200`](index.md#status-codes) and the following
+response attributes:
+
+| Attribute | Type | Description |
+|:-------------------|:-------|:-------------------------------------------------------------------------------------|
+| `[].name` | string | Name of the SAML group |
+| `[].access_level` | string | Minimum [access level](members.md#valid-access-levels) for members of the SAML group |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/groups/1/saml_group_links"
+```
+
+Example response:
+
+```json
+[
+ {
+ "name": "saml-group-1",
+ "access_level": "Guest"
+ },
+ {
+ "name": "saml-group-2",
+ "access_level": "Maintainer"
+ }
+]
+```
+
+### Get SAML group link
+
+Get a SAML group link for the group.
+
+```plaintext
+GET /groups/:id/saml_group_links/:saml_group_name
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+|:-------------------|:---------------|:---------|:-------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+| `saml_group_name` | string | yes | Name of an SAML group |
+
+If successful, returns [`200`](index.md#status-codes) and the following
+response attributes:
+
+| Attribute | Type | Description |
+|:---------------|:-------|:-------------------------------------------------------------------------------------|
+| `name` | string | Name of the SAML group |
+| `access_level` | string | Minimum [access level](members.md#valid-access-levels) for members of the SAML group |
+
+Example request:
+
+```shell
+curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/groups/1/saml_group_links/saml-group-1"
+```
+
+Example response:
+
+```json
+{
+"name": "saml-group-1",
+"access_level": "Guest"
+}
+```
### Add SAML group link
@@ -1444,11 +1513,36 @@ Adds a SAML group link for a group.
POST /groups/:id/saml_group_links
```
-| Attribute | Type | Required | Description |
-| --------- | -------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
-| `saml_group_name` | string | yes | The name of a SAML group |
-| `access_level` | string | yes | Minimum [access level](members.md#valid-access-levels) for members of the SAML group |
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+|:-------------------|:---------------|:---------|:-------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+| `saml_group_name` | string | yes | Name of a SAML group |
+| `access_level` | string | yes | Minimum [access level](members.md#valid-access-levels) for members of the SAML group |
+
+If successful, returns [`201`](index.md#status-codes) and the following
+response attributes:
+
+| Attribute | Type | Description |
+|:---------------|:-------|:-------------------------------------------------------------------------------------|
+| `name` | string | Name of the SAML group |
+| `access_level` | string | Minimum [access level](members.md#valid-access-levels) for members of the SAML group |
+
+Example request:
+
+```shell
+curl --request POST --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/groups/1/saml_group_links"
+```
+
+Example response:
+
+```json
+{
+"name": "saml-group-1",
+"access_level": "Guest"
+}
+```
### Delete SAML group link
@@ -1458,10 +1552,20 @@ Deletes a SAML group link for the group.
DELETE /groups/:id/saml_group_links/:saml_group_name
```
-| Attribute | Type | Required | Description |
-| --------- | -------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
-| `saml_group_name` | string | yes | The name of an SAML group |
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+|:-------------------|:---------------|:---------|:-------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) |
+| `saml_group_name` | string | yes | Name of an SAML group |
+
+If successful, returns [`204`](index.md#status-codes) status code without any response body.
+
+Example request:
+
+```shell
+curl --request DELETE --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/groups/1/saml_group_links/saml-group-1"
+```
## Namespaces in groups
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index 326ad268546..c689d61ad68 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -169,6 +169,20 @@ By default, impersonation is enabled. GitLab can be configured to [disable imper
![user impersonation button](img/impersonate_user_button_v13_8.png)
+#### User identities
+
+> The ability to see a user's SCIM identity was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/294608) in GitLab 15.3.
+
+When using authentication providers, administrators can see the identities for a user:
+
+1. On the top bar, select **Menu > Admin**.
+1. On the left sidebar, select **Overview > Users**.
+1. From the list of users, select a user.
+1. Select **Identities**.
+
+This list shows the user's identities, including SCIM identities. Administrators can use this information to troubleshoot SCIM-related issues and confirm
+the identities being used for an account.
+
#### User Permission Export **(PREMIUM SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1772) in GitLab 13.8.
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
index 4e9305b88cf..7962f171166 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -220,6 +220,8 @@ It is important that this SCIM `id` and SCIM `externalId` are configured to the
### How do I verify user's SAML NameId matches the SCIM externalId
+Admins can use the Admin Area to [list SCIM identities for a user](../../admin_area/#user-identities).
+
Group owners can see the list of users and the `externalId` stored for each user in the group SAML SSO Settings page.
A possible alternative is to use the [SCIM API](../../../api/scim.md#get-a-list-of-scim-provisioned-users) to manually retrieve the `externalId` we have stored for users, also called the `external_uid` or `NameId`.
diff --git a/doc/user/group/settings/group_access_tokens.md b/doc/user/group/settings/group_access_tokens.md
index 9e8fc120731..c3098bb56c2 100644
--- a/doc/user/group/settings/group_access_tokens.md
+++ b/doc/user/group/settings/group_access_tokens.md
@@ -154,5 +154,8 @@ to groups instead of projects. Bot users for groups:
- Do not count as licensed seats.
- Can have a maximum role of Owner for a group. For more information, see
[Create a group access token](../../../api/group_access_tokens.md#create-a-group-access-token).
+- The username is set to `group_{project_id}_bot` for the first access token. For example, `project_123_bot`.
+- The email is set to `group{group_id}_bot@noreply.{Gitlab.config.gitlab.host}`. For example, `group123_bot@noreply.example.com`.
+- All other properties are similar to [bot users for projects](../../project/settings/project_access_tokens.md#bot-users-for-projects)
For more information, see [Bot users for projects](../../project/settings/project_access_tokens.md#bot-users-for-projects).
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index ef9d989d013..7a6c3e4d53f 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -88,7 +88,7 @@ module API
requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.', allow_blank: false
requires :commit_message, type: String, desc: 'Commit message'
requires :actions, type: Array, desc: 'Actions to perform in commit' do
- requires :action, type: String, desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze
+ requires :action, type: String, desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze, allow_blank: false
requires :file_path, type: String, desc: 'Full path to the file. Ex. `lib/class.rb`'
given action: ->(action) { action == 'move' } do
requires :previous_path, type: String, desc: 'Original full path to the file being moved. Ex. `lib/class1.rb`'
diff --git a/lib/gitlab/alert_management/payload/base.rb b/lib/gitlab/alert_management/payload/base.rb
index 5e535ded439..2d769148c5f 100644
--- a/lib/gitlab/alert_management/payload/base.rb
+++ b/lib/gitlab/alert_management/payload/base.rb
@@ -102,19 +102,19 @@ module Gitlab
# AlertManagement::Alert directly for read operations.
def alert_params
{
- description: description&.truncate(::AlertManagement::Alert::DESCRIPTION_MAX_LENGTH),
+ description: truncate(description, ::AlertManagement::Alert::DESCRIPTION_MAX_LENGTH),
ended_at: ends_at,
environment: environment,
fingerprint: gitlab_fingerprint,
hosts: truncate_hosts(Array(hosts).flatten),
- monitoring_tool: monitoring_tool&.truncate(::AlertManagement::Alert::TOOL_MAX_LENGTH),
+ monitoring_tool: truncate(monitoring_tool, ::AlertManagement::Alert::TOOL_MAX_LENGTH),
payload: payload,
project_id: project.id,
prometheus_alert: gitlab_alert,
- service: service&.truncate(::AlertManagement::Alert::SERVICE_MAX_LENGTH),
+ service: truncate(service, ::AlertManagement::Alert::SERVICE_MAX_LENGTH),
severity: severity,
started_at: starts_at,
- title: title&.truncate(::AlertManagement::Alert::TITLE_MAX_LENGTH)
+ title: truncate(title, ::AlertManagement::Alert::TITLE_MAX_LENGTH)
}.transform_values(&:presence).compact
end
@@ -161,6 +161,10 @@ module Gitlab
SEVERITY_MAPPING
end
+ def truncate(value, length)
+ value.to_s.truncate(length)
+ end
+
def truncate_hosts(hosts)
return hosts if hosts.join.length <= ::AlertManagement::Alert::HOSTS_MAX_LENGTH
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d4810028725..93b74d3d54c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6285,9 +6285,6 @@ msgstr ""
msgid "Billing|Export list"
msgstr ""
-msgid "Billing|From October 19, 2022, free groups will be limited to 5 members"
-msgstr ""
-
msgid "Billing|Group invite"
msgstr ""
@@ -6329,9 +6326,6 @@ msgstr ""
msgid "Billing|You are about to remove user %{username} from your subscription. If you continue, the user will be removed from the %{namespace} group and all its subgroups and projects. This action can't be undone."
msgstr ""
-msgid "Billing|You can begin moving members in %{namespaceName} now. A member loses access to the group when you turn off %{strongStart}In a seat%{strongEnd}. If over 5 members have %{strongStart}In a seat%{strongEnd} enabled after October 19, 2022, we'll select the 5 members who maintain access. We'll first count members that have Owner and Maintainer roles, then the most recently active members until we reach 5 members. The remaining members will get a status of Over limit and lose access to the group."
-msgstr ""
-
msgid "Billing|Your group recently changed to use the Free plan. %{over_limit_message} You can free up space for new members by removing those who no longer need access or toggling them to over-limit. To get an unlimited number of members, you can %{link_start}upgrade%{link_end} to a paid tier."
msgstr ""
@@ -44690,6 +44684,11 @@ msgstr ""
msgid "You can always edit this later"
msgstr ""
+msgid "You can begin moving members in %{namespace_name} now. A member loses access to the group when you turn off %{strong_start}In a seat%{strong_end}. If over %{free_user_limit} member has %{strong_start}In a seat%{strong_end} enabled after October 19, 2022, we'll select the %{free_user_limit} member who maintains access. We'll first count members that have Owner and Maintainer roles, then the most recently active members until we reach %{free_user_limit} member. The remaining members will get a status of Over limit and lose access to the group."
+msgid_plural "You can begin moving members in %{namespace_name} now. A member loses access to the group when you turn off %{strong_start}In a seat%{strong_end}. If over %{free_user_limit} members have %{strong_start}In a seat%{strong_end} enabled after October 19, 2022, we'll select the %{free_user_limit} members who maintain access. We'll first count members that have Owner and Maintainer roles, then the most recently active members until we reach %{free_user_limit} members. The remaining members will get a status of Over limit and lose access to the group."
+msgstr[0] ""
+msgstr[1] ""
+
msgid "You can check it in your %{pat_link_start}personal access tokens%{pat_link_end} settings."
msgstr ""
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_list_item_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js
similarity index 94%
rename from spec/frontend/issues/show/components/incidents/timeline_events_list_item_spec.js
rename to spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js
index e686f2eb4ec..90e55003ab3 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_list_item_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_item_spec.js
@@ -2,7 +2,7 @@ import timezoneMock from 'timezone-mock';
import { GlIcon, GlDropdown } from '@gitlab/ui';
import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import IncidentTimelineEventListItem from '~/issues/show/components/incidents/timeline_events_list_item.vue';
+import IncidentTimelineEventItem from '~/issues/show/components/incidents/timeline_events_item.vue';
import { mockEvents } from './mock_data';
describe('IncidentTimelineEventList', () => {
@@ -10,7 +10,7 @@ describe('IncidentTimelineEventList', () => {
const mountComponent = ({ propsData, provide } = {}) => {
const { action, noteHtml, occurredAt } = mockEvents[0];
- wrapper = mountExtended(IncidentTimelineEventListItem, {
+ wrapper = mountExtended(IncidentTimelineEventItem, {
propsData: {
action,
noteHtml,
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
index ae07237cf7d..4d2d53c990e 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
@@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import IncidentTimelineEventList from '~/issues/show/components/incidents/timeline_events_list.vue';
-import IncidentTimelineEventListItem from '~/issues/show/components/incidents/timeline_events_list_item.vue';
+import IncidentTimelineEventListItem from '~/issues/show/components/incidents/timeline_events_item.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import deleteTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/delete_timeline_event.mutation.graphql';
diff --git a/spec/frontend/runner/components/stat/runner_stats_spec.js b/spec/frontend/runner/components/stat/runner_stats_spec.js
index 241c0c886df..7f1f22be94f 100644
--- a/spec/frontend/runner/components/stat/runner_stats_spec.js
+++ b/spec/frontend/runner/components/stat/runner_stats_spec.js
@@ -1,13 +1,13 @@
import { shallowMount, mount } from '@vue/test-utils';
import { s__ } from '~/locale';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
-import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue';
+import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants';
describe('RunnerStats', () => {
let wrapper;
- const findStatusStats = () => wrapper.findAllComponents(RunnerStatusStat).wrappers;
+ const findSingleStats = () => wrapper.findAllComponents(RunnerSingleStat).wrappers;
const createComponent = ({ props = {}, mountFn = shallowMount, ...options } = {}) => {
wrapper = mountFn(RunnerStats, {
@@ -55,8 +55,8 @@ describe('RunnerStats', () => {
const mockVariables = { paused: true };
createComponent({ props: { variables: mockVariables } });
- findStatusStats().forEach((stat) => {
- expect(stat.props('variables')).toEqual(mockVariables);
+ findSingleStats().forEach((stat) => {
+ expect(stat.props('variables')).toMatchObject(mockVariables);
});
});
});
diff --git a/spec/frontend/runner/components/stat/runner_status_stat_spec.js b/spec/frontend/runner/components/stat/runner_status_stat_spec.js
deleted file mode 100644
index 428b476866e..00000000000
--- a/spec/frontend/runner/components/stat/runner_status_stat_spec.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue';
-import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
-import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE, INSTANCE_TYPE } from '~/runner/constants';
-
-describe('RunnerStatusStat', () => {
- let wrapper;
-
- const findRunnerSingleStat = () => wrapper.findComponent(RunnerSingleStat);
-
- const createComponent = ({ props = {} } = {}) => {
- wrapper = shallowMount(RunnerStatusStat, {
- propsData: {
- scope: INSTANCE_TYPE,
- status: STATUS_ONLINE,
- ...props,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe.each`
- status | variant | title | metaText
- ${STATUS_ONLINE} | ${'success'} | ${'Online runners'} | ${'online'}
- ${STATUS_OFFLINE} | ${'muted'} | ${'Offline runners'} | ${'offline'}
- ${STATUS_STALE} | ${'warning'} | ${'Stale runners'} | ${'stale'}
- `('Renders a stat for status "$status"', ({ status, variant, title, metaText }) => {
- beforeEach(() => {
- createComponent({ props: { status } });
- });
-
- it('Renders text', () => {
- expect(findRunnerSingleStat().attributes()).toMatchObject({
- variant,
- title,
- metatext: metaText,
- });
- });
-
- it('Passes filters', () => {
- expect(findRunnerSingleStat().props('variables')).toEqual({ status });
- });
-
- it('Does not skip query with no filters', () => {
- expect(findRunnerSingleStat().props('skip')).toEqual(false);
- });
- });
-
- it('Merges filters', () => {
- createComponent({ props: { status: STATUS_ONLINE, variables: { paused: true } } });
-
- expect(findRunnerSingleStat().props('variables')).toEqual({
- status: STATUS_ONLINE,
- paused: true,
- });
- });
-
- it('Skips query when other status is in the filters', () => {
- createComponent({ props: { status: STATUS_ONLINE, variables: { status: STATUS_OFFLINE } } });
-
- expect(findRunnerSingleStat().props('skip')).toEqual(true);
- });
-});
diff --git a/spec/lib/gitlab/alert_management/payload/base_spec.rb b/spec/lib/gitlab/alert_management/payload/base_spec.rb
index d3c1a96253c..ad2a3c7b462 100644
--- a/spec/lib/gitlab/alert_management/payload/base_spec.rb
+++ b/spec/lib/gitlab/alert_management/payload/base_spec.rb
@@ -228,6 +228,46 @@ RSpec.describe Gitlab::AlertManagement::Payload::Base do
it { is_expected.to eq({ hosts: shortened_hosts, project_id: project.id }) }
end
end
+
+ context 'with present, non-string values for string fields' do
+ let_it_be(:stubs) do
+ {
+ description: { "description" => "description" },
+ monitoring_tool: ['datadog', 5],
+ service: 4356875,
+ title: true
+ }
+ end
+
+ before do
+ allow(parsed_payload).to receive_messages(stubs)
+ end
+
+ it 'casts values to strings' do
+ is_expected.to eq({
+ description: "{\"description\"=>\"description\"}",
+ monitoring_tool: "[\"datadog\", 5]",
+ service: '4356875',
+ project_id: project.id,
+ title: "true"
+ })
+ end
+ end
+
+ context 'with blank values for string fields' do
+ let_it_be(:stubs) do
+ {
+ description: nil,
+ monitoring_tool: '',
+ service: {},
+ title: []
+ }
+ end
+
+ it 'leaves the fields blank' do
+ is_expected.to eq({ project_id: project.id })
+ end
+ end
end
describe '#gitlab_fingerprint' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index edf072e617d..68fe45cd026 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -979,6 +979,40 @@ RSpec.describe API::Commits do
end
end
+ context 'when action is missing' do
+ let(:params) do
+ {
+ branch: 'master',
+ commit_message: 'Invalid',
+ actions: [{ action: nil, file_path: 'files/ruby/popen.rb' }]
+ }
+ end
+
+ it 'responds with 400 bad request' do
+ post api(url, user), params: params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eq('actions[0][action] is empty')
+ end
+ end
+
+ context 'when action is not supported' do
+ let(:params) do
+ {
+ branch: 'master',
+ commit_message: 'Invalid',
+ actions: [{ action: 'unknown', file_path: 'files/ruby/popen.rb' }]
+ }
+ end
+
+ it 'responds with 400 bad request' do
+ post api(url, user), params: params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eq('actions[0][action] does not have a valid value')
+ end
+ end
+
context 'when committing into a fork as a maintainer' do
include_context 'merge request allowing collaboration'