From a89912871cb169249d3ddc2ed59326d47ecdae82 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 17 Jun 2020 12:08:42 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../components/popover.vue | 2 +- .../javascripts/monitoring/stores/actions.js | 11 +- .../monitoring/stores/mutations.js | 12 +- .../javascripts/monitoring/stores/utils.js | 141 ++++++++++++++---- .../components/legacy_container.vue | 2 + app/graphql/mutations/snippets/update.rb | 18 ++- .../shared/_issuable_meta_data.html.haml | 2 +- .../218026-add-falco-documentation.yml | 5 + ...t-input-file-action-to-update-mutation.yml | 5 + ...ake-fixed-notification-default-enabled.yml | 5 + doc/administration/instance_limits.md | 4 + .../graphql/reference/gitlab_schema.graphql | 40 +++++ doc/api/graphql/reference/gitlab_schema.json | 112 ++++++++++++++ doc/user/clusters/applications.md | 88 +++++++++++ doc/user/profile/notifications.md | 2 +- doc/user/project/code_intelligence.md | 52 +++++++ .../project/img/code_intelligence_v13_1.png | Bin 0 -> 83690 bytes doc/user/project/index.md | 1 + doc/user/project/issues/design_management.md | 9 +- lib/api/entities/issuable_entity.rb | 7 +- lib/gitlab/ci/features.rb | 2 +- lib/gitlab/issuable_metadata.rb | 14 +- lib/gitlab/usage_data.rb | 23 ++- lib/gitlab/utils/usage_data.rb | 4 + locale/gitlab.pot | 2 +- .../components/charts/time_series_spec.js | 6 +- spec/frontend/monitoring/fixture_data.js | 15 +- .../frontend/monitoring/store/actions_spec.js | 8 +- .../frontend/monitoring/store/getters_spec.js | 5 +- .../monitoring/store/mutations_spec.js | 33 +++- spec/frontend/monitoring/store/utils_spec.js | 139 ++++++++++++++--- spec/frontend/monitoring/store_utils.js | 5 +- .../components/legacy_container_spec.js | 25 +++- spec/lib/gitlab/import_export/all_models.yml | 5 + spec/lib/gitlab/usage_data_spec.rb | 12 +- spec/lib/gitlab/utils/usage_data_spec.rb | 10 ++ .../graphql/mutations/snippets/update_spec.rb | 46 +++++- 37 files changed, 742 insertions(+), 130 deletions(-) create mode 100644 changelogs/unreleased/218026-add-falco-documentation.yml create mode 100644 changelogs/unreleased/fj-fj-add-snippet-input-file-action-to-update-mutation.yml create mode 100644 changelogs/unreleased/make-fixed-notification-default-enabled.yml create mode 100644 doc/user/project/code_intelligence.md create mode 100644 doc/user/project/img/code_intelligence_v13_1.png diff --git a/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue index 1e9e36feecc..764be57eda5 100644 --- a/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue +++ b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue @@ -18,7 +18,7 @@ const popoverStates = { suggest_commit_first_project_gitlab_ci_yml: { title: s__(`suggestPipeline|2/2: Commit your changes`), content: s__( - `suggestPipeline|Commit the changes and your pipeline will automatically run for the first time.`, + `suggestPipeline|The template is ready! You can now commit it to create your first pipeline.`, ), }, }; diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index 3a9cccec438..714def969ca 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -50,15 +50,14 @@ function backOffRequest(makeRequestCallback) { }, PROMETHEUS_TIMEOUT); } -function getPrometheusMetricResult(prometheusEndpoint, params) { +function getPrometheusQueryData(prometheusEndpoint, params) { return backOffRequest(() => axios.get(prometheusEndpoint, { params })) .then(res => res.data) .then(response => { if (response.status === 'error') { throw new Error(response.error); } - - return response.data.result; + return response.data; }); } @@ -229,9 +228,9 @@ export const fetchPrometheusMetric = ( commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId }); - return getPrometheusMetricResult(metric.prometheusEndpointPath, queryParams) - .then(result => { - commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, result }); + return getPrometheusQueryData(metric.prometheusEndpointPath, queryParams) + .then(data => { + commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, data }); }) .catch(error => { Sentry.captureException(error); diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js index 2d63fdd6e34..9c5f4a60284 100644 --- a/app/assets/javascripts/monitoring/stores/mutations.js +++ b/app/assets/javascripts/monitoring/stores/mutations.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import { pick } from 'lodash'; import * as types from './mutation_types'; -import { mapToDashboardViewModel, normalizeQueryResult } from './utils'; +import { mapToDashboardViewModel, normalizeQueryResponseData } from './utils'; import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils'; import { endpointKeys, initialStateKeys, metricStates } from '../constants'; import httpStatusCodes from '~/lib/utils/http_status'; @@ -135,19 +135,19 @@ export default { metric.state = metricStates.LOADING; } }, - [types.RECEIVE_METRIC_RESULT_SUCCESS](state, { metricId, result }) { + [types.RECEIVE_METRIC_RESULT_SUCCESS](state, { metricId, data }) { const metric = findMetricInDashboard(metricId, state.dashboard); metric.loading = false; - state.showEmptyState = false; - if (!result || result.length === 0) { + state.showEmptyState = false; + if (!data.result || data.result.length === 0) { metric.state = metricStates.NO_DATA; metric.result = null; } else { - const normalizedResults = result.map(normalizeQueryResult); + const result = normalizeQueryResponseData(data); metric.state = metricStates.OK; - metric.result = Object.freeze(normalizedResults); + metric.result = Object.freeze(result); } }, [types.RECEIVE_METRIC_RESULT_FAILURE](state, { metricId, error }) { diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js index 058fab5f4fc..a7c4ba71db7 100644 --- a/app/assets/javascripts/monitoring/stores/utils.js +++ b/app/assets/javascripts/monitoring/stores/utils.js @@ -295,9 +295,87 @@ export const mapToDashboardViewModel = ({ }; }; +// Prometheus Results Parsing + +const dateTimeFromUnixTime = unixTime => new Date(unixTime * 1000).toISOString(); + +const mapScalarValue = ([unixTime, value]) => [dateTimeFromUnixTime(unixTime), Number(value)]; + +// Note: `string` value type is unused as of prometheus 2.19. +const mapStringValue = ([unixTime, value]) => [dateTimeFromUnixTime(unixTime), value]; + /** - * Processes a single Range vector, part of the result - * of type `matrix` in the form: + * Processes a scalar result. + * + * The corresponding result property has the following format: + * + * [ , "" ] + * + * @param {array} result + * @returns {array} + */ +const normalizeScalarResult = result => [ + { + metric: {}, + value: mapScalarValue(result), + values: [mapScalarValue(result)], + }, +]; + +/** + * Processes a string result. + * + * The corresponding result property has the following format: + * + * [ , "" ] + * + * Note: This value type is unused as of prometheus 2.19. + * + * @param {array} result + * @returns {array} + */ +const normalizeStringResult = result => [ + { + metric: {}, + value: mapStringValue(result), + values: [mapStringValue(result)], + }, +]; + +/** + * Proccesses an instant vector. + * + * Instant vectors are returned as result type `vector`. + * + * The corresponding result property has the following format: + * + * [ + * { + * "metric": { "": "", ... }, + * "value": [ , "" ] + * }, + * ... + * ] + * + * This method also adds the matrix version of the vector + * by introducing a `values` array with a single element. This + * allows charts to default to `values` if needed. + * + * @param {array} result + * @returns {array} + */ +const normalizeVectorResult = result => + result.map(({ metric, value }) => { + const scalar = mapScalarValue(value); + // Add a single element to `values`, to support matrix + // style charts. + return { metric, value: scalar, values: [scalar] }; + }); + +/** + * Range vectors are returned as result type matrix. + * + * The corresponding result property has the following format: * * { * "metric": { "": "", ... }, @@ -306,32 +384,45 @@ export const mapToDashboardViewModel = ({ * * See https://prometheus.io/docs/prometheus/latest/querying/api/#range-vectors * - * @param {*} timeSeries + * @param {array} result + * @returns {array} */ -export const normalizeQueryResult = timeSeries => { - let normalizedResult = {}; +const normalizeResultMatrix = result => + result.map(({ metric, values }) => ({ metric, values: values.map(mapScalarValue) })); - if (timeSeries.values) { - normalizedResult = { - ...timeSeries, - values: timeSeries.values.map(([timestamp, value]) => [ - new Date(timestamp * 1000).toISOString(), - Number(value), - ]), - }; - // Check result for empty data - normalizedResult.values = normalizedResult.values.filter(series => { - const hasValue = d => !Number.isNaN(d[1]) && (d[1] !== null || d[1] !== undefined); - return series.find(hasValue); - }); - } else if (timeSeries.value) { - normalizedResult = { - ...timeSeries, - value: [new Date(timeSeries.value[0] * 1000).toISOString(), Number(timeSeries.value[1])], - }; +/** + * Parse response data from a Prometheus Query that comes + * in the format: + * + * { + * "resultType": "matrix" | "vector" | "scalar" | "string", + * "result": + * } + * + * @see https://prometheus.io/docs/prometheus/latest/querying/api/#expression-query-result-formats + * + * @param {object} data - Data containing results and result type. + * @returns {object} - A result array of metric results: + * [ + * { + * metric: { ... }, + * value: ['2015-07-01T20:10:51.781Z', '1'], + * values: [['2015-07-01T20:10:51.781Z', '1'] , ... ], + * }, + * ... + * ] + * + */ +export const normalizeQueryResponseData = data => { + const { resultType, result } = data; + if (resultType === 'vector') { + return normalizeVectorResult(result); + } else if (resultType === 'scalar') { + return normalizeScalarResult(result); + } else if (resultType === 'string') { + return normalizeStringResult(result); } - - return normalizedResult; + return normalizeResultMatrix(result); }; /** diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/legacy_container.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/legacy_container.vue index bf353eca489..d2fc2c66924 100644 --- a/app/assets/javascripts/projects/experiment_new_project_creation/components/legacy_container.vue +++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/legacy_container.vue @@ -14,11 +14,13 @@ export default { } else { this.source = legacyEntry.parentNode; this.$el.appendChild(legacyEntry); + legacyEntry.classList.add('active'); } }, beforeDestroy() { if (this.source) { + this.$el.firstChild.classList.remove('active'); this.source.appendChild(this.$el.firstChild); } }, diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index b6bdcb9b67b..f0a22ebaa9d 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -30,12 +30,16 @@ module Mutations description: 'The visibility level of the snippet', required: false + argument :files, [Types::Snippets::FileInputType], + description: 'The snippet files to update', + required: false + def resolve(args) snippet = authorized_find!(id: args.delete(:id)) result = ::Snippets::UpdateService.new(snippet.project, - context[:current_user], - args).execute(snippet) + context[:current_user], + update_params(args)).execute(snippet) snippet = result.payload[:snippet] { @@ -47,7 +51,15 @@ module Mutations private def ability_name - "update" + 'update' + end + + def update_params(args) + args.tap do |update_args| + # We need to rename `files` into `snippet_files` because + # it's the expected key param + update_args[:snippet_files] = update_args.delete(:files)&.map(&:to_h) + end end end end diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml index 7807371285c..191e6c132f8 100644 --- a/app/views/shared/_issuable_meta_data.html.haml +++ b/app/views/shared/_issuable_meta_data.html.haml @@ -2,7 +2,7 @@ - issue_votes = @issuable_meta_data[issuable.id] - upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes - issuable_path = issuable_path(issuable, anchor: 'notes') -- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count(current_user) +- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count - if issuable_mr > 0 %li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') } diff --git a/changelogs/unreleased/218026-add-falco-documentation.yml b/changelogs/unreleased/218026-add-falco-documentation.yml new file mode 100644 index 00000000000..15620582d31 --- /dev/null +++ b/changelogs/unreleased/218026-add-falco-documentation.yml @@ -0,0 +1,5 @@ +--- +title: Add Falco to the managed cluster apps template +merge_request: 32779 +author: +type: added diff --git a/changelogs/unreleased/fj-fj-add-snippet-input-file-action-to-update-mutation.yml b/changelogs/unreleased/fj-fj-add-snippet-input-file-action-to-update-mutation.yml new file mode 100644 index 00000000000..a43312cc27f --- /dev/null +++ b/changelogs/unreleased/fj-fj-add-snippet-input-file-action-to-update-mutation.yml @@ -0,0 +1,5 @@ +--- +title: Add files argument to snippet update mutation +merge_request: 34514 +author: +type: changed diff --git a/changelogs/unreleased/make-fixed-notification-default-enabled.yml b/changelogs/unreleased/make-fixed-notification-default-enabled.yml new file mode 100644 index 00000000000..7c66e234220 --- /dev/null +++ b/changelogs/unreleased/make-fixed-notification-default-enabled.yml @@ -0,0 +1,5 @@ +--- +title: Send fixed pipeline notification by default +merge_request: 34589 +author: +type: added diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index 3d2f7380494..db2cd7b477a 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -284,6 +284,10 @@ NOTE: **Note:** Set the limit to `0` to disable it. See the [documentation on Snippets settings](snippets/index.md). +## Design Management limits + +See the [Design Management Limitations](../user/project/issues/design_management.md#limitations) section. + ## Push Event Limits ### Webhooks and Project Services diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index adc0f8e0710..127fc959fee 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -11715,6 +11715,41 @@ type SnippetEdge { node: Snippet } +""" +Type of a snippet file input action +""" +enum SnippetFileInputActionEnum { + create + delete + move + update +} + +""" +Represents an action to perform over a snippet file +""" +input SnippetFileInputType { + """ + Type of input action + """ + action: SnippetFileInputActionEnum! + + """ + Snippet file content + """ + content: String + + """ + Path of the snippet file + """ + filePath: String! + + """ + Previous path of the snippet file + """ + previousPath: String +} + type SnippetPermissions { """ Indicates the user can perform `admin_snippet` on this resource @@ -12908,6 +12943,11 @@ input UpdateSnippetInput { """ fileName: String + """ + The snippet files to update + """ + files: [SnippetFileInputType!] + """ The global id of the snippet to update """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index e05a2ac858d..8992014276f 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -34577,6 +34577,100 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "ENUM", + "name": "SnippetFileInputActionEnum", + "description": "Type of a snippet file input action", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "create", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "update", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "delete", + "description": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "move", + "description": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "SnippetFileInputType", + "description": "Represents an action to perform over a snippet file", + "fields": null, + "inputFields": [ + { + "name": "action", + "description": "Type of input action", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "SnippetFileInputActionEnum", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "previousPath", + "description": "Previous path of the snippet file", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "filePath", + "description": "Path of the snippet file", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "content", + "description": "Snippet file content", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "SnippetPermissions", @@ -38099,6 +38193,24 @@ }, "defaultValue": null }, + { + "name": "files", + "description": "The snippet files to update", + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "SnippetFileInputType", + "ofType": null + } + } + }, + "defaultValue": null + }, { "name": "clientMutationId", "description": "A unique identifier for the client performing the mutation.", diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md index 86624d12bcf..39020923ad5 100644 --- a/doc/user/clusters/applications.md +++ b/doc/user/clusters/applications.md @@ -609,6 +609,7 @@ Supported applications: - [Sentry](#install-sentry-using-gitlab-cicd) - [GitLab Runner](#install-gitlab-runner-using-gitlab-cicd) - [Cilium](#install-cilium-using-gitlab-cicd) +- [Falco](#install-falco-using-gitlab-cicd) - [Vault](#install-vault-using-gitlab-cicd) - [JupyterHub](#install-jupyterhub-using-gitlab-cicd) - [Elastic Stack](#install-elastic-stack-using-gitlab-cicd) @@ -986,6 +987,93 @@ metrics: - 'flow:sourceContext=namespace;destinationContext=namespace' ``` +### Install Falco using GitLab CI/CD + +> [Introduced](https://gitlab.com/gitlab-org/cluster-integration/cluster-applications/-/merge_requests/91) in GitLab 13.1. + +GitLab Container Host Security Monitoring uses [Falco](https://falco.org/) +as a runtime security tool that listens to the Linux kernel using eBPF. Falco parses system calls +and asserts the stream against a configurable rules engine in real-time. For more information, see +[Falco's Documentation](https://falco.org/docs/). + +You can enable Falco in the +`.gitlab/managed-apps/config.yaml` file: + +```yaml +falco: + installed: true +``` + +You can customize Falco's Helm variables by defining the +`.gitlab/managed-apps/falco/values.yaml` file in your cluster +management project. Refer to the +[Falco chart](https://github.com/helm/charts/blob/master/stable/falco/) +for the available configuration options. + +CAUTION: **Caution:** +By default eBPF support is enabled and Falco will use an [eBPF probe](https://falco.org/docs/event-sources/drivers/#using-the-ebpf-probe) to pass system calls to userspace. +If your cluster doesn't support this, you can configure it to use Falco kernel module instead by adding the following to `.gitlab/managed-apps/falco/values.yaml`: + +```yaml +ebpf: + enabled: false +``` + +In rare cases where automatic probe installation on your cluster isn't possible and the kernel/probe +isn't precompiled, you may need to manually prepare the kernel module or eBPF probe with +[driverkit](https://github.com/falcosecurity/driverkit#against-a-kubernetes-cluster) +and install it on each cluster node. + +By default, Falco is deployed with a limited set of rules. To add more rules, add the following to +`.gitlab/managed-apps/falco/values.yaml` (you can get examples from +[Cloud Native Security Hub](https://securityhub.dev/)): + +```yaml +customRules: + file-integrity.yaml: |- + - rule: Detect New File + desc: detect new file created + condition: > + evt.type = chmod or evt.type = fchmod + output: > + File below a known directory opened for writing (user=%user.name + command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2]) + priority: ERROR + tags: [filesystem] + - rule: Detect New Directory + desc: detect new directory created + condition: > + mkdir + output: > + File below a known directory opened for writing (user=%user.name + command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2]) + priority: ERROR + tags: [filesystem] +``` + +By default, Falco only outputs security events to logs as JSON objects. To set it to output to an +[external API](https://falco.org/docs/alerts#https-output-send-alerts-to-an-https-end-point) +or [application](https://falco.org/docs/alerts#program-output), +add the following to `.gitlab/managed-apps/falco/values.yaml`: + +```yaml +falco: + programOutput: + enabled: true + keepAlive: false + program: mail -s "Falco Notification" someone@example.com + + httpOutput: + enabled: true + url: http://some.url +``` + +You can check these logs with the following command: + +```shell +kubectl logs -l app=falco -n gitlab-managed-apps +``` + ### Install Vault using GitLab CI/CD > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9982) in GitLab 12.9. diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md index ee228050945..ee7786fc150 100644 --- a/doc/user/profile/notifications.md +++ b/doc/user/profile/notifications.md @@ -187,7 +187,7 @@ To minimize the number of notifications that do not require any action, from [Gi | Remove milestone merge request | Subscribers, participants mentioned, and Custom notification level with this event selected | | New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher | | Failed pipeline | The author of the pipeline | -| Fixed pipeline | The author of the pipeline. Disabled by default. To activate it you must [enable the `ci_pipeline_fixed_notifications` feature flag](../../development/feature_flags/development.md#enabling-a-feature-flag-in-development). | +| Fixed pipeline | The author of the pipeline. Enabled by default. | | Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set. If the pipeline failed previously, a `Fixed pipeline` message will be sent for the first successful pipeline after the failure, then a `Successful pipeline` message for any further successful pipelines. | | New epic **(ULTIMATE)** | | | Close epic **(ULTIMATE)** | | diff --git a/doc/user/project/code_intelligence.md b/doc/user/project/code_intelligence.md new file mode 100644 index 00000000000..3717e46a7ae --- /dev/null +++ b/doc/user/project/code_intelligence.md @@ -0,0 +1,52 @@ +--- +type: reference +--- + +# Code Intelligence + +> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/1576) in GitLab 13.1. + +Code Intelligence adds code navigation features common to interactive +development environments (IDE), including: + +- Type signatures and symbol documentation. +- Go-to definition + +Code Intelligence is built into GitLab and powered by [LSIF](https://lsif.dev/) +(Language Server Index Format), a file format for precomputed code +intelligence data. + +## Configuration + +Enable code intelligence for a project by adding a GitLab CI/CD job to the project's +`.gitlab-ci.yml` which will generate the LSIF artifact: + +```yaml +code_navigation: + script: + - go get github.com/sourcegraph/lsif-go/cmd/lsif-go + - lsif-go +artifacts: + reports: + lsif: dump.lsif +``` + +The generated LSIF file must be less than 170MiB. + +After the job succeeds, code intelligence data can be viewed while browsing the code: + +![Code intelligence](img/code_intelligence_v13_1.png) + +## Language support + +Generating an LSIF file requires a language server indexer implementation for the +relevant language. + +| Language | Implementation | +|---|---| +| Go | [sourcegraph/lsif-go](https://github.com/sourcegraph/lsif-go) | +| JavaScript | [sourcegraph/lsif-node](https://github.com/sourcegraph/lsif-node) | +| TypeScript | [sourcegraph/lsif-node](https://github.com/sourcegraph/lsif-node) | + +View a complete list of [available LSIF indexers](https://lsif.dev/#implementations-server) on their website and +refer to their documentation to see how to generate an LSIF file for your specific language. diff --git a/doc/user/project/img/code_intelligence_v13_1.png b/doc/user/project/img/code_intelligence_v13_1.png new file mode 100644 index 0000000000000000000000000000000000000000..0dff27bab43efcd19800dcdc5c6bfaf0bb61151d GIT binary patch literal 83690 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 oRn?07;` General**, expand **Visibility, project features, permissions** and enable **Git Large File Storage**. -Design Management requires that projects are using -[hashed storage](../../../administration/repository_storage_types.md#hashed-storage) -(the default storage type since v10.0). +Design Management also requires that projects are using +[hashed storage](../../../administration/raketasks/storage.md#migrate-to-hashed-storage). Since + GitLab 10.0, newly created projects use hashed storage by default. A GitLab admin can verify the storage type of a +project by navigating to **Admin Area > Projects** and then selecting the project in question. +A project can be identified as hashed-stored if its *Gitaly relative path* contains `@hashed`. If the requirements are not met, the **Designs** tab displays a message to the user. @@ -47,6 +49,7 @@ and [PDFs](https://gitlab.com/gitlab-org/gitlab/-/issues/32811) is planned for a ## Limitations - Design uploads are limited to 10 files at a time. +- From GitLab 13.1, Design filenames are limited to 255 characters. - Design Management data [isn't deleted when a project is destroyed](https://gitlab.com/gitlab-org/gitlab/-/issues/13429) yet. - Design Management data [won't be moved](https://gitlab.com/gitlab-org/gitlab/-/issues/13426) diff --git a/lib/api/entities/issuable_entity.rb b/lib/api/entities/issuable_entity.rb index 5bee59de539..784bb8d57ed 100644 --- a/lib/api/entities/issuable_entity.rb +++ b/lib/api/entities/issuable_entity.rb @@ -11,7 +11,12 @@ module API # Avoids an N+1 query when metadata is included def issuable_metadata(subject, options, method, args = nil) cached_subject = options.dig(:issuable_metadata, subject.id) - (cached_subject || subject).public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend + + if cached_subject + cached_subject[method] + else + subject.public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend + end end end end diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index a2eb31369c7..5d6cf54e610 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -23,7 +23,7 @@ module Gitlab end def self.pipeline_fixed_notifications? - ::Feature.enabled?(:ci_pipeline_fixed_notifications) + ::Feature.enabled?(:ci_pipeline_fixed_notifications, default_enabled: true) end def self.instance_variables_ui_enabled? diff --git a/lib/gitlab/issuable_metadata.rb b/lib/gitlab/issuable_metadata.rb index e946fc00c4d..f96c937aec3 100644 --- a/lib/gitlab/issuable_metadata.rb +++ b/lib/gitlab/issuable_metadata.rb @@ -7,11 +7,13 @@ module Gitlab # data structure to store issuable meta data like # upvotes, downvotes, notes and closing merge requests counts for issues and merge requests # this avoiding n+1 queries when loading issuable collections on frontend - IssuableMeta = Struct.new(:upvotes, :downvotes, :user_notes_count, :mrs_count) do - def merge_requests_count(user = nil) - mrs_count - end - end + IssuableMeta = Struct.new( + :upvotes, + :downvotes, + :user_notes_count, + :merge_requests_count, + :blocking_issues_count # EE-ONLY + ) attr_reader :current_user, :issuable_collection @@ -95,3 +97,5 @@ module Gitlab end end end + +Gitlab::IssuableMetadata.prepend_if_ee('EE::Gitlab::IssuableMetadata') diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 7b6f5e69ee1..16689c14815 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -29,14 +29,15 @@ module Gitlab def uncached_data clear_memoized_limits - license_usage_data - .merge(system_usage_data) - .merge(features_usage_data) - .merge(components_usage_data) - .merge(cycle_analytics_usage_data) - .merge(object_store_usage_data) - .merge(topology_usage_data) - .merge(recording_ce_finish_data) + with_finished_at(:recording_ce_finished_at) do + license_usage_data + .merge(system_usage_data) + .merge(features_usage_data) + .merge(components_usage_data) + .merge(cycle_analytics_usage_data) + .merge(object_store_usage_data) + .merge(topology_usage_data) + end end def to_json(force_refresh: false) @@ -59,12 +60,6 @@ module Gitlab Time.now end - def recording_ce_finish_data - { - recording_ce_finished_at: Time.now - } - end - # rubocop: disable Metrics/AbcSize # rubocop: disable CodeReuse/ActiveRecord def system_usage_data diff --git a/lib/gitlab/utils/usage_data.rb b/lib/gitlab/utils/usage_data.rb index afc4e000977..f90d7c12660 100644 --- a/lib/gitlab/utils/usage_data.rb +++ b/lib/gitlab/utils/usage_data.rb @@ -92,6 +92,10 @@ module Gitlab [result, duration] end + def with_finished_at(key, &block) + yield.merge(key => Time.now) + end + private def redis_usage_counter diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2d2eb4973cc..ac02d6acdd7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -27648,7 +27648,7 @@ msgstr "" msgid "suggestPipeline|2/2: Commit your changes" msgstr "" -msgid "suggestPipeline|Commit the changes and your pipeline will automatically run for the first time." +msgid "suggestPipeline|The template is ready! You can now commit it to create your first pipeline." msgstr "" msgid "suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}" diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index 50d2c9c80b2..3cb27be30ac 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -15,7 +15,7 @@ import { createStore } from '~/monitoring/stores'; import { panelTypes, chartHeight } from '~/monitoring/constants'; import TimeSeries from '~/monitoring/components/charts/time_series.vue'; import * as types from '~/monitoring/stores/mutation_types'; -import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data'; +import { deploymentData, mockProjectDir, annotationsData, metricsResult } from '../../mock_data'; import { metricsDashboardPayload, metricsDashboardViewModel, @@ -702,9 +702,7 @@ describe('Time series component', () => { beforeEach(() => { store = createStore(); const graphData = cloneDeep(metricsDashboardViewModel.panelGroups[0].panels[3]); - graphData.metrics.forEach(metric => - Object.assign(metric, { result: metricResultStatus.result }), - ); + graphData.metrics.forEach(metric => Object.assign(metric, { result: metricsResult })); createWrapper({ graphData: { ...graphData, type: 'area-chart' } }, mount); return wrapper.vm.$nextTick(); diff --git a/spec/frontend/monitoring/fixture_data.js b/spec/frontend/monitoring/fixture_data.js index b7b72a15992..08a0deca808 100644 --- a/spec/frontend/monitoring/fixture_data.js +++ b/spec/frontend/monitoring/fixture_data.js @@ -14,16 +14,25 @@ export const metricsDashboardPanelCount = 22; export const metricResultStatus = { // First metric in fixture `metrics_dashboard/environment_metrics_dashboard.json` metricId: 'NO_DB_response_metrics_nginx_ingress_throughput_status_code', - result: metricsResult, + data: { + resultType: 'matrix', + result: metricsResult, + }, }; export const metricResultPods = { // Second metric in fixture `metrics_dashboard/environment_metrics_dashboard.json` metricId: 'NO_DB_response_metrics_nginx_ingress_latency_pod_average', - result: metricsResult, + data: { + resultType: 'matrix', + result: metricsResult, + }, }; export const metricResultEmpty = { metricId: 'NO_DB_response_metrics_nginx_ingress_16_throughput_status_code', - result: [], + data: { + resultType: 'matrix', + result: [], + }, }; // Graph data diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index d0290386f12..cb7d2368b04 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -738,7 +738,7 @@ describe('Monitoring store actions', () => { type: types.RECEIVE_METRIC_RESULT_SUCCESS, payload: { metricId: metric.metricId, - result: data.result, + data, }, }, ], @@ -775,7 +775,7 @@ describe('Monitoring store actions', () => { type: types.RECEIVE_METRIC_RESULT_SUCCESS, payload: { metricId: metric.metricId, - result: data.result, + data, }, }, ], @@ -817,7 +817,7 @@ describe('Monitoring store actions', () => { type: types.RECEIVE_METRIC_RESULT_SUCCESS, payload: { metricId: metric.metricId, - result: data.result, + data, }, }, ], @@ -852,7 +852,7 @@ describe('Monitoring store actions', () => { type: types.RECEIVE_METRIC_RESULT_SUCCESS, payload: { metricId: metric.metricId, - result: data.result, + data, }, }, ], diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js index 933ccb1e46c..eae495cda17 100644 --- a/spec/frontend/monitoring/store/getters_spec.js +++ b/spec/frontend/monitoring/store/getters_spec.js @@ -27,7 +27,10 @@ describe('Monitoring store Getters', () => { const { metricId } = state.dashboard.panelGroups[group].panels[panel].metrics[metric]; mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, { metricId, - result, + data: { + resultType: 'matrix', + result, + }, }); }; diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js index 0283f1a86a4..111ee69f4e6 100644 --- a/spec/frontend/monitoring/store/mutations_spec.js +++ b/spec/frontend/monitoring/store/mutations_spec.js @@ -225,11 +225,28 @@ describe('Monitoring mutations', () => { describe('Individual panel/metric results', () => { const metricId = 'NO_DB_response_metrics_nginx_ingress_throughput_status_code'; - const result = [ - { - values: [[0, 1], [1, 1], [1, 3]], - }, - ]; + const data = { + resultType: 'matrix', + result: [ + { + metric: { + __name__: 'up', + job: 'prometheus', + instance: 'localhost:9090', + }, + values: [[1435781430.781, '1'], [1435781445.781, '1'], [1435781460.781, '1']], + }, + { + metric: { + __name__: 'up', + job: 'node', + instance: 'localhost:9091', + }, + values: [[1435781430.781, '0'], [1435781445.781, '0'], [1435781460.781, '1']], + }, + ], + }; + const dashboard = metricsDashboardPayload; const getMetric = () => stateCopy.dashboard.panelGroups[1].panels[0].metrics[0]; @@ -262,7 +279,7 @@ describe('Monitoring mutations', () => { mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](stateCopy, { metricId, - result, + data, }); expect(stateCopy.showEmptyState).toBe(false); @@ -273,10 +290,10 @@ describe('Monitoring mutations', () => { mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](stateCopy, { metricId, - result, + data, }); - expect(getMetric().result).toHaveLength(result.length); + expect(getMetric().result).toHaveLength(data.result.length); expect(getMetric()).toEqual( expect.objectContaining({ loading: false, diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js index 3a70bda51da..a010e5c68fc 100644 --- a/spec/frontend/monitoring/store/utils_spec.js +++ b/spec/frontend/monitoring/store/utils_spec.js @@ -5,7 +5,7 @@ import { parseAnnotationsResponse, removeLeadingSlash, mapToDashboardViewModel, - normalizeQueryResult, + normalizeQueryResponseData, convertToGrafanaTimeRange, addDashboardMetaDataToLink, } from '~/monitoring/stores/utils'; @@ -400,28 +400,6 @@ describe('mapToDashboardViewModel', () => { }); }); -describe('normalizeQueryResult', () => { - const testData = { - metric: { - __name__: 'up', - job: 'prometheus', - instance: 'localhost:9090', - }, - values: [[1435781430.781, '1'], [1435781445.781, '1'], [1435781460.781, '1']], - }; - - it('processes a simple matrix result', () => { - expect(normalizeQueryResult(testData)).toEqual({ - metric: { __name__: 'up', job: 'prometheus', instance: 'localhost:9090' }, - values: [ - ['2015-07-01T20:10:30.781Z', 1], - ['2015-07-01T20:10:45.781Z', 1], - ['2015-07-01T20:11:00.781Z', 1], - ], - }); - }); -}); - describe('uniqMetricsId', () => { [ { input: { id: 1 }, expected: `${NOT_IN_DB_PREFIX}_1` }, @@ -607,3 +585,118 @@ describe('user-defined links utils', () => { }); }); }); + +describe('normalizeQueryResponseData', () => { + // Data examples from + // https://prometheus.io/docs/prometheus/latest/querying/api/#expression-queries + + it('processes a string result', () => { + const mockScalar = { + resultType: 'string', + result: [1435781451.781, '1'], + }; + + expect(normalizeQueryResponseData(mockScalar)).toEqual([ + { + metric: {}, + value: ['2015-07-01T20:10:51.781Z', '1'], + values: [['2015-07-01T20:10:51.781Z', '1']], + }, + ]); + }); + + it('processes a scalar result', () => { + const mockScalar = { + resultType: 'scalar', + result: [1435781451.781, '1'], + }; + + expect(normalizeQueryResponseData(mockScalar)).toEqual([ + { + metric: {}, + value: ['2015-07-01T20:10:51.781Z', 1], + values: [['2015-07-01T20:10:51.781Z', 1]], + }, + ]); + }); + + it('processes a vector result', () => { + const mockVector = { + resultType: 'vector', + result: [ + { + metric: { + __name__: 'up', + job: 'prometheus', + instance: 'localhost:9090', + }, + value: [1435781451.781, '1'], + }, + { + metric: { + __name__: 'up', + job: 'node', + instance: 'localhost:9100', + }, + value: [1435781451.781, '0'], + }, + ], + }; + + expect(normalizeQueryResponseData(mockVector)).toEqual([ + { + metric: { __name__: 'up', job: 'prometheus', instance: 'localhost:9090' }, + value: ['2015-07-01T20:10:51.781Z', 1], + values: [['2015-07-01T20:10:51.781Z', 1]], + }, + { + metric: { __name__: 'up', job: 'node', instance: 'localhost:9100' }, + value: ['2015-07-01T20:10:51.781Z', 0], + values: [['2015-07-01T20:10:51.781Z', 0]], + }, + ]); + }); + + it('processes a matrix result', () => { + const mockMatrix = { + resultType: 'matrix', + result: [ + { + metric: { + __name__: 'up', + job: 'prometheus', + instance: 'localhost:9090', + }, + values: [[1435781430.781, '1'], [1435781445.781, '1'], [1435781460.781, '1']], + }, + { + metric: { + __name__: 'up', + job: 'node', + instance: 'localhost:9091', + }, + values: [[1435781430.781, '0'], [1435781445.781, '0'], [1435781460.781, '1']], + }, + ], + }; + + expect(normalizeQueryResponseData(mockMatrix)).toEqual([ + { + metric: { __name__: 'up', instance: 'localhost:9090', job: 'prometheus' }, + values: [ + ['2015-07-01T20:10:30.781Z', 1], + ['2015-07-01T20:10:45.781Z', 1], + ['2015-07-01T20:11:00.781Z', 1], + ], + }, + { + metric: { __name__: 'up', instance: 'localhost:9091', job: 'node' }, + values: [ + ['2015-07-01T20:10:30.781Z', 0], + ['2015-07-01T20:10:45.781Z', 0], + ['2015-07-01T20:11:00.781Z', 1], + ], + }, + ]); + }); +}); diff --git a/spec/frontend/monitoring/store_utils.js b/spec/frontend/monitoring/store_utils.js index eb2578aa9db..e2c71e41b6c 100644 --- a/spec/frontend/monitoring/store_utils.js +++ b/spec/frontend/monitoring/store_utils.js @@ -8,7 +8,10 @@ export const setMetricResult = ({ store, result, group = 0, panel = 0, metric = store.commit(`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, { metricId, - result, + data: { + resultType: 'matrix', + result, + }, }); }; diff --git a/spec/frontend/projects/experiment_new_project_creation/components/legacy_container_spec.js b/spec/frontend/projects/experiment_new_project_creation/components/legacy_container_spec.js index 41bf2daab5f..cd8b39f0426 100644 --- a/spec/frontend/projects/experiment_new_project_creation/components/legacy_container_spec.js +++ b/spec/frontend/projects/experiment_new_project_creation/components/legacy_container_spec.js @@ -23,13 +23,28 @@ describe('Legacy container component', () => { createComponent({ selector: '.dummy-target' }); }); - it('moves node inside component when mounted', () => { - expect(dummy.parentNode).toBe(wrapper.element); + describe('when mounted', () => { + it('moves node inside component', () => { + expect(dummy.parentNode).toBe(wrapper.element); + }); + + it('sets active class', () => { + expect(dummy.classList.contains('active')).toBe(true); + }); }); - it('moves node back when unmounted', () => { - wrapper.destroy(); - expect(dummy.parentNode).toBe(document.body); + describe('when unmounted', () => { + beforeEach(() => { + wrapper.destroy(); + }); + + it('moves node back', () => { + expect(dummy.parentNode).toBe(document.body); + }); + + it('removes active class', () => { + expect(dummy.classList.contains('active')).toBe(false); + }); }); }); diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index ef9321dc1fc..c702dc1521c 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -31,6 +31,8 @@ issues: - closed_by - epic_issue - epic +- feature_flag_issues +- feature_flags - designs - design_versions - description_versions @@ -569,6 +571,9 @@ self_managed_prometheus_alert_events: epic_issues: - issue - epic +feature_flag_issues: +- issue +- feature_flag tracing_setting: - project reviews: diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 31176999333..bbbffc1de4c 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -169,6 +169,10 @@ describe Gitlab::UsageData, :aggregate_failures do expect { subject }.not_to raise_error end + it 'includes a recording_ce_finished_at timestamp' do + expect(subject[:recording_ce_finished_at]).to be_a(Time) + end + it 'jira usage works when queries time out' do allow_any_instance_of(ActiveRecord::Relation) .to receive(:find_in_batches).and_raise(ActiveRecord::StatementInvalid.new('')) @@ -216,14 +220,6 @@ describe Gitlab::UsageData, :aggregate_failures do end end - describe '.recording_ce_finished_at' do - subject { described_class.recording_ce_finish_data } - - it 'gathers time ce recording finishes at' do - expect(subject[:recording_ce_finished_at]).to be_a(Time) - end - end - context 'when not relying on database records' do describe '#features_usage_data_ce' do subject { described_class.features_usage_data_ce } diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb index 7de615384c5..aeb90156c1c 100644 --- a/spec/lib/gitlab/utils/usage_data_spec.rb +++ b/spec/lib/gitlab/utils/usage_data_spec.rb @@ -108,4 +108,14 @@ describe Gitlab::Utils::UsageData do expect(duration).to eq(2) end end + + describe '#with_finished_at' do + it 'adds a timestamp to the hash yielded by the block' do + freeze_time do + result = described_class.with_finished_at(:current_time) { { a: 1 } } + + expect(result).to eq(a: 1, current_time: Time.now) + end + end + end end diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb index 968ea5aed52..de2e309c1b6 100644 --- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb +++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb @@ -16,8 +16,8 @@ describe 'Updating a Snippet' do let(:current_user) { snippet.author } let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s } - let(:mutation) do - variables = { + let(:mutation_vars) do + { id: snippet_gid, content: updated_content, description: updated_description, @@ -25,8 +25,9 @@ describe 'Updating a Snippet' do file_name: updated_file_name, title: updated_title } - - graphql_mutation(:update_snippet, variables) + end + let(:mutation) do + graphql_mutation(:update_snippet, mutation_vars) end def mutation_response @@ -101,7 +102,6 @@ describe 'Updating a Snippet' do end it_behaves_like 'graphql update actions' - it_behaves_like 'when the snippet is not found' end @@ -148,4 +148,40 @@ describe 'Updating a Snippet' do it_behaves_like 'when the snippet is not found' end + + context 'when using the files params' do + let!(:snippet) { create(:personal_snippet, :private, :repository) } + let(:updated_content) { 'updated_content' } + let(:updated_file) { 'CHANGELOG' } + let(:deleted_file) { 'README' } + let(:mutation_vars) do + { + id: snippet_gid, + files: [ + { action: :update, filePath: updated_file, content: updated_content }, + { action: :delete, filePath: deleted_file } + ] + } + end + + it 'updates the Snippet' do + blob_to_update = blob_at(updated_file) + expect(blob_to_update.data).not_to eq updated_content + + blob_to_delete = blob_at(deleted_file) + expect(blob_to_delete).to be_present + + post_graphql_mutation(mutation, current_user: current_user) + + blob_to_update = blob_at(updated_file) + expect(blob_to_update.data).to eq updated_content + + blob_to_delete = blob_at(deleted_file) + expect(blob_to_delete).to be_nil + end + + def blob_at(filename) + snippet.repository.blob_at('HEAD', filename) + end + end end