Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
895563036a
commit
91ef4dcc05
50 changed files with 268 additions and 525 deletions
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlAlert } from '@gitlab/ui';
|
import { GlAlert } from '@gitlab/ui';
|
||||||
import { __ } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
import ServiceDeskSetting from './service_desk_setting.vue';
|
import ServiceDeskSetting from './service_desk_setting.vue';
|
||||||
import ServiceDeskService from '../services/service_desk_service';
|
import ServiceDeskService from '../services/service_desk_service';
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
|
@ -122,11 +122,13 @@ export default {
|
||||||
this.incomingEmail = data?.service_desk_address;
|
this.incomingEmail = data?.service_desk_address;
|
||||||
this.showAlert(__('Changes were successfully made.'), 'success');
|
this.showAlert(__('Changes were successfully made.'), 'success');
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(err => {
|
||||||
this.showAlert(
|
this.showAlert(
|
||||||
__('An error occurred while saving the template. Please check if the template exists.'),
|
sprintf(__('An error occured while making the changes: %{error}'), {
|
||||||
),
|
error: err?.response?.data?.message,
|
||||||
)
|
}),
|
||||||
|
);
|
||||||
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.isTemplateSaving = false;
|
this.isTemplateSaving = false;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlButton, GlFormSelect, GlToggle, GlLoadingIcon } from '@gitlab/ui';
|
import { GlButton, GlFormSelect, GlToggle, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import tooltip from '~/vue_shared/directives/tooltip';
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
|
@ -17,6 +17,7 @@ export default {
|
||||||
GlFormSelect,
|
GlFormSelect,
|
||||||
GlToggle,
|
GlToggle,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
|
GlSprintf,
|
||||||
},
|
},
|
||||||
mixins: [glFeatureFlagsMixin()],
|
mixins: [glFeatureFlagsMixin()],
|
||||||
props: {
|
props: {
|
||||||
|
@ -60,6 +61,7 @@ export default {
|
||||||
selectedTemplate: this.initialSelectedTemplate,
|
selectedTemplate: this.initialSelectedTemplate,
|
||||||
outgoingName: this.initialOutgoingName || __('GitLab Support Bot'),
|
outgoingName: this.initialOutgoingName || __('GitLab Support Bot'),
|
||||||
projectKey: this.initialProjectKey,
|
projectKey: this.initialProjectKey,
|
||||||
|
baseEmail: this.incomingEmail.replace(this.initialProjectKey, ''),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -123,12 +125,33 @@ export default {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<span v-if="projectKey" class="form-text text-muted">
|
||||||
|
<gl-sprintf :message="__('Emails sent to %{email} will still be supported')">
|
||||||
|
<template #email>
|
||||||
|
<code>{{ baseEmail }}</code>
|
||||||
|
</template>
|
||||||
|
</gl-sprintf>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<gl-loading-icon :inline="true" />
|
<gl-loading-icon :inline="true" />
|
||||||
<span class="sr-only">{{ __('Fetching incoming email') }}</span>
|
<span class="sr-only">{{ __('Fetching incoming email') }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="hasProjectKeySupport">
|
||||||
|
<label for="service-desk-project-suffix" class="mt-3">
|
||||||
|
{{ __('Project name suffix') }}
|
||||||
|
</label>
|
||||||
|
<input id="service-desk-project-suffix" v-model.trim="projectKey" class="form-control" />
|
||||||
|
<span class="form-text text-muted">
|
||||||
|
{{
|
||||||
|
__(
|
||||||
|
'Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address.',
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
<label for="service-desk-template-select" class="mt-3">
|
<label for="service-desk-template-select" class="mt-3">
|
||||||
{{ __('Template to append to all Service Desk issues') }}
|
{{ __('Template to append to all Service Desk issues') }}
|
||||||
</label>
|
</label>
|
||||||
|
@ -144,19 +167,7 @@ export default {
|
||||||
<span class="form-text text-muted">
|
<span class="form-text text-muted">
|
||||||
{{ __('Emails sent from Service Desk will have this name') }}
|
{{ __('Emails sent from Service Desk will have this name') }}
|
||||||
</span>
|
</span>
|
||||||
<template v-if="hasProjectKeySupport">
|
<div class="gl-display-flex gl-justify-content-end">
|
||||||
<label for="service-desk-project-suffix" class="mt-3">
|
|
||||||
{{ __('Project name suffix') }}
|
|
||||||
</label>
|
|
||||||
<input id="service-desk-project-suffix" v-model.trim="projectKey" class="form-control" />
|
|
||||||
<span class="form-text text-muted mb-3">
|
|
||||||
{{
|
|
||||||
__(
|
|
||||||
'Project name suffix is a user-defined string which will be appended to the project path, and will form the Service Desk email address.',
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
<gl-button
|
<gl-button
|
||||||
variant="success"
|
variant="success"
|
||||||
class="gl-mt-5"
|
class="gl-mt-5"
|
||||||
|
@ -168,4 +179,5 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -90,6 +90,7 @@ export default {
|
||||||
:size="12"
|
:size="12"
|
||||||
:title="stateTitle"
|
:title="stateTitle"
|
||||||
:aria-label="state"
|
:aria-label="state"
|
||||||
|
data-testid="referenceIcon"
|
||||||
/>
|
/>
|
||||||
{{ displayReference }}
|
{{ displayReference }}
|
||||||
</component>
|
</component>
|
||||||
|
@ -105,6 +106,7 @@ export default {
|
||||||
:title="removeButtonLabel"
|
:title="removeButtonLabel"
|
||||||
:aria-label="removeButtonLabel"
|
:aria-label="removeButtonLabel"
|
||||||
:disabled="removeDisabled"
|
:disabled="removeDisabled"
|
||||||
|
data-testid="removeBtn"
|
||||||
type="button"
|
type="button"
|
||||||
class="js-issue-token-remove-button"
|
class="js-issue-token-remove-button"
|
||||||
@click="onRemoveRequest"
|
@click="onRemoveRequest"
|
||||||
|
|
|
@ -111,7 +111,7 @@ const mixins = {
|
||||||
return this.isMergeRequest && this.pipelineStatus && Object.keys(this.pipelineStatus).length;
|
return this.isMergeRequest && this.pipelineStatus && Object.keys(this.pipelineStatus).length;
|
||||||
},
|
},
|
||||||
isOpen() {
|
isOpen() {
|
||||||
return this.state === 'opened';
|
return this.state === 'opened' || this.state === 'reopened';
|
||||||
},
|
},
|
||||||
isClosed() {
|
isClosed() {
|
||||||
return this.state === 'closed';
|
return this.state === 'closed';
|
||||||
|
|
|
@ -22,8 +22,8 @@ module Postgresql
|
||||||
def self.lag_too_great?(max = 100.megabytes)
|
def self.lag_too_great?(max = 100.megabytes)
|
||||||
return false unless in_use?
|
return false unless in_use?
|
||||||
|
|
||||||
lag_function = "#{Gitlab::Database.pg_wal_lsn_diff}" \
|
lag_function = "pg_wal_lsn_diff" \
|
||||||
"(#{Gitlab::Database.pg_current_wal_insert_lsn}(), restart_lsn)::bigint"
|
"(pg_current_wal_insert_lsn(), restart_lsn)::bigint"
|
||||||
|
|
||||||
# We force the use of a transaction here so the query always goes to the
|
# We force the use of a transaction here so the query always goes to the
|
||||||
# primary, even when using the EE DB load balancer.
|
# primary, even when using the EE DB load balancer.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
- add_page_startup_api_call discussions_path(@issue)
|
- add_page_startup_api_call discussions_path(@issue)
|
||||||
|
- add_page_startup_api_call notes_url
|
||||||
|
|
||||||
- @gfm_form = true
|
- @gfm_form = true
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/using_a_geo_server.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/using_a_geo_server.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/disaster_recovery/bring_primary_back.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/disaster_recovery/bring_primary_back.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/configuration.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/configuration.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/configuration.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/configuration.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/setup/database.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/setup/database.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/setup/database.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/setup/database.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/disaster_recovery/index.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/disaster_recovery/index.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/docker_registry.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/docker_registry.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/faq.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/faq.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/multiple_servers.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/multiple_servers.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/object_storage.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/object_storage.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/disaster_recovery/planned_failover.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/disaster_recovery/planned_failover.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/security_review.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/security_review.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/operations/fast_ssh_key_lookup.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/operations/fast_ssh_key_lookup.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/troubleshooting.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/troubleshooting.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/tuning.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/tuning.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/updating_the_geo_nodes.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/updating_the_geo_nodes.md).
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
redirect_to: '../administration/geo/replication/using_a_geo_server.md'
|
|
||||||
---
|
|
||||||
|
|
||||||
This document was moved to [another location](../administration/geo/replication/using_a_geo_server.md).
|
|
|
@ -48,7 +48,7 @@ To enable the CAS OmniAuth provider you must register your application with your
|
||||||
url: 'CAS_SERVER',
|
url: 'CAS_SERVER',
|
||||||
login_url: '/CAS_PATH/login',
|
login_url: '/CAS_PATH/login',
|
||||||
service_validate_url: '/CAS_PATH/p3/serviceValidate',
|
service_validate_url: '/CAS_PATH/p3/serviceValidate',
|
||||||
logout_url: '/CAS_PATH/logout'} }
|
logout_url: '/CAS_PATH/logout' } }
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Change 'CAS_PATH' to the root of your CAS instance (ie. `cas`).
|
1. Change 'CAS_PATH' to the root of your CAS instance (ie. `cas`).
|
||||||
|
|
|
@ -78,7 +78,8 @@ Follow these steps to incorporate the GitHub OAuth 2 app in your GitLab server:
|
||||||
For GitHub Enterprise:
|
For GitHub Enterprise:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- { name: 'github', app_id: 'YOUR_APP_ID',
|
- { name: 'github',
|
||||||
|
app_id: 'YOUR_APP_ID',
|
||||||
app_secret: 'YOUR_APP_SECRET',
|
app_secret: 'YOUR_APP_SECRET',
|
||||||
url: "https://github.example.com/",
|
url: "https://github.example.com/",
|
||||||
args: { scope: 'user:email' } }
|
args: { scope: 'user:email' } }
|
||||||
|
@ -125,7 +126,8 @@ omnibus_gitconfig['system'] = { "http" => ["sslVerify = false"] }
|
||||||
For installation from source:
|
For installation from source:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- { name: 'github', app_id: 'YOUR_APP_ID',
|
- { name: 'github',
|
||||||
|
app_id: 'YOUR_APP_ID',
|
||||||
app_secret: 'YOUR_APP_SECRET',
|
app_secret: 'YOUR_APP_SECRET',
|
||||||
url: "https://github.example.com/",
|
url: "https://github.example.com/",
|
||||||
verify_ssl: false,
|
verify_ssl: false,
|
||||||
|
|
|
@ -63,7 +63,8 @@ GitLab.com will generate an application ID and secret key for you to use.
|
||||||
For installations from source:
|
For installations from source:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- { name: 'gitlab', app_id: 'YOUR_APP_ID',
|
- { name: 'gitlab',
|
||||||
|
app_id: 'YOUR_APP_ID',
|
||||||
app_secret: 'YOUR_APP_SECRET',
|
app_secret: 'YOUR_APP_SECRET',
|
||||||
args: { scope: 'api' } }
|
args: { scope: 'api' } }
|
||||||
```
|
```
|
||||||
|
|
|
@ -84,7 +84,8 @@ On your GitLab server:
|
||||||
For installations from source:
|
For installations from source:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
|
- { name: 'google_oauth2',
|
||||||
|
app_id: 'YOUR_APP_ID',
|
||||||
app_secret: 'YOUR_APP_SECRET',
|
app_secret: 'YOUR_APP_SECRET',
|
||||||
args: { access_type: 'offline', approval_prompt: '' } }
|
args: { access_type: 'offline', approval_prompt: '' } }
|
||||||
```
|
```
|
||||||
|
|
|
@ -207,6 +207,7 @@ remove the OmniAuth provider named `kerberos` from your `gitlab.yml` /
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
omniauth:
|
omniauth:
|
||||||
|
# Rest of configuration omitted
|
||||||
# ...
|
# ...
|
||||||
providers:
|
providers:
|
||||||
- { name: 'kerberos' } # <-- remove this line
|
- { name: 'kerberos' } # <-- remove this line
|
||||||
|
|
|
@ -142,7 +142,7 @@ The chosen OmniAuth provider is now active and can be used to sign in to GitLab
|
||||||
|
|
||||||
## Automatically Link Existing Users to OmniAuth Users
|
## Automatically Link Existing Users to OmniAuth Users
|
||||||
|
|
||||||
> [Introduced in GitLab 13.4.](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36664)
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36664) in GitLab 13.4.
|
||||||
|
|
||||||
You can automatically link OmniAuth users with existing GitLab users if their email addresses match.
|
You can automatically link OmniAuth users with existing GitLab users if their email addresses match.
|
||||||
For example, the following setting is used to enable the auto link feature for both a SAML provider and the Twitter OAuth provider:
|
For example, the following setting is used to enable the auto link feature for both a SAML provider and the Twitter OAuth provider:
|
||||||
|
|
|
@ -65,7 +65,8 @@ To enable the Twitter OmniAuth provider you must register your application with
|
||||||
For installations from source:
|
For installations from source:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- { name: 'twitter', app_id: 'YOUR_APP_ID',
|
- { name: 'twitter',
|
||||||
|
app_id: 'YOUR_APP_ID',
|
||||||
app_secret: 'YOUR_APP_SECRET' }
|
app_secret: 'YOUR_APP_SECRET' }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,8 @@ receivers:
|
||||||
bearer_token: 9e1cbfcd546896a9ea8be557caf13a76
|
bearer_token: 9e1cbfcd546896a9ea8be557caf13a76
|
||||||
send_resolved: true
|
send_resolved: true
|
||||||
url: http://192.168.178.31:3001/root/manual_prometheus/prometheus/alerts/notify.json
|
url: http://192.168.178.31:3001/root/manual_prometheus/prometheus/alerts/notify.json
|
||||||
...
|
# Rest of configuration omitted
|
||||||
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
For GitLab to associate your alerts with an [environment](../../ci/environments/index.md),
|
For GitLab to associate your alerts with an [environment](../../ci/environments/index.md),
|
||||||
|
|
|
@ -125,7 +125,7 @@ the Agent in subsequent steps. You can create an Agent record either:
|
||||||
|
|
||||||
- Through GraphQL: **(PREMIUM ONLY)**
|
- Through GraphQL: **(PREMIUM ONLY)**
|
||||||
|
|
||||||
```json
|
```graphql
|
||||||
mutation createAgent {
|
mutation createAgent {
|
||||||
createClusterAgent(input: { projectPath: "path-to/your-awesome-project", name: "<agent-name>" }) {
|
createClusterAgent(input: { projectPath: "path-to/your-awesome-project", name: "<agent-name>" }) {
|
||||||
clusterAgent {
|
clusterAgent {
|
||||||
|
|
|
@ -269,7 +269,7 @@ To add a Kubernetes cluster to your project, group, or instance:
|
||||||
|
|
||||||
Copy the `<authentication_token>` value from the output:
|
Copy the `<authentication_token>` value from the output:
|
||||||
|
|
||||||
```yaml
|
```plaintext
|
||||||
Name: gitlab-token-b5zv4
|
Name: gitlab-token-b5zv4
|
||||||
Namespace: kube-system
|
Namespace: kube-system
|
||||||
Labels: <none>
|
Labels: <none>
|
||||||
|
|
|
@ -222,7 +222,8 @@ the environment of the deployed function:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
provider:
|
provider:
|
||||||
...
|
# Other configuration omitted
|
||||||
|
# ...
|
||||||
environment:
|
environment:
|
||||||
A_VARIABLE: ${env:A_VARIABLE}
|
A_VARIABLE: ${env:A_VARIABLE}
|
||||||
```
|
```
|
||||||
|
|
|
@ -92,10 +92,6 @@ module Gitlab
|
||||||
@version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
|
@version ||= database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.postgresql_9_or_less?
|
|
||||||
version.to_f < 10
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.postgresql_minimum_supported_version?
|
def self.postgresql_minimum_supported_version?
|
||||||
version.to_f >= MINIMUM_POSTGRES_VERSION
|
version.to_f >= MINIMUM_POSTGRES_VERSION
|
||||||
end
|
end
|
||||||
|
@ -127,28 +123,6 @@ module Gitlab
|
||||||
# ignore - happens when Rake tasks yet have to create a database, e.g. for testing
|
# ignore - happens when Rake tasks yet have to create a database, e.g. for testing
|
||||||
end
|
end
|
||||||
|
|
||||||
# map some of the function names that changed between PostgreSQL 9 and 10
|
|
||||||
# https://wiki.postgresql.org/wiki/New_in_postgres_10
|
|
||||||
def self.pg_wal_lsn_diff
|
|
||||||
Gitlab::Database.postgresql_9_or_less? ? 'pg_xlog_location_diff' : 'pg_wal_lsn_diff'
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.pg_current_wal_insert_lsn
|
|
||||||
Gitlab::Database.postgresql_9_or_less? ? 'pg_current_xlog_insert_location' : 'pg_current_wal_insert_lsn'
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.pg_last_wal_receive_lsn
|
|
||||||
Gitlab::Database.postgresql_9_or_less? ? 'pg_last_xlog_receive_location' : 'pg_last_wal_receive_lsn'
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.pg_last_wal_replay_lsn
|
|
||||||
Gitlab::Database.postgresql_9_or_less? ? 'pg_last_xlog_replay_location' : 'pg_last_wal_replay_lsn'
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.pg_last_xact_replay_timestamp
|
|
||||||
'pg_last_xact_replay_timestamp'
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.nulls_last_order(field, direction = 'ASC')
|
def self.nulls_last_order(field, direction = 'ASC')
|
||||||
Arel.sql("#{field} #{direction} NULLS LAST")
|
Arel.sql("#{field} #{direction} NULLS LAST")
|
||||||
end
|
end
|
||||||
|
|
|
@ -2686,6 +2686,9 @@ msgstr ""
|
||||||
msgid "An error has occurred"
|
msgid "An error has occurred"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "An error occured while making the changes: %{error}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "An error occurred adding a draft to the thread."
|
msgid "An error occurred adding a draft to the thread."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -2959,9 +2962,6 @@ msgstr ""
|
||||||
msgid "An error occurred while saving assignees"
|
msgid "An error occurred while saving assignees"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "An error occurred while saving the template. Please check if the template exists."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "An error occurred while searching for milestones"
|
msgid "An error occurred while searching for milestones"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -9527,6 +9527,9 @@ msgstr ""
|
||||||
msgid "Emails sent from Service Desk will have this name"
|
msgid "Emails sent from Service Desk will have this name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Emails sent to %{email} will still be supported"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Emails separated by comma"
|
msgid "Emails separated by comma"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,10 @@ describe('AddIssuableForm', () => {
|
||||||
const input = findFormInput(wrapper);
|
const input = findFormInput(wrapper);
|
||||||
if (input) input.blur();
|
if (input) input.blur();
|
||||||
|
|
||||||
|
if (wrapper) {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with data', () => {
|
describe('with data', () => {
|
||||||
|
|
|
@ -1,241 +1,146 @@
|
||||||
import Vue from 'vue';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { PathIdSeparator } from '~/related_issues/constants';
|
import { PathIdSeparator } from '~/related_issues/constants';
|
||||||
import issueToken from '~/related_issues/components/issue_token.vue';
|
import IssueToken from '~/related_issues/components/issue_token.vue';
|
||||||
|
|
||||||
describe('IssueToken', () => {
|
describe('IssueToken', () => {
|
||||||
const idKey = 200;
|
const idKey = 200;
|
||||||
const displayReference = 'foo/bar#123';
|
const displayReference = 'foo/bar#123';
|
||||||
const title = 'some title';
|
|
||||||
const pathIdSeparator = PathIdSeparator.Issue;
|
|
||||||
const eventNamespace = 'pendingIssuable';
|
const eventNamespace = 'pendingIssuable';
|
||||||
let IssueToken;
|
const path = '/foo/bar/issues/123';
|
||||||
let vm;
|
const pathIdSeparator = PathIdSeparator.Issue;
|
||||||
|
const title = 'some title';
|
||||||
|
|
||||||
beforeEach(() => {
|
let wrapper;
|
||||||
IssueToken = Vue.extend(issueToken);
|
|
||||||
|
const defaultProps = {
|
||||||
|
idKey,
|
||||||
|
displayReference,
|
||||||
|
pathIdSeparator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const createComponent = (props = {}) => {
|
||||||
|
wrapper = shallowMount(IssueToken, {
|
||||||
|
propsData: { ...defaultProps, ...props },
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
if (vm) {
|
if (wrapper) {
|
||||||
vm.$destroy();
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const findLink = () => wrapper.find({ ref: 'link' });
|
||||||
|
const findReference = () => wrapper.find({ ref: 'reference' });
|
||||||
|
const findReferenceIcon = () => wrapper.find('[data-testid="referenceIcon"]');
|
||||||
|
const findRemoveBtn = () => wrapper.find('[data-testid="removeBtn"]');
|
||||||
|
const findTitle = () => wrapper.find({ ref: 'title' });
|
||||||
|
|
||||||
describe('with reference supplied', () => {
|
describe('with reference supplied', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vm = new IssueToken({
|
createComponent();
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows reference', () => {
|
it('shows reference', () => {
|
||||||
expect(vm.$el.textContent.trim()).toEqual(displayReference);
|
expect(wrapper.text()).toContain(displayReference);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not link without path specified', () => {
|
it('does not link without path specified', () => {
|
||||||
expect(vm.$refs.link.tagName.toLowerCase()).toEqual('span');
|
expect(findLink().element.tagName).toBe('SPAN');
|
||||||
expect(vm.$refs.link.getAttribute('href')).toBeNull();
|
expect(findLink().attributes('href')).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with reference and title supplied', () => {
|
describe('with reference and title supplied', () => {
|
||||||
beforeEach(() => {
|
|
||||||
vm = new IssueToken({
|
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
title,
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows reference and title', () => {
|
it('shows reference and title', () => {
|
||||||
expect(vm.$refs.reference.textContent.trim()).toEqual(displayReference);
|
createComponent({
|
||||||
expect(vm.$refs.title.textContent.trim()).toEqual(title);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with path supplied', () => {
|
|
||||||
const path = '/foo/bar/issues/123';
|
|
||||||
beforeEach(() => {
|
|
||||||
vm = new IssueToken({
|
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
title,
|
title,
|
||||||
path,
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(findReference().text()).toBe(displayReference);
|
||||||
|
expect(findTitle().text()).toBe(title);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with path and title supplied', () => {
|
||||||
it('links reference and title', () => {
|
it('links reference and title', () => {
|
||||||
expect(vm.$refs.link.getAttribute('href')).toEqual(path);
|
createComponent({
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(findLink().attributes('href')).toBe(path);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with state supplied', () => {
|
describe('with state supplied', () => {
|
||||||
describe("`state: 'opened'`", () => {
|
it.each`
|
||||||
beforeEach(() => {
|
state | icon | cssClass
|
||||||
vm = new IssueToken({
|
${'opened'} | ${'issue-open-m'} | ${'issue-token-state-icon-open'}
|
||||||
propsData: {
|
${'reopened'} | ${'issue-open-m'} | ${'issue-token-state-icon-open'}
|
||||||
idKey,
|
${'closed'} | ${'issue-close'} | ${'issue-token-state-icon-closed'}
|
||||||
eventNamespace,
|
`('shows "$icon" icon when "$state"', ({ state, icon, cssClass }) => {
|
||||||
displayReference,
|
createComponent({
|
||||||
pathIdSeparator,
|
path,
|
||||||
state: 'opened',
|
state,
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows green circle icon', () => {
|
expect(findReferenceIcon().props('name')).toBe(icon);
|
||||||
expect(vm.$el.querySelector('.issue-token-state-icon-open.fa.fa-circle-o')).toBeDefined();
|
expect(findReferenceIcon().classes()).toContain(cssClass);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("`state: 'reopened'`", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vm = new IssueToken({
|
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
state: 'reopened',
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows green circle icon', () => {
|
|
||||||
expect(vm.$el.querySelector('.issue-token-state-icon-open.fa.fa-circle-o')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("`state: 'closed'`", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vm = new IssueToken({
|
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
state: 'closed',
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows red minus icon', () => {
|
|
||||||
expect(vm.$el.querySelector('.issue-token-state-icon-closed.fa.fa-minus')).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with reference, title, state', () => {
|
describe('with reference, title, state', () => {
|
||||||
const state = 'opened';
|
const state = 'opened';
|
||||||
beforeEach(() => {
|
|
||||||
vm = new IssueToken({
|
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
title,
|
|
||||||
state,
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows reference, title, and state', () => {
|
it('shows reference, title, and state', () => {
|
||||||
const stateIcon = vm.$refs.reference.querySelector('svg');
|
createComponent({
|
||||||
|
title,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
|
||||||
expect(stateIcon.getAttribute('aria-label')).toEqual(state);
|
expect(findReferenceIcon().attributes('aria-label')).toBe(state);
|
||||||
expect(vm.$refs.reference.textContent.trim()).toEqual(displayReference);
|
expect(findReference().text()).toBe(displayReference);
|
||||||
expect(vm.$refs.title.textContent.trim()).toEqual(title);
|
expect(findTitle().text()).toBe(title);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with canRemove', () => {
|
describe('with canRemove', () => {
|
||||||
describe('`canRemove: false` (default)', () => {
|
describe('`canRemove: false` (default)', () => {
|
||||||
beforeEach(() => {
|
|
||||||
vm = new IssueToken({
|
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not have remove button', () => {
|
it('does not have remove button', () => {
|
||||||
expect(vm.$el.querySelector('.issue-token-remove-button')).toBeNull();
|
createComponent();
|
||||||
|
|
||||||
|
expect(findRemoveBtn().exists()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('`canRemove: true`', () => {
|
describe('`canRemove: true`', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vm = new IssueToken({
|
createComponent({
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
eventNamespace,
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
canRemove: true,
|
canRemove: true,
|
||||||
},
|
});
|
||||||
}).$mount();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has remove button', () => {
|
it('has remove button', () => {
|
||||||
expect(vm.$el.querySelector('.issue-token-remove-button')).toBeDefined();
|
expect(findRemoveBtn().exists()).toBe(true);
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('methods', () => {
|
it('emits event when clicked', () => {
|
||||||
beforeEach(() => {
|
findRemoveBtn().trigger('click');
|
||||||
vm = new IssueToken({
|
|
||||||
propsData: {
|
const emitted = wrapper.emitted(`${eventNamespace}RemoveRequest`);
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
expect(emitted).toHaveLength(1);
|
||||||
displayReference,
|
expect(emitted[0]).toEqual([idKey]);
|
||||||
pathIdSeparator,
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when getting checked', () => {
|
it('tooltip should not be escaped', () => {
|
||||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
expect(findRemoveBtn().attributes('data-original-title')).toBe(
|
||||||
vm.onRemoveRequest();
|
`Remove ${displayReference}`,
|
||||||
|
);
|
||||||
expect(vm.$emit).toHaveBeenCalledWith('pendingIssuableRemoveRequest', vm.idKey);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('tooltip', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vm = new IssueToken({
|
|
||||||
propsData: {
|
|
||||||
idKey,
|
|
||||||
eventNamespace,
|
|
||||||
displayReference,
|
|
||||||
pathIdSeparator,
|
|
||||||
canRemove: true,
|
|
||||||
},
|
|
||||||
}).$mount();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not be escaped', () => {
|
|
||||||
const { originalTitle } = vm.$refs.removeButton.dataset;
|
|
||||||
|
|
||||||
expect(originalTitle).toEqual(`Remove ${displayReference}`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,10 @@ describe('RelatedIssuesBlock', () => {
|
||||||
const findIssueCountBadgeAddButton = () => wrapper.find(GlButton);
|
const findIssueCountBadgeAddButton = () => wrapper.find(GlButton);
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
if (wrapper) {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with defaults', () => {
|
describe('with defaults', () => {
|
||||||
|
|
|
@ -14,7 +14,10 @@ describe('RelatedIssuesList', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
if (wrapper) {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with defaults', () => {
|
describe('with defaults', () => {
|
||||||
|
|
|
@ -218,9 +218,7 @@ describe('ServiceDeskRoot', () => {
|
||||||
.$nextTick()
|
.$nextTick()
|
||||||
.then(waitForPromises)
|
.then(waitForPromises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(wrapper.html()).toContain(
|
expect(wrapper.html()).toContain('An error occured while making the changes:');
|
||||||
'An error occurred while saving the template. Please check if the template exists.',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -70,25 +70,6 @@ RSpec.describe Gitlab::Database do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.postgresql_9_or_less?' do
|
|
||||||
it 'returns true when using postgresql 8.4' do
|
|
||||||
allow(described_class).to receive(:version).and_return('8.4')
|
|
||||||
expect(described_class.postgresql_9_or_less?).to eq(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns true when using PostgreSQL 9.6' do
|
|
||||||
allow(described_class).to receive(:version).and_return('9.6')
|
|
||||||
|
|
||||||
expect(described_class.postgresql_9_or_less?).to eq(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns false when using PostgreSQL 10 or newer' do
|
|
||||||
allow(described_class).to receive(:version).and_return('10')
|
|
||||||
|
|
||||||
expect(described_class.postgresql_9_or_less?).to eq(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.postgresql_minimum_supported_version?' do
|
describe '.postgresql_minimum_supported_version?' do
|
||||||
it 'returns false when using PostgreSQL 10' do
|
it 'returns false when using PostgreSQL 10' do
|
||||||
allow(described_class).to receive(:version).and_return('10')
|
allow(described_class).to receive(:version).and_return('10')
|
||||||
|
@ -150,68 +131,6 @@ RSpec.describe Gitlab::Database do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.pg_wal_lsn_diff' do
|
|
||||||
it 'returns old name when using PostgreSQL 9.6' do
|
|
||||||
allow(described_class).to receive(:version).and_return('9.6')
|
|
||||||
|
|
||||||
expect(described_class.pg_wal_lsn_diff).to eq('pg_xlog_location_diff')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns new name when using PostgreSQL 10 or newer' do
|
|
||||||
allow(described_class).to receive(:version).and_return('10')
|
|
||||||
|
|
||||||
expect(described_class.pg_wal_lsn_diff).to eq('pg_wal_lsn_diff')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.pg_current_wal_insert_lsn' do
|
|
||||||
it 'returns old name when using PostgreSQL 9.6' do
|
|
||||||
allow(described_class).to receive(:version).and_return('9.6')
|
|
||||||
|
|
||||||
expect(described_class.pg_current_wal_insert_lsn).to eq('pg_current_xlog_insert_location')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns new name when using PostgreSQL 10 or newer' do
|
|
||||||
allow(described_class).to receive(:version).and_return('10')
|
|
||||||
|
|
||||||
expect(described_class.pg_current_wal_insert_lsn).to eq('pg_current_wal_insert_lsn')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.pg_last_wal_receive_lsn' do
|
|
||||||
it 'returns old name when using PostgreSQL 9.6' do
|
|
||||||
allow(described_class).to receive(:version).and_return('9.6')
|
|
||||||
|
|
||||||
expect(described_class.pg_last_wal_receive_lsn).to eq('pg_last_xlog_receive_location')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns new name when using PostgreSQL 10 or newer' do
|
|
||||||
allow(described_class).to receive(:version).and_return('10')
|
|
||||||
|
|
||||||
expect(described_class.pg_last_wal_receive_lsn).to eq('pg_last_wal_receive_lsn')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.pg_last_wal_replay_lsn' do
|
|
||||||
it 'returns old name when using PostgreSQL 9.6' do
|
|
||||||
allow(described_class).to receive(:version).and_return('9.6')
|
|
||||||
|
|
||||||
expect(described_class.pg_last_wal_replay_lsn).to eq('pg_last_xlog_replay_location')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns new name when using PostgreSQL 10 or newer' do
|
|
||||||
allow(described_class).to receive(:version).and_return('10')
|
|
||||||
|
|
||||||
expect(described_class.pg_last_wal_replay_lsn).to eq('pg_last_wal_replay_lsn')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.pg_last_xact_replay_timestamp' do
|
|
||||||
it 'returns pg_last_xact_replay_timestamp' do
|
|
||||||
expect(described_class.pg_last_xact_replay_timestamp).to eq('pg_last_xact_replay_timestamp')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.nulls_last_order' do
|
describe '.nulls_last_order' do
|
||||||
it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'}
|
it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'}
|
||||||
it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'}
|
it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'}
|
||||||
|
|
Loading…
Reference in a new issue