Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-07 00:08:19 +00:00
parent 317e9df33c
commit 175f124d93
17 changed files with 391 additions and 156 deletions

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlFormGroup, GlSafeHtmlDirective } from '@gitlab/ui';
import { GlButton, GlFormGroup } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { helpPagePath } from '~/helpers/help_page_helper';
import { getDraft, clearDraft, updateDraft } from '~/lib/utils/autosave';
@ -11,16 +11,15 @@ import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { getWorkItemQuery } from '../utils';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
import { i18n, TRACKING_CATEGORY_SHOW, WIDGET_TYPE_DESCRIPTION } from '../constants';
import WorkItemDescriptionRendered from './work_item_description_rendered.vue';
export default {
directives: {
SafeHtml: GlSafeHtmlDirective,
},
components: {
EditedAt,
GlButton,
GlFormGroup,
MarkdownField,
WorkItemDescriptionRendered,
},
mixins: [Tracking.mixin()],
props: {
@ -50,6 +49,7 @@ export default {
isSubmitting: false,
isSubmittingWithKeydown: false,
descriptionText: '',
descriptionHtml: '',
};
},
apollo: {
@ -66,6 +66,10 @@ export default {
skip() {
return !this.workItemId;
},
result() {
this.descriptionText = this.workItemDescription?.description;
this.descriptionHtml = this.workItemDescription?.descriptionHtml;
},
error() {
this.error = i18n.fetchError;
},
@ -76,7 +80,7 @@ export default {
return this.workItemId;
},
canEdit() {
return this.workItem?.userPermissions?.updateWorkItem;
return this.workItem?.userPermissions?.updateWorkItem || false;
},
tracking() {
return {
@ -85,12 +89,6 @@ export default {
property: `type_${this.workItemType}`,
};
},
descriptionHtml() {
return this.workItemDescription?.descriptionHtml;
},
descriptionEmpty() {
return this.descriptionHtml?.trim() === '';
},
workItemDescription() {
const descriptionWidget = this.workItem?.widgets?.find(
(widget) => widget.type === WIDGET_TYPE_DESCRIPTION,
@ -154,8 +152,10 @@ export default {
updateDraft(this.autosaveKey, this.descriptionText);
},
async updateWorkItem(event) {
if (event.key) {
async updateWorkItem(event = {}) {
const { key } = event;
if (key) {
this.isSubmittingWithKeydown = true;
}
@ -191,73 +191,70 @@ export default {
this.isSubmitting = false;
},
handleDescriptionTextUpdated(newText) {
this.descriptionText = newText;
this.updateWorkItem();
},
},
};
</script>
<template>
<gl-form-group
v-if="isEditing"
class="gl-my-5 gl-border-t gl-pt-6"
:label="__('Description')"
label-for="work-item-description"
>
<markdown-field
can-attach-file
:textarea-value="descriptionText"
:is-submitting="isSubmitting"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="$options.markdownDocsPath"
class="gl-p-3 bordered-box gl-mt-5"
<div>
<gl-form-group
v-if="isEditing"
class="gl-mb-5 gl-border-t gl-pt-6"
:label="__('Description')"
label-for="work-item-description"
>
<template #textarea>
<textarea
id="work-item-description"
ref="textarea"
v-model="descriptionText"
:disabled="isSubmitting"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="false"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.meta.enter="updateWorkItem"
@keydown.ctrl.enter="updateWorkItem"
@keydown.exact.esc.stop="cancelEditing"
@input="onInput"
></textarea>
</template>
</markdown-field>
<div class="gl-display-flex">
<gl-button
category="primary"
variant="confirm"
:loading="isSubmitting"
data-testid="save-description"
@click="updateWorkItem"
>{{ __('Save') }}</gl-button
<markdown-field
can-attach-file
:textarea-value="descriptionText"
:is-submitting="isSubmitting"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="$options.markdownDocsPath"
class="gl-p-3 bordered-box gl-mt-5"
>
<gl-button category="tertiary" class="gl-ml-3" data-testid="cancel" @click="cancelEditing">{{
__('Cancel')
}}</gl-button>
</div>
</gl-form-group>
<div v-else class="gl-mb-5 gl-border-t">
<div class="gl-display-inline-flex gl-align-items-center gl-mb-5">
<label class="d-block col-form-label gl-mr-5">{{ __('Description') }}</label>
<gl-button
v-if="canEdit"
class="gl-ml-auto"
icon="pencil"
data-testid="edit-description"
:aria-label="__('Edit description')"
@click="startEditing"
/>
</div>
<template #textarea>
<textarea
id="work-item-description"
ref="textarea"
v-model="descriptionText"
:disabled="isSubmitting"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="false"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.meta.enter="updateWorkItem"
@keydown.ctrl.enter="updateWorkItem"
@keydown.exact.esc.stop="cancelEditing"
@input="onInput"
></textarea>
</template>
</markdown-field>
<div v-if="descriptionEmpty" class="gl-text-secondary gl-mb-5">{{ __('None') }}</div>
<div v-else v-safe-html="descriptionHtml" class="md gl-mb-5 gl-min-h-8"></div>
<div class="gl-display-flex">
<gl-button
category="primary"
variant="confirm"
:loading="isSubmitting"
data-testid="save-description"
@click="updateWorkItem"
>{{ __('Save') }}
</gl-button>
<gl-button category="tertiary" class="gl-ml-3" data-testid="cancel" @click="cancelEditing"
>{{ __('Cancel') }}
</gl-button>
</div>
</gl-form-group>
<work-item-description-rendered
v-else
:work-item-description="workItemDescription"
:can-edit="canEdit"
@startEditing="startEditing"
@descriptionUpdated="handleDescriptionTextUpdated"
/>
<edited-at
v-if="lastEditedAt"
:updated-at="lastEditedAt"

View File

@ -0,0 +1,117 @@
<script>
import { GlButton, GlSafeHtmlDirective } from '@gitlab/ui';
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
const isCheckbox = (target) => target?.classList.contains('task-list-item-checkbox');
export default {
directives: {
SafeHtml: GlSafeHtmlDirective,
},
components: {
GlButton,
},
props: {
workItemDescription: {
type: Object,
required: true,
},
canEdit: {
type: Boolean,
required: true,
},
},
computed: {
descriptionText() {
return this.workItemDescription?.description;
},
descriptionHtml() {
return this.workItemDescription?.descriptionHtml;
},
descriptionEmpty() {
return this.descriptionHtml?.trim() === '';
},
},
watch: {
descriptionHtml: {
handler() {
this.renderGFM();
},
immediate: true,
},
},
methods: {
async renderGFM() {
await this.$nextTick();
$(this.$refs['gfm-content']).renderGFM();
if (this.canEdit) {
this.checkboxes = this.$el.querySelectorAll('.task-list-item-checkbox');
// enable boxes, disabled by default in markdown
this.checkboxes.forEach((checkbox) => {
// eslint-disable-next-line no-param-reassign
checkbox.disabled = false;
});
}
},
toggleCheckboxes(event) {
const { target } = event;
if (isCheckbox(target)) {
target.disabled = true;
const { sourcepos } = target.parentElement.dataset;
if (!sourcepos) return;
const [startRange] = sourcepos.split('-');
let [startRow] = startRange.split(':');
startRow = Number(startRow) - 1;
const descriptionTextRows = this.descriptionText.split('\n');
const newDescriptionText = descriptionTextRows
.map((row, index) => {
if (startRow === index) {
if (target.checked) {
return row.replace(/\[ \]/, '[x]');
}
return row.replace(/\[[x~]\]/i, '[ ]');
}
return row;
})
.join('\n');
this.$emit('descriptionUpdated', newDescriptionText);
}
},
},
};
</script>
<template>
<div class="gl-mb-5 gl-border-t gl-pt-5">
<div class="gl-display-inline-flex gl-align-items-center gl-mb-5">
<label class="d-block col-form-label gl-mr-5">{{ __('Description') }}</label>
<gl-button
v-if="canEdit"
class="gl-ml-auto"
icon="pencil"
data-testid="edit-description"
:aria-label="__('Edit description')"
@click="$emit('startEditing')"
/>
</div>
<div v-if="descriptionEmpty" class="gl-text-secondary gl-mb-5">{{ __('None') }}</div>
<div
v-else
ref="gfm-content"
v-safe-html="descriptionHtml"
class="md gl-mb-5 gl-min-h-8"
@change="toggleCheckboxes"
></div>
</div>
</template>

View File

@ -335,7 +335,7 @@ class IssuableFinder
return items if items.is_a?(ActiveRecord::NullRelation)
return items if Feature.enabled?(:disable_anonymous_search, type: :ops) && current_user.nil?
return items.pg_full_text_search(search, matched_columns: params[:in].to_s.split(',')) if use_full_text_search?
return filter_by_full_text_search(items) if use_full_text_search?
if use_cte_for_search?
cte = Gitlab::SQL::CTE.new(klass.table_name, items)
@ -353,6 +353,10 @@ class IssuableFinder
Feature.enabled?(:issues_full_text_search, params.project || params.group)
end
def filter_by_full_text_search(items)
items.pg_full_text_search(search, matched_columns: params[:in].to_s.split(','))
end
# rubocop: disable CodeReuse/ActiveRecord
def by_iids(items)
params[:iids].present? ? items.where(iid: params[:iids]) : items

View File

@ -29,6 +29,8 @@
# issue_types: array of strings (one of WorkItems::Type.base_types)
#
class IssuesFinder < IssuableFinder
extend ::Gitlab::Utils::Override
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
def self.scalar_params
@ -96,6 +98,16 @@ class IssuesFinder < IssuableFinder
by_negated_issue_types(issues)
end
override :filter_by_full_text_search
def filter_by_full_text_search(items)
# This project condition is used as a hint to PG about the partitions that need searching
# because the search data is partitioned by project.
# In certain cases, like the recent items search, the query plan is much better without this condition.
return super if params[:skip_full_text_search_project_condition].present?
super.with_projects_matching_search_data
end
def by_confidential(items)
return items if params[:confidential].nil?

View File

@ -212,6 +212,7 @@ class Issue < ApplicationRecord
end
scope :with_null_relative_position, -> { where(relative_position: nil) }
scope :with_non_null_relative_position, -> { where.not(relative_position: nil) }
scope :with_projects_matching_search_data, -> { where('issue_search_data.project_id = issues.project_id') }
before_validation :ensure_namespace_id, :ensure_work_item_type
@ -271,11 +272,6 @@ class Issue < ApplicationRecord
def order_upvotes_asc
reorder(upvotes_count: :asc)
end
override :pg_full_text_search
def pg_full_text_search(query, matched_columns: [])
super.where('issue_search_data.project_id = issues.project_id')
end
end
def self.participant_includes

View File

@ -46,5 +46,5 @@ The following table outlines failure modes and mitigation paths for the product
| Single Gitaly Node + Geo Secondary | Downtime - Must restore from backup, can perform a manual failover to secondary | Downtime - Must restore from Backup, errors could have propagated to secondary | Manual intervention - failover to Geo secondary | |
| Sharded Gitaly Install | Partial Downtime - Only repositories on impacted node affected, must restore from backup | Partial Downtime - Only repositories on impacted node affected, must restore from backup | Downtime - Must wait for outage to end | |
| Sharded Gitaly Install + Geo Secondary | Partial Downtime - Only repositories on impacted node affected, must restore from backup, could perform manual failover to secondary for impacted repositories | Partial Downtime - Only repositories on impacted node affected, must restore from backup, errors could have propagated to secondary | Manual intervention - failover to Geo secondary | |
| Gitaly Cluster Install* | No Downtime - will swap repository primary to another node after 10 seconds | Not applicable; All writes are voted on by multiple Gitaly Cluster nodes | Downtime - Must wait for outage to end | Snapshot backups for Gitaly Cluster nodes not supported at this time |
| Gitaly Cluster Install* + Geo Secondary | No Downtime - will swap repository primary to another node after 10 seconds | Not applicable; All writes are voted on by multiple Gitaly Cluster nodes | Manual intervention - failover to Geo secondary | Snapshot backups for Gitaly Cluster nodes not supported at this time |
| Gitaly Cluster Install* | No Downtime - swaps repository primary to another node after 10 seconds | Not applicable; All writes are voted on by multiple Gitaly Cluster nodes | Downtime - Must wait for outage to end | Snapshot backups for Gitaly Cluster nodes not supported at this time |
| Gitaly Cluster Install* + Geo Secondary | No Downtime - swaps repository primary to another node after 10 seconds | Not applicable; All writes are voted on by multiple Gitaly Cluster nodes | Manual intervention - failover to Geo secondary | Snapshot backups for Gitaly Cluster nodes not supported at this time |

View File

@ -120,18 +120,18 @@ The following are required to run Geo:
- [CentOS](https://www.centos.org) 7.4 or later
- [Ubuntu](https://ubuntu.com) 16.04 or later
- PostgreSQL 12 or 13 with [Streaming Replication](https://wiki.postgresql.org/wiki/Streaming_Replication)
- Note,[PostgreSQL 12 is deprecated](../../update/deprecations.md#postgresql-12-deprecated) and will be removed in GitLab 16.0.
- Note,[PostgreSQL 12 is deprecated](../../update/deprecations.md#postgresql-12-deprecated) and is removed in GitLab 16.0.
- Git 2.9 or later
- Git-lfs 2.4.2 or later on the user side when using LFS
- All sites must run [the same GitLab and PostgreSQL versions](setup/database.md#postgresql-replication).
- If using different operating system versions between Geo sites, [check OS locale data compatibility](replication/troubleshooting.md#check-os-locale-data-compatibility) across Geo sites.
Additionally, check the GitLab [minimum requirements](../../install/requirements.md),
and we recommend you use the latest version of GitLab for a better experience.
and use the latest version of GitLab for a better experience.
### Firewall rules
The following table lists basic ports that must be open between the **primary** and **secondary** sites for Geo. To simplify failovers, we recommend opening ports in both directions.
The following table lists basic ports that must be open between the **primary** and **secondary** sites for Geo. To simplify failovers, you should open ports in both directions.
| Source site | Source port | Destination site | Destination port | Protocol |
|-------------|-------------|------------------|------------------|-------------|

View File

@ -24,8 +24,8 @@ To disable Geo, follow these steps:
## Remove all secondary Geo sites
To disable Geo, you need to first remove all your secondary Geo sites, which means replication will not happen
anymore on these sites. You can follow our docs to [remove your secondary Geo sites](remove_geo_site.md).
To disable Geo, you need to first remove all your secondary Geo sites, which means replication does not happen
anymore on these sites. You can follow our documentation to [remove your secondary Geo sites](remove_geo_site.md).
If the current site that you want to keep using is a secondary site, you need to first promote it to primary.
You can use our steps on [how to promote a secondary site](../disaster_recovery/index.md#step-3-promoting-a-secondary-site)

View File

@ -29,7 +29,7 @@ The following are GitLab upgrade validation tests we performed.
[Switch from repmgr to Patroni on a Geo primary site](https://gitlab.com/gitlab-org/gitlab/-/issues/224652):
- Description: Tested switching from repmgr to Patroni on a multi-node Geo primary site. Used [the orchestrator tool](https://gitlab.com/gitlab-org/gitlab-orchestrator) to deploy a Geo installation with 3 database nodes managed by repmgr. With this approach, we were also able to address a related issue for [verifying a Geo installation with Patroni and PostgreSQL 11](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5113).
- Outcome: Partial success. We enabled Patroni on the primary site and set up database replication on the secondary site. However, we found that Patroni would delete the secondary site's replication slot whenever Patroni was restarted. Another issue is that when Patroni elects a new leader in the cluster, the secondary site will fail to automatically follow the new leader. Until these issues are resolved, we cannot officially support and recommend Patroni for Geo installations.
- Outcome: Partial success. We enabled Patroni on the primary site and set up database replication on the secondary site. However, we found that Patroni would delete the secondary site's replication slot whenever Patroni was restarted. Another issue is that when Patroni elects a new leader in the cluster, the secondary site fails to automatically follow the new leader. Until these issues are resolved, we cannot officially support and recommend Patroni for Geo installations.
- Follow up issues/actions:
- [Investigate permanent replication slot for Patroni with Geo single node secondary](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5528)
@ -213,7 +213,7 @@ The following are additional validation tests we performed.
[Validate Object storage replication using GCP based object storage](https://gitlab.com/gitlab-org/gitlab/-/issues/351464):
- Description: Tested the average time it takes for a single image to replicate from the primary object storage location to the secondary when using GCP based object storage replication and [GitLab based object storage replication](object_storage.md#enabling-gitlab-managed-object-storage-replication). This was tested by uploading a 1mb image to a project on the primary site every second for 60 seconds. The time was then measured until a image was available on the secondary site. This was achieved using a [Ruby Script](https://gitlab.com/gitlab-org/quality/geo-replication-tester).
- Outcome: GCP handles replication differently than other Cloud Providers. In GCP, the process is to a create single bucket that is either multi, dual or single region based. This means that the bucket will automatically store replicas in a region based on the option chosen. Even when using multi region, this will still only replicate within a single continent, the options being America, Europe, or Asia. At current there doesn't seem to be any way to replicate objects between continents using GCP based replication. For Geo managed replication the average time when replicating within the same region was 6 seconds, and when replicating cross region this rose to just 9 seconds.
- Outcome: GCP handles replication differently than other Cloud Providers. In GCP, the process is to a create single bucket that is either multi, dual, or single region based. This means that the bucket automatically stores replicas in a region based on the option chosen. Even when using multi region, this only replicates in a single continent, the options being America, Europe, or Asia. At current there doesn't seem to be any way to replicate objects between continents using GCP based replication. For Geo managed replication the average time when replicating in the same region was 6 seconds, and when replicating cross region this rose to just 9 seconds.
## Other tests

View File

@ -31,7 +31,7 @@ In this example, we have already set up:
- `primary.example.com` as a Geo **primary** site.
- `secondary.example.com` as a Geo **secondary** site.
We will create a `git.example.com` subdomain that will automatically direct
We create a `git.example.com` subdomain that automatically directs
requests:
- From Europe to the **secondary** site.

View File

@ -49,8 +49,8 @@ Example response:
Example request:
```shell
curl --location --request GET "https://gdk.test:3443/api/v4/groups/33/scim/identities" \
--header "<PRIVATE-TOKEN>" \
curl --location --request GET "https://gitlab.example.com/api/v4/groups/33/scim/identities" \
--header "PRIVATE-TOKEN: <PRIVATE-TOKEN>" \
--form "extern_uid=<ID_TO_BE_UPDATED>" \
```
@ -77,7 +77,7 @@ Parameters:
Example request:
```shell
curl --location --request PATCH "https://gdk.test:3443/api/v4/groups/33/scim/sydney_jones" \
--header "<PRIVATE TOKEN>" \
curl --location --request PATCH "https://gitlab.example.com/api/v4/groups/33/scim/sydney_jones" \
--header "PRIVATE-TOKEN: <PRIVATE TOKEN>" \
--form "extern_uid=sydney_jones_new" \
```

View File

@ -70,44 +70,45 @@ is **not** `19.03.0`. See [troubleshooting information](#error-response-from-dae
## Supported languages and frameworks
GitLab SAST supports a variety of languages, package managers, and frameworks. Our SAST security scanners also feature automatic language detection which works even for mixed-language projects. If any supported language is detected in project source code we automatically run the appropriate SAST analyzers.
GitLab SAST supports scanning a variety of programming languages and frameworks.
Once you [enable SAST](#configuration), the right set of analyzers runs automatically even if your project uses more than one language.
You can also [view our language roadmap](https://about.gitlab.com/direction/secure/static-analysis/sast/#language-support) and [request other language support by opening an issue](https://gitlab.com/groups/gitlab-org/-/epics/297).
Check the [SAST direction page](https://about.gitlab.com/direction/secure/static-analysis/sast/#language-support) to learn more about our plans for language support in SAST.
| Language (package managers) / framework | Scan tool | Introduced in GitLab Version |
|------------------------------------------------|-----------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0 |
| .NET Framework<sup>1</sup> | [Security Code Scan](https://security-code-scan.github.io) | 13.0 |
| .NET (all versions, C# only) | [Semgrep](https://semgrep.dev) | 15.4 |
| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1 |
| C | [Semgrep](https://semgrep.dev) | 14.2 |
| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7 |
| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.1 |
| Go<sup>3</sup> | [Gosec](https://github.com/securego/gosec) | 10.7 |
| Go | [Semgrep](https://semgrep.dev) | 14.4 |
| Groovy<sup>2</sup> | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Maven, SBT) |
| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1 |
| Java (any build system) | [Semgrep](https://semgrep.dev) | 14.10 |
| Java<sup>2, 3</sup> | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (SBT) |
| Java (Android) | [MobSF (beta)](https://github.com/MobSF/Mobile-Security-Framework-MobSF) | 13.5 |
| JavaScript<sup>3</sup> | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8 |
| JavaScript | [Semgrep](https://semgrep.dev) | 13.10 |
| Kotlin (Android) | [MobSF (beta)](https://github.com/MobSF/Mobile-Security-Framework-MobSF) | 13.5 |
| Kotlin (General)<sup>2</sup> | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 13.11 |
| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6 |
| Node.js | [NodeJsScan](https://github.com/ajinabraham/NodeJsScan) | 11.1 |
| Objective-C (iOS) | [MobSF (beta)](https://github.com/MobSF/Mobile-Security-Framework-MobSF) | 13.5 |
| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8 |
| Python<sup>3</sup> | [bandit](https://github.com/PyCQA/bandit) | 10.3 |
| Python | [Semgrep](https://semgrep.dev) | 13.9 |
| React<sup>3</sup> | [ESLint react plugin](https://github.com/yannickcr/eslint-plugin-react) | 12.5 |
| React | [Semgrep](https://semgrep.dev) | 13.10 |
| Ruby | [brakeman](https://brakemanscanner.org) | 13.9 |
| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3 |
| Scala<sup>2</sup> | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Gradle, Maven) |
| Swift (iOS) | [MobSF (beta)](https://github.com/MobSF/Mobile-Security-Framework-MobSF) | 13.5 |
| TypeScript<sup>3</sup> | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.9, [merged](https://gitlab.com/gitlab-org/gitlab/-/issues/36059) with ESLint in 13.2 |
| TypeScript | [Semgrep](https://semgrep.dev) | 13.10 |
| Language / framework | [Analyzer](analyzers.md) used for scanning | Minimum supported GitLab version |
|------------------------------|--------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|
| .NET Core | [Security Code Scan](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan) | 11.0 |
| .NET Framework<sup>1</sup> | [Security Code Scan](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan) | 13.0 |
| .NET (all versions, C# only) | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with GitLab-managed rules | 15.4 |
| Apex (Salesforce) | [PMD](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) | 12.1 |
| C | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with GitLab-managed rules | 14.2 |
| C/C++ | [Flawfinder](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder) | 10.7 |
| Elixir (Phoenix) | [Sobelow](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) | 11.1 |
| Go<sup>3</sup> | [Gosec](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) | 10.7 |
| Go | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with GitLab-managed rules | 14.4 |
| Groovy<sup>2</sup> | [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) with the find-sec-bugs plugin | 11.3 (Gradle) & 11.9 (Maven, SBT) |
| Helm Charts | [Kubesec](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec) | 13.1 |
| Java (any build system) | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with GitLab-managed rules | 14.10 |
| Java<sup>2, 3</sup> | [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) with the find-sec-bugs plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (SBT) |
| Java (Android) | [MobSF (beta)](https://gitlab.com/gitlab-org/security-products/analyzers/mobsf) | 13.5 |
| JavaScript<sup>3</sup> | [ESLint security plugin](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) | 11.8 |
| JavaScript | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with GitLab-managed rules | 13.10 |
| Kotlin (Android) | [MobSF (beta)](https://gitlab.com/gitlab-org/security-products/analyzers/mobsf) | 13.5 |
| Kotlin (General)<sup>2</sup> | [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) with the find-sec-bugs plugin | 13.11 |
| Kubernetes manifests | [Kubesec](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec) | 12.6 |
| Node.js | [NodeJsScan](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan) | 11.1 |
| Objective-C (iOS) | [MobSF (beta)](https://gitlab.com/gitlab-org/security-products/analyzers/mobsf) | 13.5 |
| PHP | [phpcs-security-audit](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit) | 10.8 |
| Python<sup>3</sup> | [bandit](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) | 10.3 |
| Python | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with GitLab-managed rules | 13.9 |
| React<sup>3</sup> | [ESLint react plugin](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) | 12.5 |
| React | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with GitLab-managed rules | 13.10 |
| Ruby | [brakeman](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman) | 13.9 |
| Ruby on Rails | [brakeman](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman) | 10.3 |
| Scala<sup>2</sup> | [SpotBugs](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) with the find-sec-bugs plugin | 11.0 (SBT) & 11.9 (Gradle, Maven) |
| Swift (iOS) | [MobSF (beta)](https://gitlab.com/gitlab-org/security-products/analyzers/mobsf) | 13.5 |
| TypeScript<sup>3</sup> | [ESLint security plugin](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) | 11.9, [merged](https://gitlab.com/gitlab-org/gitlab/-/issues/36059) with ESLint in 13.2 |
| TypeScript | [Semgrep](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) with GitLab-managed rules | 13.10 |
1. .NET 4 support is limited. The analyzer runs in a Linux container and does not have access to Windows-specific libraries or features. Use the Semgrep-based scanner if you need .NET 4 support.
1. The SpotBugs-based analyzer supports [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/), and [SBT](https://www.scala-sbt.org/). It can also be used with variants like the
@ -180,6 +181,7 @@ as shown in the following table:
| Capability | In Free & Premium | In Ultimate |
|:---------------------------------------------------------------- -|:--------------------|:-------------------|
| Automatically scan code with [appropriate analyzers](#supported-languages-and-frameworks) | **{check-circle}** | **{check-circle}** |
| [Configure SAST scanners](#configuration) | **{check-circle}** | **{check-circle}** |
| [Customize SAST settings](#available-cicd-variables) | **{check-circle}** | **{check-circle}** |
| Download [JSON Report](#reports-json-format) | **{check-circle}** | **{check-circle}** |

View File

@ -33,7 +33,7 @@ module Gitlab
end
def search(term)
finder.new(user, search: term, in: 'title')
finder.new(user, search: term, in: 'title', skip_full_text_search_project_condition: true)
.execute
.limit(SEARCH_LIMIT).reorder(nil).id_in_ordered(latest_ids) # rubocop: disable CodeReuse/ActiveRecord
end

View File

@ -0,0 +1,108 @@
import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
import { nextTick } from 'vue';
import WorkItemDescriptionRendered from '~/work_items/components/work_item_description_rendered.vue';
import { descriptionTextWithCheckboxes, descriptionHtmlWithCheckboxes } from '../mock_data';
describe('WorkItemDescription', () => {
let wrapper;
const findEditButton = () => wrapper.find('[data-testid="edit-description"]');
const findCheckboxAtIndex = (index) => wrapper.findAll('input[type="checkbox"]').at(index);
const defaultWorkItemDescription = {
description: descriptionTextWithCheckboxes,
descriptionHtml: descriptionHtmlWithCheckboxes,
};
const createComponent = ({
workItemDescription = defaultWorkItemDescription,
canEdit = false,
} = {}) => {
wrapper = shallowMount(WorkItemDescriptionRendered, {
propsData: {
workItemDescription,
canEdit,
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders gfm', async () => {
const renderGFMSpy = jest.spyOn($.fn, 'renderGFM');
createComponent();
await nextTick();
expect(renderGFMSpy).toHaveBeenCalled();
});
describe('with checkboxes', () => {
beforeEach(() => {
createComponent({
canEdit: true,
workItemDescription: {
description: `- [x] todo 1\n- [ ] todo 2`,
descriptionHtml: `<ul dir="auto" class="task-list" data-sourcepos="1:1-4:0">
<li class="task-list-item" data-sourcepos="1:1-2:15">
<input checked="" class="task-list-item-checkbox" type="checkbox"> todo 1</li>
<li class="task-list-item" data-sourcepos="2:1-2:15">
<input class="task-list-item-checkbox" type="checkbox"> todo 2</li>
</ul>`,
},
});
});
it('checks unchecked checkbox', async () => {
findCheckboxAtIndex(1).setChecked();
await nextTick();
const updatedDescription = `- [x] todo 1\n- [x] todo 2`;
expect(wrapper.emitted('descriptionUpdated')).toEqual([[updatedDescription]]);
});
it('disables checkbox while updating', async () => {
findCheckboxAtIndex(1).setChecked();
await nextTick();
expect(findCheckboxAtIndex(1).attributes().disabled).toBeDefined();
});
it('unchecks checked checkbox', async () => {
findCheckboxAtIndex(0).setChecked(false);
await nextTick();
const updatedDescription = `- [ ] todo 1\n- [ ] todo 2`;
expect(wrapper.emitted('descriptionUpdated')).toEqual([[updatedDescription]]);
});
});
describe('Edit button', () => {
it('is not visible when canUpdate = false', async () => {
await createComponent({
canUpdate: false,
});
expect(findEditButton().exists()).toBe(false);
});
it('toggles edit mode', async () => {
createComponent({
canEdit: true,
});
findEditButton().vm.$emit('click');
await nextTick();
expect(wrapper.emitted('startEditing')).toEqual([[]]);
});
});
});

View File

@ -9,6 +9,7 @@ import { updateDraft } from '~/lib/utils/autosave';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
import WorkItemDescriptionRendered from '~/work_items/components/work_item_description_rendered.vue';
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
@ -34,8 +35,8 @@ describe('WorkItemDescription', () => {
const workItemByIidResponseHandler = jest.fn().mockResolvedValue(projectWorkItemResponse);
let workItemResponseHandler;
const findEditButton = () => wrapper.find('[data-testid="edit-description"]');
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
const findRenderedDescription = () => wrapper.findComponent(WorkItemDescriptionRendered);
const findEditedAt = () => wrapper.findComponent(EditedAt);
const editDescription = (newText) => wrapper.find('textarea').setValue(newText);
@ -75,7 +76,7 @@ describe('WorkItemDescription', () => {
await waitForPromises();
if (isEditing) {
findEditButton().vm.$emit('click');
findRenderedDescription().vm.$emit('startEditing');
await nextTick();
}
@ -85,28 +86,6 @@ describe('WorkItemDescription', () => {
wrapper.destroy();
});
describe('Edit button', () => {
it('is not visible when canUpdate = false', async () => {
await createComponent({
canUpdate: false,
});
expect(findEditButton().exists()).toBe(false);
});
it('toggles edit mode', async () => {
await createComponent({
canUpdate: true,
});
findEditButton().vm.$emit('click');
await nextTick();
expect(findMarkdownField().exists()).toBe(true);
});
});
describe('editing description', () => {
it('shows edited by text', async () => {
const lastEditedAt = '2022-09-21T06:18:42Z';

View File

@ -180,6 +180,19 @@ export const mockParent = {
},
};
export const descriptionTextWithCheckboxes = `- [ ] todo 1\n- [ ] todo 2`;
export const descriptionHtmlWithCheckboxes = `
<ul dir="auto" class="task-list" data-sourcepos"1:1-2:12">
<li class="task-list-item" data-sourcepos="1:1-1:11">
<input class="task-list-item-checkbox" type="checkbox"> todo 1
</li>
<li class="task-list-item" data-sourcepos="2:1-2:12">
<input class="task-list-item-checkbox" type="checkbox"> todo 2
</li>
</ul>
`;
export const workItemResponseFactory = ({
canUpdate = false,
canDelete = false,
@ -195,6 +208,7 @@ export const workItemResponseFactory = ({
allowsScopedLabels = false,
lastEditedAt = null,
lastEditedBy = null,
withCheckboxes = false,
parent = mockParent.parent,
} = {}) => ({
data: {
@ -227,9 +241,10 @@ export const workItemResponseFactory = ({
{
__typename: 'WorkItemWidgetDescription',
type: 'DESCRIPTION',
description: 'some **great** text',
descriptionHtml:
'<p data-sourcepos="1:1-1:19" dir="auto">some <strong>great</strong> text</p>',
description: withCheckboxes ? descriptionTextWithCheckboxes : 'some **great** text',
descriptionHtml: withCheckboxes
? descriptionHtmlWithCheckboxes
: '<p data-sourcepos="1:1-1:19" dir="auto">some <strong>great</strong> text</p>',
lastEditedAt,
lastEditedBy,
},

View File

@ -75,3 +75,8 @@ mapping:
- source: ee/app/mailers/ee/preview/.+\.rb
test: spec/mailers/previews_spec.rb
## GLFM spec and config files for CE and EE should map to respective markdown snapshot specs
- source: glfm_specification/.+
test: spec/requests/api/markdown_snapshot_spec.rb
- source: glfm_specification/.+
test: ee/spec/requests/api/markdown_snapshot_spec.rb