Add latest changes from gitlab-org/gitlab@master
|
@ -9,6 +9,8 @@ import {
|
|||
GlSprintf,
|
||||
GlAlert,
|
||||
} from '@gitlab/ui';
|
||||
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
|
||||
import { timeRanges } from '~/vue_shared/constants';
|
||||
import DashboardPanel from './dashboard_panel.vue';
|
||||
|
||||
const initialYml = `title: Go heap size
|
||||
|
@ -30,6 +32,7 @@ export default {
|
|||
GlSprintf,
|
||||
GlAlert,
|
||||
DashboardPanel,
|
||||
DateTimePicker,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -41,20 +44,35 @@ export default {
|
|||
'panelPreviewIsLoading',
|
||||
'panelPreviewError',
|
||||
'panelPreviewGraphData',
|
||||
'panelPreviewTimeRange',
|
||||
'panelPreviewIsShown',
|
||||
'projectPath',
|
||||
'addDashboardDocumentationPath',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions('monitoringDashboard', ['fetchPanelPreview']),
|
||||
...mapActions('monitoringDashboard', [
|
||||
'fetchPanelPreview',
|
||||
'fetchPanelPreviewMetrics',
|
||||
'setPanelPreviewTimeRange',
|
||||
]),
|
||||
onSubmit() {
|
||||
this.fetchPanelPreview(this.yml);
|
||||
},
|
||||
onDateTimePickerInput(timeRange) {
|
||||
this.setPanelPreviewTimeRange(timeRange);
|
||||
// refetch data only if preview has been clicked
|
||||
// and there are no errors
|
||||
if (this.panelPreviewIsShown && !this.panelPreviewError) {
|
||||
this.fetchPanelPreviewMetrics();
|
||||
}
|
||||
},
|
||||
},
|
||||
timeRanges,
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="prometheus-panel-builder">
|
||||
<div class="gl-xs-flex-direction-column gl-display-flex gl-mx-n3">
|
||||
<gl-card class="gl-flex-grow-1 gl-flex-basis-0 gl-mx-3">
|
||||
<template #header>
|
||||
|
@ -151,7 +169,13 @@ export default {
|
|||
<gl-alert v-if="panelPreviewError" variant="warning" :dismissible="false">
|
||||
{{ panelPreviewError }}
|
||||
</gl-alert>
|
||||
|
||||
<date-time-picker
|
||||
ref="dateTimePicker"
|
||||
class="gl-flex-grow-1 preview-date-time-picker"
|
||||
:value="panelPreviewTimeRange"
|
||||
:options="$options.timeRanges"
|
||||
@input="onDateTimePickerInput"
|
||||
/>
|
||||
<dashboard-panel :graph-data="panelPreviewGraphData" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -3,7 +3,7 @@ import statusCodes from '~/lib/utils/http_status';
|
|||
import { backOff } from '~/lib/utils/common_utils';
|
||||
import { PROMETHEUS_TIMEOUT } from '../constants';
|
||||
|
||||
const backOffRequest = makeRequestCallback =>
|
||||
const cancellableBackOffRequest = makeRequestCallback =>
|
||||
backOff((next, stop) => {
|
||||
makeRequestCallback()
|
||||
.then(resp => {
|
||||
|
@ -13,16 +13,19 @@ const backOffRequest = makeRequestCallback =>
|
|||
stop(resp);
|
||||
}
|
||||
})
|
||||
.catch(stop);
|
||||
// If the request is cancelled by axios
|
||||
// then consider it as noop so that its not
|
||||
// caught by subsequent catches
|
||||
.catch(thrown => (axios.isCancel(thrown) ? undefined : stop(thrown)));
|
||||
}, PROMETHEUS_TIMEOUT);
|
||||
|
||||
export const getDashboard = (dashboardEndpoint, params) =>
|
||||
backOffRequest(() => axios.get(dashboardEndpoint, { params })).then(
|
||||
cancellableBackOffRequest(() => axios.get(dashboardEndpoint, { params })).then(
|
||||
axiosResponse => axiosResponse.data,
|
||||
);
|
||||
|
||||
export const getPrometheusQueryData = (prometheusEndpoint, params) =>
|
||||
backOffRequest(() => axios.get(prometheusEndpoint, { params }))
|
||||
export const getPrometheusQueryData = (prometheusEndpoint, params, opts) =>
|
||||
cancellableBackOffRequest(() => axios.get(prometheusEndpoint, { params, ...opts }))
|
||||
.then(axiosResponse => axiosResponse.data)
|
||||
.then(prometheusResponse => prometheusResponse.data)
|
||||
.catch(error => {
|
||||
|
|
|
@ -16,10 +16,12 @@ import getDashboardValidationWarnings from '../queries/getDashboardValidationWar
|
|||
import { convertObjectPropsToCamelCase } from '../../lib/utils/common_utils';
|
||||
import { s__, sprintf } from '../../locale';
|
||||
import { getDashboard, getPrometheusQueryData } from '../requests';
|
||||
import { defaultTimeRange } from '~/vue_shared/constants';
|
||||
|
||||
import { ENVIRONMENT_AVAILABLE_STATE, OVERVIEW_DASHBOARD_PATH, VARIABLE_TYPES } from '../constants';
|
||||
|
||||
const axiosCancelToken = axios.CancelToken;
|
||||
let cancelTokenSource;
|
||||
|
||||
function prometheusMetricQueryParams(timeRange) {
|
||||
const { start, end } = convertToFixedRange(timeRange);
|
||||
|
||||
|
@ -491,12 +493,18 @@ export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQuery
|
|||
|
||||
// Panel Builder
|
||||
|
||||
export const setPanelPreviewTimeRange = ({ commit }, timeRange) => {
|
||||
commit(types.SET_PANEL_PREVIEW_TIME_RANGE, timeRange);
|
||||
};
|
||||
|
||||
export const fetchPanelPreview = ({ state, commit, dispatch }, panelPreviewYml) => {
|
||||
if (!panelPreviewYml) {
|
||||
return null;
|
||||
}
|
||||
|
||||
commit(types.SET_PANEL_PREVIEW_IS_SHOWN, true);
|
||||
commit(types.REQUEST_PANEL_PREVIEW, panelPreviewYml);
|
||||
|
||||
return axios
|
||||
.post(state.panelPreviewEndpoint, { panel_yaml: panelPreviewYml })
|
||||
.then(({ data }) => {
|
||||
|
@ -510,7 +518,12 @@ export const fetchPanelPreview = ({ state, commit, dispatch }, panelPreviewYml)
|
|||
};
|
||||
|
||||
export const fetchPanelPreviewMetrics = ({ state, commit }) => {
|
||||
const defaultQueryParams = prometheusMetricQueryParams(defaultTimeRange);
|
||||
if (cancelTokenSource) {
|
||||
cancelTokenSource.cancel();
|
||||
}
|
||||
cancelTokenSource = axiosCancelToken.source();
|
||||
|
||||
const defaultQueryParams = prometheusMetricQueryParams(state.panelPreviewTimeRange);
|
||||
|
||||
state.panelPreviewGraphData.metrics.forEach((metric, index) => {
|
||||
commit(types.REQUEST_PANEL_PREVIEW_METRIC_RESULT, { index });
|
||||
|
@ -519,7 +532,9 @@ export const fetchPanelPreviewMetrics = ({ state, commit }) => {
|
|||
if (metric.step) {
|
||||
params.step = metric.step;
|
||||
}
|
||||
return getPrometheusQueryData(metric.prometheusEndpointPath, params)
|
||||
return getPrometheusQueryData(metric.prometheusEndpointPath, params, {
|
||||
cancelToken: cancelTokenSource.token,
|
||||
})
|
||||
.then(data => {
|
||||
commit(types.RECEIVE_PANEL_PREVIEW_METRIC_RESULT_SUCCESS, { index, data });
|
||||
})
|
||||
|
|
|
@ -57,3 +57,6 @@ export const RECEIVE_PANEL_PREVIEW_METRIC_RESULT_SUCCESS =
|
|||
'RECEIVE_PANEL_PREVIEW_METRIC_RESULT_SUCCESS';
|
||||
export const RECEIVE_PANEL_PREVIEW_METRIC_RESULT_FAILURE =
|
||||
'RECEIVE_PANEL_PREVIEW_METRIC_RESULT_FAILURE';
|
||||
|
||||
export const SET_PANEL_PREVIEW_TIME_RANGE = 'SET_PANEL_PREVIEW_TIME_RANGE';
|
||||
export const SET_PANEL_PREVIEW_IS_SHOWN = 'SET_PANEL_PREVIEW_IS_SHOWN';
|
||||
|
|
|
@ -264,4 +264,10 @@ export default {
|
|||
metric.state = emptyStateFromError(error);
|
||||
metric.result = null;
|
||||
},
|
||||
[types.SET_PANEL_PREVIEW_TIME_RANGE](state, timeRange) {
|
||||
state.panelPreviewTimeRange = timeRange;
|
||||
},
|
||||
[types.SET_PANEL_PREVIEW_IS_SHOWN](state, isPreviewShown) {
|
||||
state.panelPreviewIsShown = isPreviewShown;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import invalidUrl from '~/lib/utils/invalid_url';
|
||||
import { timezones } from '../format_date';
|
||||
import { dashboardEmptyStates } from '../constants';
|
||||
import { defaultTimeRange } from '~/vue_shared/constants';
|
||||
|
||||
export default () => ({
|
||||
// API endpoints
|
||||
|
@ -66,6 +67,8 @@ export default () => ({
|
|||
panelPreviewIsLoading: false,
|
||||
panelPreviewGraphData: null,
|
||||
panelPreviewError: null,
|
||||
panelPreviewTimeRange: defaultTimeRange,
|
||||
panelPreviewIsShown: false,
|
||||
|
||||
// Other project data
|
||||
dashboardTimezone: timezones.LOCAL,
|
||||
|
|
|
@ -340,3 +340,11 @@
|
|||
opacity: 0;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.prometheus-panel-builder {
|
||||
.preview-date-time-picker {
|
||||
// same as in .dropdown-menu-toggle
|
||||
// see app/assets/stylesheets/framework/dropdowns.scss
|
||||
width: 160px;
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 20 KiB |
BIN
doc/administration/gitaly/img/cluster_example_v13_3.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
doc/administration/gitaly/img/shard_example_v13_3.png
Normal file
After Width: | Height: | Size: 14 KiB |
|
@ -59,6 +59,38 @@ Follow the [HA Gitaly epic](https://gitlab.com/groups/gitlab-org/-/epics/1489)
|
|||
for improvements including
|
||||
[horizontally distributing reads](https://gitlab.com/groups/gitlab-org/-/epics/2013).
|
||||
|
||||
## Cluster or shard
|
||||
|
||||
Gitaly supports multiple models of scaling:
|
||||
|
||||
- Clustering using Gitaly Cluster, where each repository is stored on multiple Gitaly nodes in the
|
||||
cluster. Read requests are distributed between repository replicas and write requests are
|
||||
broadcast to repository replicas.
|
||||
- Sharding using [repository storage paths](../repository_storage_paths.md), where each repository
|
||||
is stored on the assigned Gitaly node. All requests are routed to this node.
|
||||
|
||||
| Cluster | Shard |
|
||||
|---|---|
|
||||
| ![Cluster example](img/cluster_example_v13_3.png) | ![Shard example](img/shard_example_v13_3.png) |
|
||||
|
||||
Generally, Gitaly Cluster can replace sharded configurations, at the expense of additional storage
|
||||
needed to store each repository on multiple Gitaly nodes. The benefit of using Gitaly Cluster over
|
||||
sharding is:
|
||||
|
||||
- Improved fault tolerance, because each Gitaly node has a copy of every repository.
|
||||
- Improved resource utilization, reducing the need for over-provisioning for shard-specific peak
|
||||
loads, because read loads are distributed across replicas.
|
||||
- Manual rebalancing for performance is not required, because read loads are distributed across
|
||||
replicas.
|
||||
- Simpler management, because all Gitaly nodes are identical.
|
||||
|
||||
Under some workloads, CPU and memory requirements may require a large fleet of Gitaly nodes and it
|
||||
can be uneconomical to have one to one replication factor.
|
||||
|
||||
A hybrid approach can be used in these instances, where each shard is configured as a smaller
|
||||
cluster. [Variable replication factor](https://gitlab.com/groups/gitlab-org/-/epics/3372) is planned
|
||||
to provide greater flexibility for extremely large GitLab instances.
|
||||
|
||||
## Requirements for configuring a Gitaly Cluster
|
||||
|
||||
The minimum recommended configuration for a Gitaly Cluster requires:
|
||||
|
@ -142,6 +174,16 @@ database on the same PostgreSQL server if using
|
|||
[Geo](../geo/replication/index.md). The replication state is internal to each instance
|
||||
of GitLab and should not be replicated.
|
||||
|
||||
These instructions help set up a single PostgreSQL database, which creates a single point of
|
||||
failure. For greater fault tolerance, the following options are available:
|
||||
|
||||
- For non-Geo installations, use one of the fault-tolerant
|
||||
[PostgreSQL setups](../postgresql/index.md).
|
||||
- For Geo instances, either:
|
||||
- Set up a separate [PostgreSQL instance](https://www.postgresql.org/docs/11/high-availability.html).
|
||||
- Use a cloud-managed PostgreSQL service. AWS
|
||||
[Relational Database Service](https://aws.amazon.com/rds/)) is recommended.
|
||||
|
||||
To complete this section you will need:
|
||||
|
||||
- 1 Praefect node
|
||||
|
|
|
@ -449,8 +449,9 @@ you can pull from the Container Registry, but you cannot push.
|
|||
1. This example uses the `aws` CLI. If you haven't configured the
|
||||
CLI before, you have to configure your credentials by running `sudo aws configure`.
|
||||
Because a non-admin user likely can't access the Container Registry folder,
|
||||
ensure you use `sudo`. To check your credential configuration, run [`ls`]
|
||||
to list all buckets.
|
||||
ensure you use `sudo`. To check your credential configuration, run
|
||||
[`ls`](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3/ls.html) to list
|
||||
all buckets.
|
||||
|
||||
```shell
|
||||
sudo aws --endpoint-url https://your-object-storage-backend.com s3 ls
|
||||
|
|
|
@ -8,8 +8,8 @@ guidance on getting started from the [command line](getting_started.md#command-l
|
|||
|
||||
The [example users query](#set-up-the-graphiql-explorer) looks for a subset of users in
|
||||
a GitLab instance either by username or
|
||||
[global ID](../../development/api_graphql_styleguide.md#exposing-global-ids). The query
|
||||
includes:
|
||||
[Global ID](../../development/api_graphql_styleguide.md#global-ids).
|
||||
The query includes:
|
||||
|
||||
- [`pageInfo`](#pageinfo)
|
||||
- [`nodes`](#nodes)
|
||||
|
|
|
@ -59,8 +59,9 @@ The GitLab GraphQL API can be used to perform:
|
|||
- [Mutations](#mutations) for creating, updating, and deleting data.
|
||||
|
||||
NOTE: **Note:**
|
||||
In the GitLab GraphQL API, `id` generally refers to a global ID,
|
||||
which is an object identifier in the format of `gid://gitlab/Issue/123`.
|
||||
In the GitLab GraphQL API, `id` refers to a
|
||||
[Global ID](https://graphql.org/learn/global-object-identification/),
|
||||
which is an object identifier in the format of `"gid://gitlab/Issue/123"`.
|
||||
|
||||
[GitLab's GraphQL Schema](reference/index.md) outlines which objects and fields are
|
||||
available for clients to query and their corresponding data types.
|
||||
|
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 32 KiB |
|
@ -79,7 +79,7 @@ POST /admin/ci/variables
|
|||
| Attribute | Type | required | Description |
|
||||
|-----------------|---------|----------|-----------------------|
|
||||
| `key` | string | yes | The `key` of a variable. Max 255 characters, only `A-Z`, `a-z`, `0-9`, and `_` are allowed. |
|
||||
| `value` | string | yes | The `value` of a variable. 10,000 characters allowed. [Since GitLab 13.3] |
|
||||
| `value` | string | yes | The `value` of a variable. 10,000 characters allowed. [Since GitLab 13.3](https://gitlab.com/gitlab-org/gitlab/-/issues/220028) |
|
||||
| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file`. |
|
||||
| `protected` | boolean | no | Whether the variable is protected. |
|
||||
| `masked` | boolean | no | Whether the variable is masked. |
|
||||
|
|
|
@ -104,33 +104,33 @@ The following table lists available parameters for jobs:
|
|||
| Keyword | Description |
|
||||
|:---------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [`script`](#script) | Shell script which is executed by Runner. |
|
||||
| [`image`](#image) | Use Docker images. Also available: `image:name` and `image:entrypoint`. |
|
||||
| [`services`](#services) | Use Docker services images. Also available: `services:name`, `services:alias`, `services:entrypoint`, and `services:command`. |
|
||||
| [`before_script`](#before_script-and-after_script) | Override a set of commands that are executed before job. |
|
||||
| [`after_script`](#before_script-and-after_script) | Override a set of commands that are executed after job. |
|
||||
| [`stage`](#stage) | Defines a job stage (default: `test`). |
|
||||
| [`only`](#onlyexcept-basic) | Limit when jobs are created. Also available: [`only:refs`, `only:kubernetes`, `only:variables`, and `only:changes`](#onlyexcept-advanced). |
|
||||
| [`except`](#onlyexcept-basic) | Limit when jobs are not created. Also available: [`except:refs`, `except:kubernetes`, `except:variables`, and `except:changes`](#onlyexcept-advanced). |
|
||||
| [`rules`](#rules) | List of conditions to evaluate and determine selected attributes of a job, and whether or not it's created. May not be used alongside `only`/`except`. |
|
||||
| [`tags`](#tags) | List of tags which are used to select Runner. |
|
||||
| [`allow_failure`](#allow_failure) | Allow job to fail. Failed job does not contribute to commit status. |
|
||||
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
|
||||
| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in` and `environment:action`. |
|
||||
| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. |
|
||||
| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:exclude`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, `artifacts:reports:codequality`, `artifacts:reports:junit`, `artifacts:reports:cobertura`, and `artifacts:reports:terraform`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_scanning`, `artifacts:reports:license_management` (removed in GitLab 13.0), `artifacts:reports:performance`, `artifacts:reports:load_performance`, and `artifacts:reports:metrics`. |
|
||||
| [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. |
|
||||
| [`before_script`](#before_script-and-after_script) | Override a set of commands that are executed before job. |
|
||||
| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. |
|
||||
| [`coverage`](#coverage) | Code coverage settings for a given job. |
|
||||
| [`retry`](#retry) | When and how many times a job can be auto-retried in case of a failure. |
|
||||
| [`timeout`](#timeout) | Define a custom job-level timeout that takes precedence over the project-wide setting. |
|
||||
| [`parallel`](#parallel) | How many instances of a job should be run in parallel. |
|
||||
| [`trigger`](#trigger) | Defines a downstream pipeline trigger. |
|
||||
| [`include`](#include) | Allows this job to include external YAML files. Also available: `include:local`, `include:file`, `include:template`, and `include:remote`. |
|
||||
| [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. |
|
||||
| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in` and `environment:action`. |
|
||||
| [`except`](#onlyexcept-basic) | Limit when jobs are not created. Also available: [`except:refs`, `except:kubernetes`, `except:variables`, and `except:changes`](#onlyexcept-advanced). |
|
||||
| [`extends`](#extends) | Configuration entries that this job is going to inherit from. |
|
||||
| [`pages`](#pages) | Upload the result of a job to use with GitLab Pages. |
|
||||
| [`variables`](#variables) | Define job variables on a job level. |
|
||||
| [`image`](#image) | Use Docker images. Also available: `image:name` and `image:entrypoint`. |
|
||||
| [`include`](#include) | Allows this job to include external YAML files. Also available: `include:local`, `include:file`, `include:template`, and `include:remote`. |
|
||||
| [`interruptible`](#interruptible) | Defines if a job can be canceled when made redundant by a newer run. |
|
||||
| [`resource_group`](#resource_group) | Limit job concurrency. |
|
||||
| [`only`](#onlyexcept-basic) | Limit when jobs are created. Also available: [`only:refs`, `only:kubernetes`, `only:variables`, and `only:changes`](#onlyexcept-advanced). |
|
||||
| [`pages`](#pages) | Upload the result of a job to use with GitLab Pages. |
|
||||
| [`parallel`](#parallel) | How many instances of a job should be run in parallel. |
|
||||
| [`release`](#release) | Instructs the Runner to generate a [Release](../../user/project/releases/index.md) object. |
|
||||
| [`resource_group`](#resource_group) | Limit job concurrency. |
|
||||
| [`retry`](#retry) | When and how many times a job can be auto-retried in case of a failure. |
|
||||
| [`rules`](#rules) | List of conditions to evaluate and determine selected attributes of a job, and whether or not it's created. May not be used alongside `only`/`except`. |
|
||||
| [`services`](#services) | Use Docker services images. Also available: `services:name`, `services:alias`, `services:entrypoint`, and `services:command`. |
|
||||
| [`stage`](#stage) | Defines a job stage (default: `test`). |
|
||||
| [`tags`](#tags) | List of tags which are used to select Runner. |
|
||||
| [`timeout`](#timeout) | Define a custom job-level timeout that takes precedence over the project-wide setting. |
|
||||
| [`trigger`](#trigger) | Defines a downstream pipeline trigger. |
|
||||
| [`variables`](#variables) | Define job variables on a job level. |
|
||||
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
|
||||
|
||||
NOTE: **Note:**
|
||||
Parameters `types` and `type` are [deprecated](#deprecated-parameters).
|
||||
|
|
|
@ -36,6 +36,19 @@ can be shared.
|
|||
It is also possible to add a `private_token` to the querystring, or
|
||||
add a `HTTP_PRIVATE_TOKEN` header.
|
||||
|
||||
## Global IDs
|
||||
|
||||
GitLab's GraphQL API uses Global IDs (i.e: `"gid://gitlab/MyObject/123"`)
|
||||
and never database primary key IDs.
|
||||
|
||||
Global ID is [a standard](https://graphql.org/learn/global-object-identification/)
|
||||
used for caching and fetching in client-side libraries.
|
||||
|
||||
See also:
|
||||
|
||||
- [Exposing Global IDs](#exposing-global-ids).
|
||||
- [Mutation arguments](#object-identifier-arguments).
|
||||
|
||||
## Types
|
||||
|
||||
We use a code-first schema, and we declare what type everything is in Ruby.
|
||||
|
@ -106,18 +119,28 @@ Further reading:
|
|||
|
||||
### Exposing Global IDs
|
||||
|
||||
When exposing an `ID` field on a type, we will by default try to
|
||||
expose a global ID by calling `to_global_id` on the resource being
|
||||
rendered.
|
||||
In keeping with GitLab's use of [Global IDs](#global-ids), always convert
|
||||
database primary key IDs into Global IDs when you expose them.
|
||||
|
||||
To override this behavior, you can implement an `id` method on the
|
||||
type for which you are exposing an ID. Please make sure that when
|
||||
exposing a `GraphQL::ID_TYPE` using a custom method that it is
|
||||
globally unique.
|
||||
All fields named `id` are
|
||||
[converted automatically](https://gitlab.com/gitlab-org/gitlab/-/blob/b0f56e7/app/graphql/types/base_object.rb#L11-14)
|
||||
into the object's Global ID.
|
||||
|
||||
The records that are exposing a `full_path` as an `ID_TYPE` are one of
|
||||
these exceptions. Since the full path is a unique identifier for a
|
||||
`Project` or `Namespace`.
|
||||
Fields that are not named `id` need to be manually converted. We can do this using
|
||||
[`Gitlab::GlobalID.build`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/global_id.rb),
|
||||
or by calling `#to_global_id` on an object that has mixed in the
|
||||
`GlobalID::Identification` module.
|
||||
|
||||
Using an example from
|
||||
[`Types::Notes::DiscussionType`](https://gitlab.com/gitlab-org/gitlab/-/blob/3c95bd9/app/graphql/types/notes/discussion_type.rb#L24-26):
|
||||
|
||||
```ruby
|
||||
field :reply_id, GraphQL::ID_TYPE
|
||||
|
||||
def reply_id
|
||||
::Gitlab::GlobalId.build(object, id: object.reply_id)
|
||||
end
|
||||
```
|
||||
|
||||
### Connection Types
|
||||
|
||||
|
@ -654,15 +677,8 @@ the objects in question.
|
|||
To find objects to display in a field, we can add resolvers to
|
||||
`app/graphql/resolvers`.
|
||||
|
||||
Arguments can be defined within the resolver, those arguments will be
|
||||
made available to the fields using the resolver. When exposing a model
|
||||
that had an internal ID (`iid`), prefer using that in combination with
|
||||
the namespace path as arguments in a resolver over a database
|
||||
ID. Otherwise use a [globally unique ID](#exposing-global-ids).
|
||||
|
||||
We already have a `FullPathLoader` that can be included in other
|
||||
resolvers to quickly find Projects and Namespaces which will have a
|
||||
lot of dependent objects.
|
||||
Arguments can be defined within the resolver in the same way as in a mutation.
|
||||
See the [Mutation arguments](#object-identifier-arguments) section.
|
||||
|
||||
To limit the amount of queries performed, we can use `BatchLoader`.
|
||||
|
||||
|
@ -751,10 +767,6 @@ actions. In the same way a GET-request should not modify data, we
|
|||
cannot modify data in a regular GraphQL-query. We can however in a
|
||||
mutation.
|
||||
|
||||
To find objects for a mutation, arguments need to be specified. As with
|
||||
[resolvers](#resolvers), prefer using internal ID or, if needed, a
|
||||
global ID rather than the database ID.
|
||||
|
||||
### Building Mutations
|
||||
|
||||
Mutations live in `app/graphql/mutations` ideally grouped per
|
||||
|
@ -809,10 +821,34 @@ If you need advice for mutation naming, canvass the Slack `#graphql` channel for
|
|||
|
||||
### Arguments
|
||||
|
||||
Arguments required by the mutation can be defined as arguments
|
||||
required for a field. These will be wrapped up in an input type for
|
||||
the mutation. For example, the `Mutations::MergeRequests::SetWip`
|
||||
with GraphQL-name `MergeRequestSetWip` defines these arguments:
|
||||
Arguments for a mutation are defined using `argument`.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
argument :my_arg, GraphQL::STRING_TYPE,
|
||||
required: true,
|
||||
description: "A description of the argument"
|
||||
```
|
||||
|
||||
Each GraphQL `argument` defined will be passed to the `#resolve` method
|
||||
of a mutation as keyword arguments.
|
||||
|
||||
Example:
|
||||
|
||||
```ruby
|
||||
def resolve(my_arg:)
|
||||
# Perform mutation ...
|
||||
end
|
||||
```
|
||||
|
||||
`graphql-ruby` will automatically wrap up arguments into an
|
||||
[input type](https://graphql.org/learn/schema/#input-types).
|
||||
|
||||
For example, the
|
||||
[`mergeRequestSetWip` mutation](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/mutations/merge_requests/set_wip.rb)
|
||||
defines these arguments (some
|
||||
[through inheritance](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/mutations/merge_requests/base.rb)):
|
||||
|
||||
```ruby
|
||||
argument :project_path, GraphQL::ID_TYPE,
|
||||
|
@ -832,12 +868,19 @@ argument :wip,
|
|||
DESC
|
||||
```
|
||||
|
||||
This would automatically generate an input type called
|
||||
These arguments automatically generate an input type called
|
||||
`MergeRequestSetWipInput` with the 3 arguments we specified and the
|
||||
`clientMutationId`.
|
||||
|
||||
These arguments are then passed to the `resolve` method of a mutation
|
||||
as keyword arguments.
|
||||
### Object identifier arguments
|
||||
|
||||
In keeping with GitLab's use of [Global IDs](#global-ids), mutation
|
||||
arguments should use Global IDs to identify an object and never database
|
||||
primary key IDs.
|
||||
|
||||
Where an object has an `iid`, prefer to use the `full_path` or `group_path`
|
||||
of its parent in combination with its `iid` as arguments to identify an
|
||||
object rather than its `id`.
|
||||
|
||||
### Fields
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ at GitLab so far:
|
|||
- Their availability:
|
||||
- No "OOO"/"PTO"/"Parental Leave" in their GitLab or Slack status.
|
||||
- No `:red_circle:`/`:palm_tree:`/`:beach:`/`:beach_umbrella:`/`:beach_with_umbrella:` emojis in GitLab or Slack status.
|
||||
- [Experimental] Their timezone: people for which the local hour is between
|
||||
- (Experimental) Their timezone: people for which the local hour is between
|
||||
6 AM and 2 PM are eligible to be picked. This is to ensure they have a good
|
||||
chance to get to perform a review during their current work day. The experimentation is tracked in
|
||||
[this issue](https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/563)
|
||||
|
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 56 KiB |
|
@ -194,6 +194,103 @@ The output from the above `terraform` commands should be viewable in the job log
|
|||
|
||||
See [this reference project](https://gitlab.com/nicholasklick/gitlab-terraform-aws) using GitLab and Terraform to deploy a basic AWS EC2 within a custom VPC.
|
||||
|
||||
## Copy Terraform state between backends
|
||||
|
||||
Terraform supports copying the state when the backend is changed or
|
||||
reconfigured. This can be useful if you need to migrate from another backend to
|
||||
GitLab managed Terraform state. It's also useful if you need to change the state
|
||||
name as in the following example:
|
||||
|
||||
```shell
|
||||
PROJECT_ID="<gitlab-project-id>"
|
||||
TF_USERNAME="<gitlab-username>"
|
||||
TF_PASSWORD="<gitlab-personal-access-token>"
|
||||
TF_ADDRESS="https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/old-state-name"
|
||||
|
||||
terraform init \
|
||||
-backend-config=address=${TF_ADDRESS} \
|
||||
-backend-config=lock_address=${TF_ADDRESS}/lock \
|
||||
-backend-config=unlock_address=${TF_ADDRESS}/lock \
|
||||
-backend-config=username=${TF_USERNAME} \
|
||||
-backend-config=password=${TF_PASSWORD} \
|
||||
-backend-config=lock_method=POST \
|
||||
-backend-config=unlock_method=DELETE \
|
||||
-backend-config=retry_wait_min=5
|
||||
```
|
||||
|
||||
```plaintext
|
||||
Initializing the backend...
|
||||
|
||||
Successfully configured the backend "http"! Terraform will automatically
|
||||
use this backend unless the backend configuration changes.
|
||||
|
||||
Initializing provider plugins...
|
||||
|
||||
Terraform has been successfully initialized!
|
||||
|
||||
You may now begin working with Terraform. Try running "terraform plan" to see
|
||||
any changes that are required for your infrastructure. All Terraform commands
|
||||
should now work.
|
||||
|
||||
If you ever set or change modules or backend configuration for Terraform,
|
||||
rerun this command to reinitialize your working directory. If you forget, other
|
||||
commands will detect it and remind you to do so if necessary.
|
||||
```
|
||||
|
||||
Now that `terraform init` has created a `.terraform/` directory that knows where
|
||||
the old state is, you can tell it about the new location:
|
||||
|
||||
```shell
|
||||
TF_ADDRESS="https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/new-state-name"
|
||||
|
||||
terraform init \
|
||||
-backend-config=address=${TF_ADDRESS} \
|
||||
-backend-config=lock_address=${TF_ADDRESS}/lock \
|
||||
-backend-config=unlock_address=${TF_ADDRESS}/lock \
|
||||
-backend-config=username=${TF_USERNAME} \
|
||||
-backend-config=password=${TF_PASSWORD} \
|
||||
-backend-config=lock_method=POST \
|
||||
-backend-config=unlock_method=DELETE \
|
||||
-backend-config=retry_wait_min=5
|
||||
```
|
||||
|
||||
```plaintext
|
||||
Initializing the backend...
|
||||
Backend configuration changed!
|
||||
|
||||
Terraform has detected that the configuration specified for the backend
|
||||
has changed. Terraform will now check for existing state in the backends.
|
||||
|
||||
|
||||
Acquiring state lock. This may take a few moments...
|
||||
Do you want to copy existing state to the new backend?
|
||||
Pre-existing state was found while migrating the previous "http" backend to the
|
||||
newly configured "http" backend. No existing state was found in the newly
|
||||
configured "http" backend. Do you want to copy this state to the new "http"
|
||||
backend? Enter "yes" to copy and "no" to start with an empty state.
|
||||
|
||||
Enter a value: yes
|
||||
|
||||
|
||||
Successfully configured the backend "http"! Terraform will automatically
|
||||
use this backend unless the backend configuration changes.
|
||||
|
||||
Initializing provider plugins...
|
||||
|
||||
Terraform has been successfully initialized!
|
||||
|
||||
You may now begin working with Terraform. Try running "terraform plan" to see
|
||||
any changes that are required for your infrastructure. All Terraform commands
|
||||
should now work.
|
||||
|
||||
If you ever set or change modules or backend configuration for Terraform,
|
||||
rerun this command to reinitialize your working directory. If you forget, other
|
||||
commands will detect it and remind you to do so if necessary.
|
||||
```
|
||||
|
||||
If you type `yes`, it will copy your state from the old location to the new
|
||||
location. You can then go back to running it from within GitLab CI.
|
||||
|
||||
## Output Terraform Plan information into a merge request
|
||||
|
||||
Using the [GitLab Terraform Report artifact](../../ci/pipelines/job_artifacts.md#artifactsreportsterraform),
|
||||
|
|
|
@ -16,7 +16,6 @@ RSpec.describe 'Projects > Settings > Packages', :js do
|
|||
allow(Gitlab.config.packages).to receive(:enabled).and_return(true)
|
||||
end
|
||||
|
||||
context 'without the need for a license' do
|
||||
it 'displays the packages toggle button' do
|
||||
visit edit_project_path(project)
|
||||
|
||||
|
@ -24,7 +23,6 @@ RSpec.describe 'Projects > Settings > Packages', :js do
|
|||
expect(page).to have_selector('input[name="project[packages_enabled]"] + button', visible: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'Packages disabled in config' do
|
||||
before do
|
||||
|
|
|
@ -4,8 +4,10 @@ import { createStore } from '~/monitoring/stores';
|
|||
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
|
||||
import * as types from '~/monitoring/stores/mutation_types';
|
||||
import { metricsDashboardResponse } from '../fixture_data';
|
||||
import { mockTimeRange } from '../mock_data';
|
||||
|
||||
import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
|
||||
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
|
||||
|
||||
const mockPanel = metricsDashboardResponse.dashboard.panel_groups[0].panels[0];
|
||||
|
||||
|
@ -37,6 +39,7 @@ describe('dashboard invalid url parameters', () => {
|
|||
const findViewDocumentationBtn = () => wrapper.find({ ref: 'viewDocumentationBtn' });
|
||||
const findOpenRepositoryBtn = () => wrapper.find({ ref: 'openRepositoryBtn' });
|
||||
const findPanel = () => wrapper.find(DashboardPanel);
|
||||
const findTimeRangePicker = () => wrapper.find(DateTimePicker);
|
||||
|
||||
beforeEach(() => {
|
||||
mockShowToast = jest.fn();
|
||||
|
@ -110,6 +113,31 @@ describe('dashboard invalid url parameters', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('time range picker', () => {
|
||||
it('is visible by default', () => {
|
||||
expect(findTimeRangePicker().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('when changed does not trigger data fetch unless preview panel button is clicked', () => {
|
||||
// mimic initial state where SET_PANEL_PREVIEW_IS_SHOWN is set to false
|
||||
store.commit(`monitoringDashboard/${types.SET_PANEL_PREVIEW_IS_SHOWN}`, false);
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(store.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('when changed triggers data fetch if preview panel button is clicked', () => {
|
||||
findForm().vm.$emit('submit', new Event('submit'));
|
||||
|
||||
store.commit(`monitoringDashboard/${types.SET_PANEL_PREVIEW_TIME_RANGE}`, mockTimeRange);
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(store.dispatch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('instructions card', () => {
|
||||
const mockDocsPath = '/docs-path';
|
||||
const mockProjectPath = '/project-path';
|
||||
|
@ -146,6 +174,14 @@ describe('dashboard invalid url parameters', () => {
|
|||
it('displays an empty dashboard panel', () => {
|
||||
expect(findPanel().props('graphData')).toBe(null);
|
||||
});
|
||||
|
||||
it('changing time range should not refetch data', () => {
|
||||
store.commit(`monitoringDashboard/${types.SET_PANEL_PREVIEW_TIME_RANGE}`, mockTimeRange);
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
expect(store.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when panel data is available', () => {
|
||||
|
|
|
@ -1183,6 +1183,7 @@ describe('Monitoring store actions', () => {
|
|||
mockYmlContent,
|
||||
state,
|
||||
[
|
||||
{ type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
|
||||
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
|
||||
{ type: types.RECEIVE_PANEL_PREVIEW_SUCCESS, payload: mockPanel },
|
||||
],
|
||||
|
@ -1200,6 +1201,7 @@ describe('Monitoring store actions', () => {
|
|||
});
|
||||
|
||||
testAction(fetchPanelPreview, mockYmlContent, state, [
|
||||
{ type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
|
||||
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
|
||||
{ type: types.RECEIVE_PANEL_PREVIEW_FAILURE, payload: mockErrorMsg },
|
||||
]);
|
||||
|
@ -1209,6 +1211,7 @@ describe('Monitoring store actions', () => {
|
|||
mock.onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent }).reply(500);
|
||||
|
||||
testAction(fetchPanelPreview, mockYmlContent, state, [
|
||||
{ type: types.SET_PANEL_PREVIEW_IS_SHOWN, payload: true },
|
||||
{ type: types.REQUEST_PANEL_PREVIEW, payload: mockYmlContent },
|
||||
{
|
||||
type: types.RECEIVE_PANEL_PREVIEW_FAILURE,
|
||||
|
|
|
@ -2361,7 +2361,6 @@ RSpec.describe API::Projects do
|
|||
expect(project.packages_enabled).to be true
|
||||
end
|
||||
|
||||
context 'without the need for a license' do
|
||||
it 'disables project packages feature' do
|
||||
put(api("/projects/#{project.id}", user), params: { packages_enabled: false })
|
||||
|
||||
|
@ -2370,7 +2369,6 @@ RSpec.describe API::Projects do
|
|||
expect(json_response['packages_enabled']).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns 400 when nothing sent' do
|
||||
project_param = {}
|
||||
|
|