diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-08 09:09:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-09-08 09:09:24 +0300 |
commit | dabcc5d12d22ca30d83c986d6ca0b9b81e7ccbfc (patch) | |
tree | 402520b6779be27a17265dd10b978836e7955e53 | |
parent | 72db8879531eb432b1d3b6957477543d59d94c49 (diff) |
Add latest changes from gitlab-org/gitlab@master
24 files changed, 426 insertions, 104 deletions
diff --git a/app/assets/javascripts/organizations/shared/utils.js b/app/assets/javascripts/organizations/shared/utils.js index 1a77242d6dc..c1aafefc553 100644 --- a/app/assets/javascripts/organizations/shared/utils.js +++ b/app/assets/javascripts/organizations/shared/utils.js @@ -17,7 +17,10 @@ export const formatProjects = (projects) => })); export const formatGroups = (groups) => - groups.map(({ id, ...group }) => ({ + groups.map(({ id, webUrl, ...group }) => ({ ...group, id: getIdFromGraphQLId(id), + webUrl, + editPath: `${webUrl}/-/edit`, + availableActions: [ACTION_EDIT, ACTION_DELETE], })); diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue index 65a601ed927..a1ef1f30ebb 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue @@ -38,7 +38,16 @@ export default { default: CONFIRM_DANGER_MODAL_CANCEL, }, }, + model: { + prop: 'visible', + event: 'change', + }, props: { + visible: { + type: Boolean, + required: false, + default: null, + }, modalId: { type: String, required: true, @@ -89,12 +98,15 @@ export default { <template> <gl-modal ref="modal" + :visible="visible" :modal-id="modalId" :data-testid="modalId" :title="$options.i18n.CONFIRM_DANGER_MODAL_TITLE" :action-primary="actionPrimary" :action-cancel="actionCancel" + size="sm" @primary="$emit('confirm')" + @change="$emit('change', $event)" > <gl-alert v-if="confirmDangerMessage" diff --git a/app/assets/javascripts/vue_shared/components/groups_list/groups_list.vue b/app/assets/javascripts/vue_shared/components/groups_list/groups_list.vue index 7da45169fee..a375a167c68 100644 --- a/app/assets/javascripts/vue_shared/components/groups_list/groups_list.vue +++ b/app/assets/javascripts/vue_shared/components/groups_list/groups_list.vue @@ -24,6 +24,7 @@ export default { :key="group.id" :group="group" :show-group-icon="showGroupIcon" + @delete="$emit('delete', $event)" /> </ul> </template> diff --git a/app/assets/javascripts/vue_shared/components/groups_list/groups_list_item.vue b/app/assets/javascripts/vue_shared/components/groups_list/groups_list_item.vue index 8a301cd0dd0..ca1e7400f2d 100644 --- a/app/assets/javascripts/vue_shared/components/groups_list/groups_list_item.vue +++ b/app/assets/javascripts/vue_shared/components/groups_list/groups_list_item.vue @@ -1,5 +1,6 @@ <script> import { GlAvatarLabeled, GlIcon, GlTooltipDirective, GlTruncateText } from '@gitlab/ui'; +import uniqueId from 'lodash/uniqueId'; import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/visibility_level/constants'; import { ACCESS_LEVEL_LABELS } from '~/access_level/constants'; @@ -7,6 +8,9 @@ import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge. import { __ } from '~/locale'; import { numberToMetricPrefix } from '~/lib/utils/number_utils'; import SafeHtml from '~/vue_shared/directives/safe_html'; +import { ACTION_EDIT, ACTION_DELETE } from '~/vue_shared/components/list_actions/constants'; +import ListActions from '~/vue_shared/components/list_actions/list_actions.vue'; +import DangerConfirmModal from '~/vue_shared/components/confirm_danger/confirm_danger_modal.vue'; export default { i18n: { @@ -25,6 +29,8 @@ export default { GlIcon, UserAccessRoleBadge, GlTruncateText, + ListActions, + DangerConfirmModal, }, directives: { GlTooltip: GlTooltipDirective, @@ -41,6 +47,12 @@ export default { default: false, }, }, + data() { + return { + isDeleteModalVisible: false, + modalId: uniqueId('groups-list-item-modal-id-'), + }; + }, computed: { visibility() { return this.group.visibility; @@ -75,94 +87,131 @@ export default { groupMembersCount() { return numberToMetricPrefix(this.group.groupMembersCount); }, + actions() { + return { + [ACTION_EDIT]: { + href: this.group.editPath, + }, + [ACTION_DELETE]: { + action: this.onActionDelete, + }, + }; + }, + hasActions() { + return this.group.availableActions?.length; + }, + hasActionDelete() { + return this.group.availableActions?.includes(ACTION_DELETE); + }, + }, + methods: { + onActionDelete() { + this.isDeleteModalVisible = true; + }, }, }; </script> <template> - <li class="groups-list-item gl-py-5 gl-md-display-flex gl-align-items-center gl-border-b"> - <div class="gl-display-flex gl-flex-grow-1"> - <gl-icon - v-if="showGroupIcon" - class="gl-mr-3 gl-mt-3 gl-md-mt-5 gl-flex-shrink-0 gl-text-secondary" - :name="groupIconName" - /> - <gl-avatar-labeled - :entity-id="group.id" - :entity-name="group.fullName" - :label="group.fullName" - :label-link="group.webUrl" - shape="rect" - :size="$options.avatarSize" - > - <template #meta> - <div class="gl-px-2"> - <div class="gl-mx-n2 gl-display-flex gl-align-items-center gl-flex-wrap"> - <div class="gl-px-2"> - <gl-icon - v-if="visibility" - v-gl-tooltip="visibilityTooltip" - :name="visibilityIcon" - class="gl-text-secondary" - /> - </div> - <div class="gl-px-2"> - <user-access-role-badge v-if="shouldShowAccessLevel">{{ - accessLevelLabel - }}</user-access-role-badge> + <li class="groups-list-item gl-py-5 gl-border-b gl-display-flex gl-align-items-flex-start"> + <div class="gl-md-display-flex gl-align-items-center gl-flex-grow-1"> + <div class="gl-display-flex gl-flex-grow-1"> + <gl-icon + v-if="showGroupIcon" + class="gl-mr-3 gl-mt-3 gl-md-mt-5 gl-flex-shrink-0 gl-text-secondary" + :name="groupIconName" + /> + <gl-avatar-labeled + :entity-id="group.id" + :entity-name="group.fullName" + :label="group.fullName" + :label-link="group.webUrl" + shape="rect" + :size="$options.avatarSize" + > + <template #meta> + <div class="gl-px-2"> + <div class="gl-mx-n2 gl-display-flex gl-align-items-center gl-flex-wrap"> + <div class="gl-px-2"> + <gl-icon + v-if="visibility" + v-gl-tooltip="visibilityTooltip" + :name="visibilityIcon" + class="gl-text-secondary" + /> + </div> + <div class="gl-px-2"> + <user-access-role-badge v-if="shouldShowAccessLevel">{{ + accessLevelLabel + }}</user-access-role-badge> + </div> </div> </div> + </template> + <gl-truncate-text + v-if="group.descriptionHtml" + :lines="2" + :mobile-lines="2" + :show-more-text="$options.i18n.showMore" + :show-less-text="$options.i18n.showLess" + class="gl-mt-2" + > + <div + v-safe-html:[$options.safeHtmlConfig]="group.descriptionHtml" + class="gl-font-sm md" + data-testid="group-description" + ></div> + </gl-truncate-text> + </gl-avatar-labeled> + </div> + <div + class="gl-md-display-flex gl-flex-direction-column gl-align-items-flex-end gl-flex-shrink-0 gl-mt-3 gl-md-pl-0 gl-md-mt-0 gl-md-ml-3" + :class="statsPadding" + > + <div class="gl-display-flex gl-align-items-center gl-gap-x-3"> + <div + v-gl-tooltip="$options.i18n.subgroups" + :aria-label="$options.i18n.subgroups" + class="gl-text-secondary" + data-testid="subgroups-count" + > + <gl-icon name="subgroup" /> + <span>{{ descendantGroupsCount }}</span> </div> - </template> - <gl-truncate-text - v-if="group.descriptionHtml" - :lines="2" - :mobile-lines="2" - :show-more-text="$options.i18n.showMore" - :show-less-text="$options.i18n.showLess" - class="gl-mt-2" - > <div - v-safe-html:[$options.safeHtmlConfig]="group.descriptionHtml" - class="gl-font-sm md" - data-testid="group-description" - ></div> - </gl-truncate-text> - </gl-avatar-labeled> - </div> - <div - class="gl-md-display-flex gl-flex-direction-column gl-align-items-flex-end gl-flex-shrink-0 gl-mt-3 gl-md-pl-0 gl-md-mt-0 gl-md-ml-3" - :class="statsPadding" - > - <div class="gl-display-flex gl-align-items-center gl-gap-x-3"> - <div - v-gl-tooltip="$options.i18n.subgroups" - :aria-label="$options.i18n.subgroups" - class="gl-text-secondary" - data-testid="subgroups-count" - > - <gl-icon name="subgroup" /> - <span>{{ descendantGroupsCount }}</span> - </div> - <div - v-gl-tooltip="$options.i18n.projects" - :aria-label="$options.i18n.projects" - class="gl-text-secondary" - data-testid="projects-count" - > - <gl-icon name="project" /> - <span>{{ projectsCount }}</span> - </div> - <div - v-gl-tooltip="$options.i18n.directMembers" - :aria-label="$options.i18n.directMembers" - class="gl-text-secondary" - data-testid="members-count" - > - <gl-icon name="users" /> - <span>{{ groupMembersCount }}</span> + v-gl-tooltip="$options.i18n.projects" + :aria-label="$options.i18n.projects" + class="gl-text-secondary" + data-testid="projects-count" + > + <gl-icon name="project" /> + <span>{{ projectsCount }}</span> + </div> + <div + v-gl-tooltip="$options.i18n.directMembers" + :aria-label="$options.i18n.directMembers" + class="gl-text-secondary" + data-testid="members-count" + > + <gl-icon name="users" /> + <span>{{ groupMembersCount }}</span> + </div> </div> </div> </div> + <list-actions + v-if="hasActions" + class="gl-ml-3 gl-md-align-self-center" + :actions="actions" + :available-actions="group.availableActions" + /> + + <danger-confirm-modal + v-if="hasActionDelete" + v-model="isDeleteModalVisible" + :modal-id="modalId" + :phrase="group.fullName" + @confirm="$emit('delete', group)" + /> </li> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue index 96dc2007ecf..05ce007e615 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue @@ -39,6 +39,13 @@ export default { return n__('Apply %d suggestion', 'Apply %d suggestions', this.batchSuggestionsCount); }, + helperText() { + if (this.batchSuggestionsCount <= 1) { + return __('This also resolves this thread'); + } + + return __('This also resolves all related threads'); + }, }, methods: { onApply() { @@ -78,6 +85,10 @@ export default { @submit="onApply" /> + <span class="gl-mt-2 gl-text-secondary"> + {{ helperText }} + </span> + <gl-button class="gl-w-auto! gl-mt-3 gl-align-self-end" category="primary" diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue index 855c7a449c4..8a0ca8ebac1 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue @@ -77,9 +77,7 @@ export default { return this.inapplicableReason; } - return this.batchSuggestionsCount > 1 - ? __('This also resolves all related threads') - : __('This also resolves this thread'); + return false; }, isDisableButton() { return this.isApplying || !this.canApply; diff --git a/config/feature_flags/development/search_index_integrity.yml b/config/feature_flags/development/search_index_integrity.yml deleted file mode 100644 index 87a9536fede..00000000000 --- a/config/feature_flags/development/search_index_integrity.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: search_index_integrity -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112369 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/392981 -milestone: '15.10' -type: development -group: group::global search -default_enabled: true diff --git a/config/initializers/click_house.rb b/config/initializers/click_house.rb index 7230eab1b03..ecf1ccb97bf 100644 --- a/config/initializers/click_house.rb +++ b/config/initializers/click_house.rb @@ -17,6 +17,18 @@ ClickHouse::Client.configure do |config| ) end + if Rails.env.development? || Rails.env.test? + config.logger = ::ClickHouse::Logger.build + config.log_proc = ->(query) do + structured_log(query.to_sql) + end + else + config.logger = Logger.new('/dev/null') + config.log_proc = ->(query) do + structured_log(query.to_redacted_sql) + end + end + config.json_parser = Gitlab::Json config.http_post_proc = ->(url, headers, body) do options = { @@ -32,3 +44,7 @@ ClickHouse::Client.configure do |config| ClickHouse::Client::Response.new(response.body, response.code, response.headers) end end + +def structured_log(query_string) + { query: query_string, correlation_id: Labkit::Correlation::CorrelationId.current_id.to_s } +end diff --git a/doc/development/documentation/testing.md b/doc/development/documentation/testing.md index 0c65e008436..c0f1d0028f9 100644 --- a/doc/development/documentation/testing.md +++ b/doc/development/documentation/testing.md @@ -507,7 +507,21 @@ To configure markdownlint in your editor, install one of the following as approp To configure Vale in your editor, install one of the following as appropriate: -- Sublime Text [`SublimeLinter-vale` package](https://packagecontrol.io/packages/SublimeLinter-vale). +- Sublime Text [`SublimeLinter-vale` package](https://packagecontrol.io/packages/SublimeLinter-vale). To have Vale + suggestions appears as blue instead of red (which is how errors appear), add `vale` configuration to your + [SublimeLinter](http://sublimelinter.readthedocs.org) configuration: + + ```json + "vale": { + "styles": [{ + "mark_style": "outline", + "scope": "region.bluish", + "types": ["suggestion"] + }] + } + ``` + +- [LSP for Sublime Text](https://lsp.sublimetext.io) package [`LSP-vale-ls`](https://packagecontrol.io/packages/LSP-vale-ls). - Visual Studio Code [`ChrisChinchilla.vale-vscode` extension](https://marketplace.visualstudio.com/items?itemName=ChrisChinchilla.vale-vscode). You can configure the plugin to [display only a subset of alerts](#show-subset-of-vale-alerts). - Vim [ALE plugin](https://github.com/dense-analysis/ale). diff --git a/doc/integration/advanced_search/elasticsearch.md b/doc/integration/advanced_search/elasticsearch.md index 29554b405be..4af842915cf 100644 --- a/doc/integration/advanced_search/elasticsearch.md +++ b/doc/integration/advanced_search/elasticsearch.md @@ -547,10 +547,7 @@ Sometimes, you might want to abandon the unfinished reindex job and resume the i > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112369) in GitLab 15.10 [with a flag](../../administration/feature_flags.md) named `search_index_integrity`. Disabled by default. > - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/392981) in GitLab 16.0. > - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/392981) in GitLab 16.3. - -FLAG: -On self-managed GitLab, by default this feature is available. To hide the feature, an administrator can [disable the feature flag](../../administration/feature_flags.md) named `search_index_integrity`. -On GitLab.com, this feature is available. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/392981) in GitLab 16.4. Feature flag `search_index_integrity` removed. Index integrity detects and fixes missing repository data. This feature is automatically used when code searches diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md index 531df693b4d..1ec04f5b3ee 100644 --- a/doc/user/application_security/policies/scan-execution-policies.md +++ b/doc/user/application_security/policies/scan-execution-policies.md @@ -98,6 +98,11 @@ the following sections and tables provide an alternative. > - The `branch_type` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/404774) in GitLab 16.1 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_type`. Disabled by default. > - Generally available in GitLab 16.2. Feature flag `security_policies_branch_type` removed. +> - The `branch_exceptions` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418741) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. Disabled by default. + +FLAG: +On self-managed GitLab, by default the `branch_exceptions` field is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. +On GitLab.com, this feature is not available. This rule enforces the defined actions whenever the pipeline runs for a selected branch. @@ -106,6 +111,7 @@ This rule enforces the defined actions whenever the pipeline runs for a selected | `type` | `string` | true | `pipeline` | The rule's type. | | `branches` <sup>1</sup> | `array` of `string` | true if `branch_type` field does not exist | `*` or the branch's name | The branch the given policy applies to (supports wildcard). | | `branch_type` <sup>1</sup> | `string` | true if `branches` field does not exist | `default`, `protected` or `all` | The types of branches the given policy applies to. | +| `branch_exceptions` | `array` of `string` | false | Names of branches | Branches to exclude from this rule. | 1. You must specify only one of `branches` or `branch_type`. @@ -113,6 +119,11 @@ This rule enforces the defined actions whenever the pipeline runs for a selected > - The `branch_type` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/404774) in GitLab 16.1 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_type`. Disabled by default. > - Generally available in GitLab 16.2. Feature flag `security_policies_branch_type` removed. +> - The `branch_exceptions` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418741) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. Disabled by default. + +FLAG: +On self-managed GitLab, by default the `branch_exceptions` field is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. +On GitLab.com, this feature is not available. This rule schedules a scan pipeline, enforcing the defined actions on the schedule defined in the `cadence` field. A scheduled pipeline does not run other jobs defined in the project's `.gitlab-ci.yml` file. When a project is linked to a security policy project, a security policy bot is created in the project and will become the author of any scheduled pipelines. @@ -121,6 +132,7 @@ This rule schedules a scan pipeline, enforcing the defined actions on the schedu | `type` | `string` | true | `schedule` | The rule's type. | | `branches` <sup>1</sup> | `array` of `string` | true if either `branch_type` or `agents` fields does not exist | `*` or the branch's name | The branch the given policy applies to (supports wildcard). | | `branch_type` <sup>1</sup> | `string` | true if either `branches` or `agents` fields does not exist | `default`, `protected` or `all` | The types of branches the given policy applies to. | +| `branch_exceptions` | `array` of `string` | false | Names of branches | Branches to exclude from this rule. | | `cadence` | `string` | true | CRON expression (for example, `0 0 * * *`) | A whitespace-separated string containing five fields that represents the scheduled time. Minimum of 15 minute intervals when used together with the `branches` field. | | `timezone` | `string` | false | Time zone identifier (for example, `America/New_York`) | Time zone to apply to the cadence. Value must be an IANA Time Zone Database identifier. | | `agents` <sup>1</sup> | `object` | true if either `branch_type` or `branches` fields do not exists | | The name of the [GitLab agents](../../clusters/agent/index.md) where [Operational Container Scanning](../../clusters/agent/vulnerabilities.md) runs. The object key is the name of the Kubernetes agent configured for your project in GitLab. | diff --git a/doc/user/application_security/policies/scan-result-policies.md b/doc/user/application_security/policies/scan-result-policies.md index f7b0f68e10c..60173bfb909 100644 --- a/doc/user/application_security/policies/scan-result-policies.md +++ b/doc/user/application_security/policies/scan-result-policies.md @@ -104,10 +104,16 @@ On GitLab.com, this feature is not available. > - The scan result policy field `vulnerability_attributes` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123052) in GitLab 16.2 [with a flag](../../../administration/feature_flags.md) named `enforce_vulnerability_attributes_rules`. Disabled by default. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/418784) in GitLab 16.3. > - The scan result policy field `vulnerability_age` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123956) in GitLab 16.2. +> - The `branch_exceptions` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418741) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. Disabled by default. FLAG: On self-managed GitLab, by default the `vulnerability_attributes` field is available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `enforce_vulnerability_attributes_rules`. On GitLab.com, this feature is available. + +FLAG: +On self-managed GitLab, by default the `branch_exceptions` field is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. +On GitLab.com, this feature is not available. + This rule enforces the defined actions based on security scan findings. | Field | Type | Required | Possible values | Description | @@ -115,6 +121,7 @@ This rule enforces the defined actions based on security scan findings. | `type` | `string` | true | `scan_finding` | The rule's type. | | `branches` | `array` of `string` | true if `branch_type` field does not exist | `[]` or the branch's name | Applicable only to protected target branches. An empty array, `[]`, applies the rule to all protected target branches. Cannot be used with the `branch_type` field. | | `branch_type` | `string` | true if `branches` field does not exist | `default` or `protected` | The types of branches the given policy applies to. Cannot be used with the `branches` field. | +| `branch_exceptions` | `array` of `string` | false | Names of branches | Branches to exclude from this rule. | | `scanners` | `array` of `string` | true | `sast`, `secret_detection`, `dependency_scanning`, `container_scanning`, `dast`, `coverage_fuzzing`, `api_fuzzing` | The security scanners for this rule to consider. `sast` includes results from both SAST and SAST IaC scanners. | | `vulnerabilities_allowed` | `integer` | true | Greater than or equal to zero | Number of vulnerabilities allowed before this rule is considered. | | `severity_levels` | `array` of `string` | true | `info`, `unknown`, `low`, `medium`, `high`, `critical` | The severity levels for this rule to consider. | @@ -126,6 +133,11 @@ This rule enforces the defined actions based on security scan findings. > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/8092) in GitLab 15.9 [with a flag](../../../administration/feature_flags.md) named `license_scanning_policies`. > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/397644) in GitLab 15.11. Feature flag `license_scanning_policies` removed. +> - The `branch_exceptions` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418741) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. Disabled by default. + +FLAG: +On self-managed GitLab, by default the `branch_exceptions` field is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. +On GitLab.com, this feature is not available. This rule enforces the defined actions based on license findings. @@ -134,22 +146,29 @@ This rule enforces the defined actions based on license findings. | `type` | `string` | true | `license_finding` | The rule's type. | | `branches` | `array` of `string` | true if `branch_type` field does not exist | `[]` or the branch's name | Applicable only to protected target branches. An empty array, `[]`, applies the rule to all protected target branches. Cannot be used with the `branch_type` field. | | `branch_type` | `string` | true if `branches` field does not exist | `default` or `protected` | The types of branches the given policy applies to. Cannot be used with the `branches` field. | +| `branch_exceptions` | `array` of `string` | false | Names of branches | Branches to exclude from this rule. | | `match_on_inclusion` | `boolean` | true | `true`, `false` | Whether the rule matches inclusion or exclusion of licenses listed in `license_types`. | | `license_types` | `array` of `string` | true | license types | [SPDX license names](https://spdx.org/licenses) to match on, for example `Affero General Public License v1.0` or `MIT License`. | | `license_states` | `array` of `string` | true | `newly_detected`, `detected` | Whether to match newly detected and/or previously detected licenses. The `newly_detected` state triggers approval when either a new package is introduced or when a new license for an existing package is detected. | ## `any_merge_request` rule type -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418752) in GitLab 16.4. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418752) in GitLab 16.4. +> - The `branch_exceptions` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418741) in GitLab 16.3 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. Disabled by default. + +FLAG: +On self-managed GitLab, by default the `branch_exceptions` field is not available. To make it available, an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `security_policies_branch_exceptions`. +On GitLab.com, this feature is not available. This rule enforces the defined actions for any merge request based on the commits signature. | Field | Type | Required | Possible values | Description | |---------------|---------------------|--------------------------------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `type` | `string` | true | `any_merge_request` | The rule's type. | +| `type` | `string` | true | `any_merge_request` | The rule's type. | | `branches` | `array` of `string` | true if `branch_type` field does not exist | `[]` or the branch's name | Applicable only to protected target branches. An empty array, `[]`, applies the rule to all protected target branches. Cannot be used with the `branch_type` field. | -| `branch_type` | `string` | true if `branches` field does not exist | `default` or `protected` | The types of branches the given policy applies to. Cannot be used with the `branches` field. | -| `commits` | `string` | true | `any`, `unsigned` | Whether the rule matches for any commits, or only if unsigned commits are detected in the merge request. | +| `branch_type` | `string` | true if `branches` field does not exist | `default` or `protected` | The types of branches the given policy applies to. Cannot be used with the `branches` field. | +| `branch_exceptions` | `array` of `string` | false | Names of branches | Branches to exclude from this rule. | +| `commits` | `string` | true | `any`, `unsigned` | Whether the rule matches for any commits, or only if unsigned commits are detected in the merge request. | ## `require_approval` action type diff --git a/gems/click_house-client/lib/click_house/client.rb b/gems/click_house-client/lib/click_house/client.rb index 2a9c5e402f5..1ca3653c45f 100644 --- a/gems/click_house-client/lib/click_house/client.rb +++ b/gems/click_house-client/lib/click_house/client.rb @@ -100,6 +100,10 @@ module ClickHouse db = lookup_database(configuration, database) query = ClickHouse::Client::Query.build(query) + + log_contents = configuration.log_proc.call(query) + configuration.logger.info(log_contents) + ActiveSupport::Notifications.instrument('sql.click_house', { query: query, database: database }) do |instrument| # Use a multipart POST request where the placeholders are sent with the param_ prefix # See: https://github.com/ClickHouse/ClickHouse/issues/8842 diff --git a/gems/click_house-client/lib/click_house/client/configuration.rb b/gems/click_house-client/lib/click_house/client/configuration.rb index 882b37993dc..4a71934466f 100644 --- a/gems/click_house-client/lib/click_house/client/configuration.rb +++ b/gems/click_house-client/lib/click_house/client/configuration.rb @@ -18,6 +18,9 @@ module ClickHouse # # *json_parser*: object for parsing JSON strings, it should respond to the "parse" method # + # *logger*: object for receiving logger commands. Default `$stdout` + # *log_proc*: any output (e.g. structure) to wrap around the query for every statement + # # Example: # # Gitlab::ClickHouse::Client.configure do |c| @@ -31,6 +34,11 @@ module ClickHouse # } # ) # + # c.logger = MyLogger.new + # c.log_proc = ->(query) do + # { query_body: query.to_redacted_sql } + # end + # # c.http_post_proc = lambda do |url, headers, body| # options = { # headers: headers, @@ -44,13 +52,15 @@ module ClickHouse # # c.json_parser = JSON # end - attr_accessor :http_post_proc, :json_parser + attr_accessor :http_post_proc, :json_parser, :logger, :log_proc attr_reader :databases def initialize @databases = {} @http_post_proc = nil @json_parser = JSON + @logger = ::Logger.new($stdout) + @log_proc = ->(query) { query.to_sql } end def register_database(name, **args) diff --git a/gems/click_house-client/spec/click_house/client_spec.rb b/gems/click_house-client/spec/click_house/client_spec.rb index 51cc097a228..ab2407a83d7 100644 --- a/gems/click_house-client/spec/click_house/client_spec.rb +++ b/gems/click_house-client/spec/click_house/client_spec.rb @@ -30,6 +30,9 @@ RSpec.describe ClickHouse::Client do let(:configuration) do ClickHouse::Client::Configuration.new.tap do |config| + config.log_proc = ->(query) do + { query_string: query.to_sql } + end config.register_database(:test_db, **database_config) config.http_post_proc = ->(_url, _headers, _query) { body = File.read(query_result_fixture) @@ -94,5 +97,34 @@ RSpec.describe ClickHouse::Client do end.to raise_error(ClickHouse::Client::DatabaseError, 'some error') end end + + describe 'default logging' do + let(:fake_logger) { instance_double("Logger", info: 'logged!') } + let(:query_string) { 'SELECT * FROM issues' } + + before do + configuration.logger = fake_logger + end + + shared_examples 'proper logging' do + it 'calls the custom logger and log_proc' do + expect(fake_logger).to receive(:info).at_least(:once).with({ query_string: query_string }) + + described_class.select(query_object, :test_db, configuration) + end + end + + context 'when query is a string' do # rubocop:disable RSpec/MultipleMemoizedHelpers + let(:query_object) { query_string } + + it_behaves_like 'proper logging' + end + + context 'when query is a Query object' do # rubocop:disable RSpec/MultipleMemoizedHelpers + let(:query_object) { ClickHouse::Client::Query.new(raw_query: query_string) } + + it_behaves_like 'proper logging' + end + end end end diff --git a/lib/click_house/logger.rb b/lib/click_house/logger.rb new file mode 100644 index 00000000000..90af644799b --- /dev/null +++ b/lib/click_house/logger.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ClickHouse + class Logger < ::Gitlab::JsonLogger + exclude_context! + + def self.file_name_noext + 'clickhouse' + end + end +end diff --git a/spec/frontend/organizations/shared/utils_spec.js b/spec/frontend/organizations/shared/utils_spec.js index 2912db739c3..778a18ab2bc 100644 --- a/spec/frontend/organizations/shared/utils_spec.js +++ b/spec/frontend/organizations/shared/utils_spec.js @@ -29,7 +29,11 @@ describe('formatGroups', () => { const formattedGroups = formatGroups(organizationGroups.nodes); const [firstFormattedGroup] = formattedGroups; - expect(firstFormattedGroup.id).toBe(getIdFromGraphQLId(firstMockGroup.id)); + expect(firstFormattedGroup).toMatchObject({ + id: getIdFromGraphQLId(firstMockGroup.id), + editPath: `${firstFormattedGroup.webUrl}/-/edit`, + availableActions: [ACTION_EDIT, ACTION_DELETE], + }); expect(formattedGroups.length).toBe(organizationGroups.nodes.length); }); }); diff --git a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js index 0b5c8d9afc3..53218d794c7 100644 --- a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js +++ b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js @@ -31,6 +31,7 @@ describe('Confirm Danger Modal', () => { propsData: { modalId, phrase, + visible: false, }, provide, stubs: { GlSprintf }, @@ -103,4 +104,16 @@ describe('Confirm Danger Modal', () => { expect(wrapper.emitted('confirm')).not.toBeUndefined(); }); }); + + describe('v-model', () => { + it('emit `change` event', () => { + findModal().vm.$emit('change', true); + + expect(wrapper.emitted('change')).toEqual([[true]]); + }); + + it('sets `visible` prop', () => { + expect(findModal().props('visible')).toBe(false); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/groups_list/groups_list_item_spec.js b/spec/frontend/vue_shared/components/groups_list/groups_list_item_spec.js index 877de4f4695..cba9f78790d 100644 --- a/spec/frontend/vue_shared/components/groups_list/groups_list_item_spec.js +++ b/spec/frontend/vue_shared/components/groups_list/groups_list_item_spec.js @@ -9,6 +9,9 @@ import { } from '~/visibility_level/constants'; import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue'; import { ACCESS_LEVEL_LABELS } from '~/access_level/constants'; +import ListActions from '~/vue_shared/components/list_actions/list_actions.vue'; +import { ACTION_EDIT, ACTION_DELETE } from '~/vue_shared/components/list_actions/constants'; +import DangerConfirmModal from '~/vue_shared/components/confirm_danger/confirm_danger_modal.vue'; import { groups } from './mock_data'; describe('GroupsListItem', () => { @@ -30,6 +33,8 @@ describe('GroupsListItem', () => { const findAvatarLabeled = () => wrapper.findComponent(GlAvatarLabeled); const findGroupDescription = () => wrapper.findByTestId('group-description'); const findVisibilityIcon = () => findAvatarLabeled().findComponent(GlIcon); + const findListActions = () => wrapper.findComponent(ListActions); + const findConfirmationModal = () => wrapper.findComponent(DangerConfirmModal); it('renders group avatar', () => { createComponent(); @@ -179,4 +184,68 @@ describe('GroupsListItem', () => { expect(wrapper.findByTestId('group-icon').exists()).toBe(false); }); }); + + describe('when group has actions', () => { + beforeEach(() => { + createComponent(); + }); + + it('displays actions dropdown', () => { + expect(findListActions().props()).toMatchObject({ + actions: { + [ACTION_EDIT]: { + href: group.editPath, + }, + [ACTION_DELETE]: { + action: expect.any(Function), + }, + }, + availableActions: [ACTION_EDIT, ACTION_DELETE], + }); + }); + + describe('when delete action is fired', () => { + beforeEach(() => { + findListActions().props('actions')[ACTION_DELETE].action(); + }); + + it('displays confirmation modal with correct props', () => { + expect(findConfirmationModal().props()).toMatchObject({ + visible: true, + phrase: group.fullName, + }); + }); + + describe('when deletion is confirmed', () => { + beforeEach(() => { + findConfirmationModal().vm.$emit('confirm'); + }); + + it('emits `delete` event', () => { + expect(wrapper.emitted('delete')).toMatchObject([[group]]); + }); + }); + }); + }); + + describe('when group does not have actions', () => { + beforeEach(() => { + createComponent({ + propsData: { + group: { + ...group, + availableActions: [], + }, + }, + }); + }); + + it('does not display actions dropdown', () => { + expect(findListActions().exists()).toBe(false); + }); + + it('does not display confirmation modal', () => { + expect(findConfirmationModal().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/groups_list/groups_list_spec.js b/spec/frontend/vue_shared/components/groups_list/groups_list_spec.js index c65aa347bcf..ec6a1dc9576 100644 --- a/spec/frontend/vue_shared/components/groups_list/groups_list_spec.js +++ b/spec/frontend/vue_shared/components/groups_list/groups_list_spec.js @@ -31,4 +31,18 @@ describe('GroupsList', () => { })), ); }); + + describe('when `GroupsListItem` emits `delete` event', () => { + const [firstGroup] = defaultPropsData.groups; + + beforeEach(() => { + createComponent(); + + wrapper.findComponent(GroupsListItem).vm.$emit('delete', firstGroup); + }); + + it('emits `delete` event', () => { + expect(wrapper.emitted('delete')).toEqual([[firstGroup]]); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/groups_list/mock_data.js b/spec/frontend/vue_shared/components/groups_list/mock_data.js index 0dad27f8311..08ee962892c 100644 --- a/spec/frontend/vue_shared/components/groups_list/mock_data.js +++ b/spec/frontend/vue_shared/components/groups_list/mock_data.js @@ -1,3 +1,5 @@ +import { ACTION_EDIT, ACTION_DELETE } from '~/vue_shared/components/list_actions/constants'; + export const groups = [ { id: 1, @@ -14,6 +16,8 @@ export const groups = [ accessLevel: { integerValue: 10, }, + editPath: 'http://127.0.0.1:3000/groups/gitlab-org/-/edit', + availableActions: [ACTION_EDIT, ACTION_DELETE], }, { id: 2, @@ -31,5 +35,7 @@ export const groups = [ accessLevel: { integerValue: 20, }, + editPath: 'http://127.0.0.1:3000/groups/gitlab-org/test-subgroup/-/edit', + availableActions: [ACTION_EDIT, ACTION_DELETE], }, ]; diff --git a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js index e3d896ca93e..cdbdbfab9d1 100644 --- a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js +++ b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js @@ -14,6 +14,7 @@ describe('Apply Suggestion component', () => { const findTextArea = () => wrapper.findComponent(GlFormTextarea); const findApplyButton = () => wrapper.findComponent(GlButton); const findAlert = () => wrapper.findComponent(GlAlert); + const findHelpText = () => wrapper.find('span'); beforeEach(() => createWrapper()); @@ -41,6 +42,22 @@ describe('Apply Suggestion component', () => { }); }); + describe('help text', () => { + describe('when applying a single suggestion', () => { + it('renders the correct help text', () => { + expect(findHelpText().text()).toEqual('This also resolves this thread'); + }); + }); + + describe('when applying in batch', () => { + it('renders the correct help text', () => { + createWrapper({ batchSuggestionsCount: 3 }); + + expect(findHelpText().text()).toEqual('This also resolves all related threads'); + }); + }); + }); + describe('disabled', () => { it('disables the dropdown', () => { createWrapper({ disabled: true }); diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js index 9768bc7a6dd..bc82357cb81 100644 --- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -219,12 +219,11 @@ describe('Suggestion Diff component', () => { describe('tooltip message for apply button', () => { const findTooltip = () => getBinding(findApplyButton().element, 'gl-tooltip'); - it('renders correct tooltip message when button is applicable', () => { - createComponent({ batchSuggestionsCount: 0 }); + it('renders no tooltip message when button is applicable', () => { + createComponent({ batchSuggestionsCount: 1, isBatched: true }); const tooltip = findTooltip(); - expect(tooltip.modifiers.viewport).toBe(true); - expect(tooltip.value).toBe('This also resolves this thread'); + expect(tooltip.value).toBe(false); }); it('renders the inapplicable reason in the tooltip when button is not applicable', () => { diff --git a/spec/lib/gitlab/database/click_house_client_spec.rb b/spec/lib/gitlab/database/click_house_client_spec.rb index 8e4a474cc9b..6e63ae56557 100644 --- a/spec/lib/gitlab/database/click_house_client_spec.rb +++ b/spec/lib/gitlab/database/click_house_client_spec.rb @@ -115,4 +115,23 @@ RSpec.describe 'ClickHouse::Client', :click_house, feature_category: :database d end end end + + describe 'logging' do + let(:query_string) { "SELECT * FROM events WHERE id IN (4, 5, 6)" } + + context 'on dev and test environments' do + it 'logs the un-redacted query' do + expect(ClickHouse::Client.configuration.logger).to receive(:info).with({ + query: query_string, + correlation_id: a_kind_of(String) + }) + + ClickHouse::Client.select(query_string, :main) + end + + it 'has a ClickHouse logger' do + expect(ClickHouse::Client.configuration.logger).to be_a(ClickHouse::Logger) + end + end + end end |