Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5b4eca2afd
commit
72797f4a60
56 changed files with 2105 additions and 94 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -2,22 +2,6 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
## 12.10.3 (2020-05-04)
|
|
||||||
|
|
||||||
### Fixed (6 changes)
|
|
||||||
|
|
||||||
- Fix errors creating project with active Prometheus service template. !30340
|
|
||||||
- Fix incorrect commits number in commits list. !30412
|
|
||||||
- Fix second 500 error with NULL restricted visibility levels. !30414
|
|
||||||
- Add LFS badge feature flag to RefsController#logs_tree. !30442
|
|
||||||
- Disable schema dumping after migrations in production. !30812
|
|
||||||
- Fixes branch name not getting escaped correctly on frontend.
|
|
||||||
|
|
||||||
### Changed (1 change)
|
|
||||||
|
|
||||||
- Handle possible RSA key exceptions when generating CI_JOB_JWT. !30702
|
|
||||||
|
|
||||||
|
|
||||||
## 12.10.2 (2020-04-30)
|
## 12.10.2 (2020-04-30)
|
||||||
|
|
||||||
### Security (8 changes)
|
### Security (8 changes)
|
||||||
|
|
3
app/assets/javascripts/actioncable_consumer.js
Normal file
3
app/assets/javascripts/actioncable_consumer.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { createConsumer } from '@rails/actioncable';
|
||||||
|
|
||||||
|
export default createConsumer();
|
|
@ -1,5 +1,7 @@
|
||||||
import white from './white';
|
import white from './white';
|
||||||
import dark from './dark';
|
import dark from './dark';
|
||||||
|
import monokai from './monokai';
|
||||||
|
import solarizedDark from './solarized_dark';
|
||||||
|
|
||||||
export const themes = [
|
export const themes = [
|
||||||
{
|
{
|
||||||
|
@ -10,6 +12,14 @@ export const themes = [
|
||||||
name: 'dark',
|
name: 'dark',
|
||||||
data: dark,
|
data: dark,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'solarized-dark',
|
||||||
|
data: solarizedDark,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'monokai',
|
||||||
|
data: monokai,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const DEFAULT_THEME = 'white';
|
export const DEFAULT_THEME = 'white';
|
||||||
|
|
169
app/assets/javascripts/ide/lib/themes/monokai.js
Normal file
169
app/assets/javascripts/ide/lib/themes/monokai.js
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
https://github.com/brijeshb42/monaco-themes/blob/master/themes/Tomorrow-Night.json
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Brijesh Bittu
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
base: 'vs-dark',
|
||||||
|
inherit: true,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
foreground: '75715e',
|
||||||
|
token: 'comment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'e6db74',
|
||||||
|
token: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'ae81ff',
|
||||||
|
token: 'constant.numeric',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'ae81ff',
|
||||||
|
token: 'constant.language',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'ae81ff',
|
||||||
|
token: 'constant.character',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'ae81ff',
|
||||||
|
token: 'constant.other',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'f92672',
|
||||||
|
token: 'keyword',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'f92672',
|
||||||
|
token: 'storage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: '66d9ef',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
token: 'storage.type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'a6e22e',
|
||||||
|
fontStyle: 'underline',
|
||||||
|
token: 'entity.name.class',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'a6e22e',
|
||||||
|
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||||
|
fontStyle: 'italic underline',
|
||||||
|
token: 'entity.other.inherited-class',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'a6e22e',
|
||||||
|
token: 'entity.name.function',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'fd971f',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
token: 'variable.parameter',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'f92672',
|
||||||
|
token: 'entity.name.tag',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'a6e22e',
|
||||||
|
token: 'entity.other.attribute-name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: '66d9ef',
|
||||||
|
token: 'support.function',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: '66d9ef',
|
||||||
|
token: 'support.constant',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: '66d9ef',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
token: 'support.type',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: '66d9ef',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
token: 'support.class',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'f8f8f0',
|
||||||
|
background: 'f92672',
|
||||||
|
token: 'invalid',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'f8f8f0',
|
||||||
|
background: 'ae81ff',
|
||||||
|
token: 'invalid.deprecated',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'cfcfc2',
|
||||||
|
token: 'meta.structure.dictionary.json string.quoted.double.json',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: '75715e',
|
||||||
|
token: 'meta.diff',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: '75715e',
|
||||||
|
token: 'meta.diff.header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'f92672',
|
||||||
|
token: 'markup.deleted',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'a6e22e',
|
||||||
|
token: 'markup.inserted',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'e6db74',
|
||||||
|
token: 'markup.changed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'ae81ffa0',
|
||||||
|
token: 'constant.numeric.line-number.find-in-files - match',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
foreground: 'e6db74',
|
||||||
|
token: 'entity.name.filename.find-in-files',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
colors: {
|
||||||
|
'editor.foreground': '#F8F8F2',
|
||||||
|
'editor.background': '#272822',
|
||||||
|
'editor.selectionBackground': '#49483E',
|
||||||
|
'editor.lineHighlightBackground': '#3E3D32',
|
||||||
|
'editorCursor.foreground': '#F8F8F0',
|
||||||
|
'editorWhitespace.foreground': '#3B3A32',
|
||||||
|
'editorIndentGuide.activeBackground': '#9D550FB0',
|
||||||
|
'editor.selectionHighlightBorder': '#222218',
|
||||||
|
},
|
||||||
|
};
|
1110
app/assets/javascripts/ide/lib/themes/solarized_dark.js
Normal file
1110
app/assets/javascripts/ide/lib/themes/solarized_dark.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,15 @@
|
||||||
|
#import "~/graphql_shared/fragments/author.fragment.graphql"
|
||||||
|
|
||||||
|
query getProjectIssue($iid: String!, $fullPath: ID!) {
|
||||||
|
project(fullPath: $fullPath) {
|
||||||
|
issue(iid: $iid) {
|
||||||
|
assignees {
|
||||||
|
nodes {
|
||||||
|
...Author
|
||||||
|
id
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
// `e.keyCode` is deprecated, these values should be migrated
|
||||||
|
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/216102
|
||||||
|
|
||||||
export const BACKSPACE_KEY_CODE = 8;
|
export const BACKSPACE_KEY_CODE = 8;
|
||||||
export const ENTER_KEY_CODE = 13;
|
export const ENTER_KEY_CODE = 13;
|
||||||
export const ESC_KEY_CODE = 27;
|
export const ESC_KEY_CODE = 27;
|
||||||
|
|
4
app/assets/javascripts/lib/utils/keys.js
Normal file
4
app/assets/javascripts/lib/utils/keys.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/* eslint-disable @gitlab/require-i18n-strings */
|
||||||
|
|
||||||
|
export const ESC_KEY = 'Escape';
|
||||||
|
export const ESC_KEY_IE11 = 'Esc'; // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
|
|
@ -19,6 +19,7 @@ import {
|
||||||
import DashboardPanel from './dashboard_panel.vue';
|
import DashboardPanel from './dashboard_panel.vue';
|
||||||
import { s__ } from '~/locale';
|
import { s__ } from '~/locale';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
|
import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys';
|
||||||
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
|
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
|
||||||
import { mergeUrlParams, redirectTo, updateHistory } from '~/lib/utils/url_utility';
|
import { mergeUrlParams, redirectTo, updateHistory } from '~/lib/utils/url_utility';
|
||||||
import invalidUrl from '~/lib/utils/invalid_url';
|
import invalidUrl from '~/lib/utils/invalid_url';
|
||||||
|
@ -248,6 +249,10 @@ export default {
|
||||||
logsPath: this.logsPath,
|
logsPath: this.logsPath,
|
||||||
currentEnvironmentName: this.currentEnvironmentName,
|
currentEnvironmentName: this.currentEnvironmentName,
|
||||||
});
|
});
|
||||||
|
window.addEventListener('keyup', this.onKeyup);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
window.removeEventListener('keyup', this.onKeyup);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!this.hasMetrics) {
|
if (!this.hasMetrics) {
|
||||||
|
@ -371,13 +376,19 @@ export default {
|
||||||
onGoBack() {
|
onGoBack() {
|
||||||
this.clearExpandedPanel();
|
this.clearExpandedPanel();
|
||||||
},
|
},
|
||||||
|
onKeyup(event) {
|
||||||
|
const { key } = event;
|
||||||
|
if (key === ESC_KEY || key === ESC_KEY_IE11) {
|
||||||
|
this.clearExpandedPanel();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
addMetric: {
|
addMetric: {
|
||||||
title: s__('Metrics|Add metric'),
|
title: s__('Metrics|Add metric'),
|
||||||
modalId: 'add-metric',
|
modalId: 'add-metric',
|
||||||
},
|
},
|
||||||
i18n: {
|
i18n: {
|
||||||
goBackLabel: s__('Metrics|Go back'),
|
goBackLabel: s__('Metrics|Go back (Esc)'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -29,9 +29,6 @@ export default {
|
||||||
resolveAllDiscussionsIssuePath() {
|
resolveAllDiscussionsIssuePath() {
|
||||||
return this.getNoteableData.create_issue_to_resolve_discussions_path;
|
return this.getNoteableData.create_issue_to_resolve_discussions_path;
|
||||||
},
|
},
|
||||||
resolvedDiscussionsCount() {
|
|
||||||
return this.resolvableDiscussionsCount - this.unresolvedDiscussionsCount;
|
|
||||||
},
|
|
||||||
toggeableDiscussions() {
|
toggeableDiscussions() {
|
||||||
return this.discussions.filter(discussion => !discussion.individual_note);
|
return this.discussions.filter(discussion => !discussion.individual_note);
|
||||||
},
|
},
|
||||||
|
@ -60,15 +57,15 @@ export default {
|
||||||
<div class="full-width-mobile d-flex d-sm-flex">
|
<div class="full-width-mobile d-flex d-sm-flex">
|
||||||
<div class="line-resolve-all">
|
<div class="line-resolve-all">
|
||||||
<span
|
<span
|
||||||
:class="{ 'is-active': allResolved }"
|
:class="{ 'line-resolve-btn is-active': allResolved, 'line-resolve-text': !allResolved }"
|
||||||
class="line-resolve-btn is-disabled"
|
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<icon :name="allResolved ? 'check-circle-filled' : 'check-circle'" />
|
<template v-if="allResolved">
|
||||||
</span>
|
<icon name="check-circle-filled" />
|
||||||
<span class="line-resolve-text">
|
{{ __('All threads resolved') }}
|
||||||
{{ resolvedDiscussionsCount }}/{{ resolvableDiscussionsCount }}
|
</template>
|
||||||
{{ n__('thread resolved', 'threads resolved', resolvableDiscussionsCount) }}
|
<template v-else>
|
||||||
|
{{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }}
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
<script>
|
||||||
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
|
import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
|
||||||
|
import actionCable from '~/actioncable_consumer';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AssigneesRealtime',
|
||||||
|
props: {
|
||||||
|
mediator: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
issuableIid: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
projectPath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
project: {
|
||||||
|
query,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
iid: this.issuableIid,
|
||||||
|
fullPath: this.projectPath,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
result(data) {
|
||||||
|
this.handleFetchResult(data);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initActionCablePolling();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
received(data) {
|
||||||
|
if (data.event === 'updated') {
|
||||||
|
this.$apollo.queries.project.refetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initActionCablePolling() {
|
||||||
|
actionCable.subscriptions.create(
|
||||||
|
{
|
||||||
|
channel: 'IssuesChannel',
|
||||||
|
project_path: this.projectPath,
|
||||||
|
iid: this.issuableIid,
|
||||||
|
},
|
||||||
|
{ received: this.received },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleFetchResult({ data }) {
|
||||||
|
const { nodes } = data.project.issue.assignees;
|
||||||
|
|
||||||
|
const assignees = nodes.map(n => ({
|
||||||
|
...n,
|
||||||
|
avatar_url: n.avatarUrl,
|
||||||
|
id: getIdFromGraphQLId(n.id),
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.mediator.store.setAssigneesFromRealtime(assignees);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return this.$slots.default;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -3,8 +3,10 @@ import Flash from '~/flash';
|
||||||
import eventHub from '~/sidebar/event_hub';
|
import eventHub from '~/sidebar/event_hub';
|
||||||
import Store from '~/sidebar/stores/sidebar_store';
|
import Store from '~/sidebar/stores/sidebar_store';
|
||||||
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
|
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
|
||||||
|
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
import AssigneeTitle from './assignee_title.vue';
|
import AssigneeTitle from './assignee_title.vue';
|
||||||
import Assignees from './assignees.vue';
|
import Assignees from './assignees.vue';
|
||||||
|
import AssigneesRealtime from './assignees_realtime.vue';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -12,7 +14,9 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
AssigneeTitle,
|
AssigneeTitle,
|
||||||
Assignees,
|
Assignees,
|
||||||
|
AssigneesRealtime,
|
||||||
},
|
},
|
||||||
|
mixins: [glFeatureFlagsMixin()],
|
||||||
props: {
|
props: {
|
||||||
mediator: {
|
mediator: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -32,6 +36,14 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: 'issue',
|
default: 'issue',
|
||||||
},
|
},
|
||||||
|
issuableIid: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
projectPath: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -39,6 +51,12 @@ export default {
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
shouldEnableRealtime() {
|
||||||
|
// Note: Realtime is only available on issues right now, future support for MR wil be built later.
|
||||||
|
return this.glFeatures.realTimeIssueSidebar && this.issuableType === 'issue';
|
||||||
|
},
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.removeAssignee = this.store.removeAssignee.bind(this.store);
|
this.removeAssignee = this.store.removeAssignee.bind(this.store);
|
||||||
this.addAssignee = this.store.addAssignee.bind(this.store);
|
this.addAssignee = this.store.addAssignee.bind(this.store);
|
||||||
|
@ -84,6 +102,12 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<assignees-realtime
|
||||||
|
v-if="shouldEnableRealtime"
|
||||||
|
:issuable-iid="issuableIid"
|
||||||
|
:project-path="projectPath"
|
||||||
|
:mediator="mediator"
|
||||||
|
/>
|
||||||
<assignee-title
|
<assignee-title
|
||||||
:number-of-assignees="store.assignees.length"
|
:number-of-assignees="store.assignees.length"
|
||||||
:loading="loading || store.isFetching.assignees"
|
:loading="loading || store.isFetching.assignees"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import VueApollo from 'vue-apollo';
|
||||||
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue';
|
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue';
|
||||||
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
|
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
|
||||||
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
|
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
|
||||||
|
@ -8,17 +9,28 @@ import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
|
||||||
import sidebarParticipants from './components/participants/sidebar_participants.vue';
|
import sidebarParticipants from './components/participants/sidebar_participants.vue';
|
||||||
import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue';
|
import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue';
|
||||||
import Translate from '../vue_shared/translate';
|
import Translate from '../vue_shared/translate';
|
||||||
|
import createDefaultClient from '~/lib/graphql';
|
||||||
|
|
||||||
Vue.use(Translate);
|
Vue.use(Translate);
|
||||||
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
|
function getSidebarOptions() {
|
||||||
|
return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
function mountAssigneesComponent(mediator) {
|
function mountAssigneesComponent(mediator) {
|
||||||
const el = document.getElementById('js-vue-sidebar-assignees');
|
const el = document.getElementById('js-vue-sidebar-assignees');
|
||||||
|
const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: createDefaultClient(),
|
||||||
|
});
|
||||||
|
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
|
const { iid, fullPath } = getSidebarOptions();
|
||||||
// eslint-disable-next-line no-new
|
// eslint-disable-next-line no-new
|
||||||
new Vue({
|
new Vue({
|
||||||
el,
|
el,
|
||||||
|
apolloProvider,
|
||||||
components: {
|
components: {
|
||||||
SidebarAssignees,
|
SidebarAssignees,
|
||||||
},
|
},
|
||||||
|
@ -26,6 +38,8 @@ function mountAssigneesComponent(mediator) {
|
||||||
createElement('sidebar-assignees', {
|
createElement('sidebar-assignees', {
|
||||||
props: {
|
props: {
|
||||||
mediator,
|
mediator,
|
||||||
|
issuableIid: String(iid),
|
||||||
|
projectPath: fullPath,
|
||||||
field: el.dataset.field,
|
field: el.dataset.field,
|
||||||
signedIn: el.hasAttribute('data-signed-in'),
|
signedIn: el.hasAttribute('data-signed-in'),
|
||||||
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
|
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
|
||||||
|
@ -144,6 +158,4 @@ export function mountSidebar(mediator) {
|
||||||
mountTimeTrackingComponent();
|
mountTimeTrackingComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSidebarOptions() {
|
export { getSidebarOptions };
|
||||||
return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
|
|
||||||
}
|
|
||||||
|
|
|
@ -89,6 +89,10 @@ export default class SidebarStore {
|
||||||
this.assignees = [];
|
this.assignees = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAssigneesFromRealtime(data) {
|
||||||
|
this.assignees = data;
|
||||||
|
}
|
||||||
|
|
||||||
setAutocompleteProjects(projects) {
|
setAutocompleteProjects(projects) {
|
||||||
this.autocompleteProjects = projects;
|
this.autocompleteProjects = projects;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
$btn-disabled-border: rgba(223, 223, 223, 0.24);
|
$btn-disabled-border: rgba(223, 223, 223, 0.24);
|
||||||
$btn-disabled-color: rgba(145, 145, 145, 0.48);
|
$btn-disabled-color: rgba(145, 145, 145, 0.48);
|
||||||
|
|
||||||
|
$dropdown-background: #404040;
|
||||||
|
$dropdown-hover-background: #525252;
|
||||||
|
|
||||||
$diff-insert: rgba(155, 185, 85, 0.2);
|
$diff-insert: rgba(155, 185, 85, 0.2);
|
||||||
$diff-remove: rgba(255, 0, 0, 0.2);
|
$diff-remove: rgba(255, 0, 0, 0.2);
|
||||||
|
|
||||||
|
@ -54,7 +57,12 @@
|
||||||
textarea,
|
textarea,
|
||||||
.md-area.is-focused,
|
.md-area.is-focused,
|
||||||
.ide-entry-dropdown-toggle,
|
.ide-entry-dropdown-toggle,
|
||||||
.nav-links:not(.quick-links) li:not(.md-header-toolbar) a:hover {
|
.nav-links:not(.quick-links) li:not(.md-header-toolbar) a:hover,
|
||||||
|
.dropdown-menu li button,
|
||||||
|
.ide-merge-request-project-path,
|
||||||
|
.dropdown-menu-selectable li a.is-active,
|
||||||
|
.dropdown-menu-inner-title,
|
||||||
|
.dropdown-menu-inner-content {
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,11 +90,17 @@
|
||||||
color: $text-color !important;
|
color: $text-color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type='search']::placeholder,
|
||||||
input[type='text']::placeholder,
|
input[type='text']::placeholder,
|
||||||
textarea::placeholder {
|
textarea::placeholder,
|
||||||
|
.dropdown-input .fa {
|
||||||
color: $input-border;
|
color: $input-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ide-nav-form .input-icon {
|
||||||
|
fill: $input-border;
|
||||||
|
}
|
||||||
|
|
||||||
.ide-staged-action-btn {
|
.ide-staged-action-btn {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +126,8 @@
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ide-sidebar-link:hover {
|
.ide-sidebar-link:hover,
|
||||||
|
.multi-file-tabs li {
|
||||||
background-color: $background-hover;
|
background-color: $background-hover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,21 +219,40 @@
|
||||||
background-color: $footer-background;
|
background-color: $footer-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='text'] {
|
input[type='text'],
|
||||||
|
input[type='search'],
|
||||||
|
.filtered-search-box {
|
||||||
border-color: $input-border;
|
border-color: $input-border;
|
||||||
background: $input-background;
|
background-color: $input-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='text'],
|
input[type='text'],
|
||||||
|
input[type='search'],
|
||||||
|
.filtered-search-box,
|
||||||
textarea {
|
textarea {
|
||||||
color: $input-color !important;
|
color: $input-color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filtered-search-box input[type='search'] {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filtered-search-token .value-container,
|
||||||
|
.filtered-search-term .value-container {
|
||||||
|
background-color: $dropdown-hover-background;
|
||||||
|
|
||||||
|
color: $text-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $input-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ide-entry-dropdown-toggle:hover {
|
.ide-entry-dropdown-toggle:hover {
|
||||||
background: $gray-800;
|
background: $gray-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:not(.btn-link):hover {
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
padding: 5px 9px;
|
padding: 5px 9px;
|
||||||
}
|
}
|
||||||
|
@ -257,6 +291,48 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
color: $text-color;
|
||||||
|
border-color: $background;
|
||||||
|
background-color: $dropdown-background;
|
||||||
|
|
||||||
|
.divider,
|
||||||
|
.nav-links:not(.quick-links) {
|
||||||
|
background-color: $dropdown-hover-background;
|
||||||
|
border-color: $dropdown-hover-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links li a.active {
|
||||||
|
border-color: $highlight-accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ide-nav-form .nav-links li a:not(.active) {
|
||||||
|
background-color: $dropdown-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links:not(.quick-links) li:not(.md-header-toolbar) a {
|
||||||
|
color: $text-color;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li > a:not(.disable-hover):hover,
|
||||||
|
li > a:not(.disable-hover):focus,
|
||||||
|
li button:not(.disable-hover):hover,
|
||||||
|
li button:not(.disable-hover):focus,
|
||||||
|
li button.is-focused {
|
||||||
|
background-color: $dropdown-hover-background;
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-title,
|
||||||
|
.dropdown-input {
|
||||||
|
border-color: $dropdown-hover-background !important;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: $btn-primary-background;
|
background-color: $btn-primary-background;
|
||||||
border-color: $btn-primary-border !important;
|
border-color: $btn-primary-border !important;
|
||||||
|
@ -320,3 +396,7 @@
|
||||||
.navbar.theme-dark {
|
.navbar.theme-dark {
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme-dark ~ .popover {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
|
@ -908,11 +908,10 @@ $note-form-margin-left: 72px;
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
|
|
||||||
.line-resolve-btn {
|
.line-resolve-btn {
|
||||||
margin-right: 5px;
|
|
||||||
color: $gray-700;
|
color: $gray-700;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
vertical-align: middle;
|
vertical-align: text-top;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||||
push_frontend_feature_flag(:save_issuable_health_status, project.group, default_enabled: true)
|
push_frontend_feature_flag(:save_issuable_health_status, project.group, default_enabled: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before_action only: :show do
|
||||||
|
push_frontend_feature_flag(:real_time_issue_sidebar, @project)
|
||||||
|
end
|
||||||
|
|
||||||
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
|
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
|
||||||
|
|
||||||
respond_to :html
|
respond_to :html
|
||||||
|
|
|
@ -14,6 +14,8 @@ module Types
|
||||||
description: 'ID of the user'
|
description: 'ID of the user'
|
||||||
field :name, GraphQL::STRING_TYPE, null: false,
|
field :name, GraphQL::STRING_TYPE, null: false,
|
||||||
description: 'Human-readable name of the user'
|
description: 'Human-readable name of the user'
|
||||||
|
field :state, GraphQL::STRING_TYPE, null: false,
|
||||||
|
description: 'State of the issue'
|
||||||
field :username, GraphQL::STRING_TYPE, null: false,
|
field :username, GraphQL::STRING_TYPE, null: false,
|
||||||
description: 'Username of the user. Unique within this instance of GitLab'
|
description: 'Username of the user. Unique within this instance of GitLab'
|
||||||
field :avatar_url, GraphQL::STRING_TYPE, null: true,
|
field :avatar_url, GraphQL::STRING_TYPE, null: true,
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module DesignManagement
|
||||||
|
class DesignAtVersionPolicy < ::BasePolicy
|
||||||
|
delegate { @subject.version }
|
||||||
|
delegate { @subject.design }
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module DesignManagement
|
||||||
|
class DesignCollectionPolicy < DesignPolicy
|
||||||
|
# Delegates everything to the `issue` just like the `DesignPolicy`
|
||||||
|
end
|
||||||
|
end
|
8
app/policies/design_management/design_policy.rb
Normal file
8
app/policies/design_management/design_policy.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module DesignManagement
|
||||||
|
class DesignPolicy < ::BasePolicy
|
||||||
|
# The IssuePolicy will delegate to the ProjectPolicy
|
||||||
|
delegate { @subject.issue }
|
||||||
|
end
|
||||||
|
end
|
8
app/policies/design_management/version_policy.rb
Normal file
8
app/policies/design_management/version_policy.rb
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module DesignManagement
|
||||||
|
class VersionPolicy < ::BasePolicy
|
||||||
|
# The IssuePolicy will delegate to the ProjectPolicy
|
||||||
|
delegate { @subject.issue }
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,6 +15,9 @@ class IssuePolicy < IssuablePolicy
|
||||||
desc "Issue is confidential"
|
desc "Issue is confidential"
|
||||||
condition(:confidential, scope: :subject) { @subject.confidential? }
|
condition(:confidential, scope: :subject) { @subject.confidential? }
|
||||||
|
|
||||||
|
desc "Issue has moved"
|
||||||
|
condition(:moved) { @subject.moved? }
|
||||||
|
|
||||||
rule { confidential & ~can_read_confidential }.policy do
|
rule { confidential & ~can_read_confidential }.policy do
|
||||||
prevent(*create_read_update_admin_destroy(:issue))
|
prevent(*create_read_update_admin_destroy(:issue))
|
||||||
prevent :read_issue_iid
|
prevent :read_issue_iid
|
||||||
|
@ -25,6 +28,15 @@ class IssuePolicy < IssuablePolicy
|
||||||
rule { locked }.policy do
|
rule { locked }.policy do
|
||||||
prevent :reopen_issue
|
prevent :reopen_issue
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
IssuePolicy.prepend_if_ee('::EE::IssuePolicy')
|
rule { ~can?(:read_issue) }.policy do
|
||||||
|
prevent :read_design
|
||||||
|
prevent :create_design
|
||||||
|
prevent :destroy_design
|
||||||
|
end
|
||||||
|
|
||||||
|
rule { locked | moved }.policy do
|
||||||
|
prevent :create_design
|
||||||
|
prevent :destroy_design
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -11,6 +11,7 @@ class ProjectPolicy < BasePolicy
|
||||||
milestone
|
milestone
|
||||||
snippet
|
snippet
|
||||||
wiki
|
wiki
|
||||||
|
design
|
||||||
note
|
note
|
||||||
pipeline
|
pipeline
|
||||||
pipeline_schedule
|
pipeline_schedule
|
||||||
|
@ -107,6 +108,11 @@ class ProjectPolicy < BasePolicy
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
with_scope :subject
|
||||||
|
condition(:design_management_disabled) do
|
||||||
|
!@subject.design_management_enabled?
|
||||||
|
end
|
||||||
|
|
||||||
# We aren't checking `:read_issue` or `:read_merge_request` in this case
|
# We aren't checking `:read_issue` or `:read_merge_request` in this case
|
||||||
# because it could be possible for a user to see an issuable-iid
|
# because it could be possible for a user to see an issuable-iid
|
||||||
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
|
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
|
||||||
|
@ -299,6 +305,8 @@ class ProjectPolicy < BasePolicy
|
||||||
enable :create_metrics_dashboard_annotation
|
enable :create_metrics_dashboard_annotation
|
||||||
enable :delete_metrics_dashboard_annotation
|
enable :delete_metrics_dashboard_annotation
|
||||||
enable :update_metrics_dashboard_annotation
|
enable :update_metrics_dashboard_annotation
|
||||||
|
enable :create_design
|
||||||
|
enable :destroy_design
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { can?(:developer_access) & user_confirmed? }.policy do
|
rule { can?(:developer_access) & user_confirmed? }.policy do
|
||||||
|
@ -511,6 +519,17 @@ class ProjectPolicy < BasePolicy
|
||||||
|
|
||||||
rule { admin }.enable :change_repository_storage
|
rule { admin }.enable :change_repository_storage
|
||||||
|
|
||||||
|
rule { can?(:read_issue) }.policy do
|
||||||
|
enable :read_design
|
||||||
|
end
|
||||||
|
|
||||||
|
# Design abilities could also be prevented in the issue policy.
|
||||||
|
rule { design_management_disabled }.policy do
|
||||||
|
prevent :read_design
|
||||||
|
prevent :create_design
|
||||||
|
prevent :destroy_design
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def team_member?
|
def team_member?
|
||||||
|
|
|
@ -68,6 +68,7 @@
|
||||||
|
|
||||||
= csrf_meta_tags
|
= csrf_meta_tags
|
||||||
= csp_meta_tag
|
= csp_meta_tag
|
||||||
|
= action_cable_meta_tag
|
||||||
|
|
||||||
- unless browser.safari?
|
- unless browser.safari?
|
||||||
%meta{ name: 'referrer', content: 'origin-when-cross-origin' }
|
%meta{ name: 'referrer', content: 'origin-when-cross-origin' }
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
%banner{ "v-if" => "!isOverviewDialogDismissed",
|
%banner{ "v-if" => "!isOverviewDialogDismissed",
|
||||||
"documentation-link": help_page_path('user/analytics/value_stream_analytics.md'),
|
"documentation-link": help_page_path('user/analytics/value_stream_analytics.md'),
|
||||||
"v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" }
|
"v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" }
|
||||||
|
.mb-3
|
||||||
|
%h3
|
||||||
|
= _("Value Stream Analytics")
|
||||||
%gl-loading-icon{ "v-show" => "isLoading", "size" => "lg" }
|
%gl-loading-icon{ "v-show" => "isLoading", "size" => "lg" }
|
||||||
.wrapper{ "v-show" => "!isLoading && !hasError" }
|
.wrapper{ "v-show" => "!isLoading && !hasError" }
|
||||||
.card
|
.card
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- issuable_type = issuable_sidebar[:type]
|
- issuable_type = issuable_sidebar[:type]
|
||||||
- signed_in = !!issuable_sidebar.dig(:current_user, :id)
|
- signed_in = !!issuable_sidebar.dig(:current_user, :id)
|
||||||
|
|
||||||
#js-vue-sidebar-assignees{ data: { field: "#{issuable_type}", signed_in: signed_in } }
|
#js-vue-sidebar-assignees{ data: { field: issuable_type, signed_in: signed_in } }
|
||||||
.title.hide-collapsed
|
.title.hide-collapsed
|
||||||
= _('Assignee')
|
= _('Assignee')
|
||||||
.spinner.spinner-sm.align-bottom
|
.spinner.spinner-sm.align-bottom
|
||||||
|
|
5
changelogs/unreleased/201927-solarized-dark.yml
Normal file
5
changelogs/unreleased/201927-solarized-dark.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Monokai and Solarized Dark syntax highlighting theme for Web IDE
|
||||||
|
merge_request: 30931
|
||||||
|
author:
|
||||||
|
type: added
|
6
changelogs/unreleased/214882-esc-key-handler.yml
Normal file
6
changelogs/unreleased/214882-esc-key-handler.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
title: When viewing a single panel, return to a full dashboard by pressing the Escape
|
||||||
|
key
|
||||||
|
merge_request: 30126
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix incorrect commits number in commits list
|
||||||
|
merge_request: 30412
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Handle possible RSA key exceptions when generating CI_JOB_JWT
|
||||||
|
merge_request: 30702
|
||||||
|
author:
|
||||||
|
type: changed
|
5
changelogs/unreleased/mw-cr-title-margin.yml
Normal file
5
changelogs/unreleased/mw-cr-title-margin.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: 'Code review analytics: Change margin between title and description'
|
||||||
|
merge_request: 30834
|
||||||
|
author:
|
||||||
|
type: changed
|
5
changelogs/unreleased/mw-ia-add-title.yml
Normal file
5
changelogs/unreleased/mw-ia-add-title.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: 'Issues Analytics: Add title to page'
|
||||||
|
merge_request: 30836
|
||||||
|
author:
|
||||||
|
type: added
|
5
changelogs/unreleased/mw-insights-add-title.yml
Normal file
5
changelogs/unreleased/mw-insights-add-title.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: 'Insights Analytics: Add title to page'
|
||||||
|
merge_request: 30853
|
||||||
|
author:
|
||||||
|
type: added
|
5
changelogs/unreleased/mw-vsa-title-cleanup.yml
Normal file
5
changelogs/unreleased/mw-vsa-title-cleanup.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: 'Value Stream Analytics: Add title and remove separator'
|
||||||
|
merge_request: 30841
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Change wording of merge request threads counter
|
||||||
|
merge_request: 30217
|
||||||
|
author:
|
||||||
|
type: changed
|
5
changelogs/unreleased/ph-215917-escapeRef.yml
Normal file
5
changelogs/unreleased/ph-215917-escapeRef.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fixes branch name not getting escaped correctly on frontend
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix second 500 error with NULL restricted visibility levels
|
||||||
|
merge_request: 30414
|
||||||
|
author:
|
||||||
|
type: fixed
|
5
changelogs/unreleased/sh-disable-schema-dump-prod.yml
Normal file
5
changelogs/unreleased/sh-disable-schema-dump-prod.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Disable schema dumping after migrations in production
|
||||||
|
merge_request: 30812
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix errors creating project with active Prometheus service template
|
||||||
|
merge_request: 30340
|
||||||
|
author:
|
||||||
|
type: fixed
|
5
changelogs/unreleased/sh-fix-lfs-badge-feature-flag.yml
Normal file
5
changelogs/unreleased/sh-fix-lfs-badge-feature-flag.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add LFS badge feature flag to RefsController#logs_tree
|
||||||
|
merge_request: 30442
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -608,7 +608,7 @@ installations from source.
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19186) in GitLab 12.6.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19186) in GitLab 12.6.
|
||||||
|
|
||||||
This file lives in `/var/log/gitlab/mail_room/mail_room_json.log` for
|
This file lives in `/var/log/gitlab/mailroom/mail_room_json.log` for
|
||||||
Omnibus GitLab packages or in `/home/git/gitlab/log/mail_room_json.log` for
|
Omnibus GitLab packages or in `/home/git/gitlab/log/mail_room_json.log` for
|
||||||
installations from source.
|
installations from source.
|
||||||
|
|
||||||
|
@ -648,7 +648,7 @@ It's stored at:
|
||||||
- `/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab packages.
|
- `/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab packages.
|
||||||
- `/home/git/gitlab/log/database_load_balancing.log` for installations from source.
|
- `/home/git/gitlab/log/database_load_balancing.log` for installations from source.
|
||||||
|
|
||||||
## `elasticsearch.log`
|
## `elasticsearch.log` **(STARTER ONLY)**
|
||||||
|
|
||||||
> Introduced in GitLab 12.6.
|
> Introduced in GitLab 12.6.
|
||||||
|
|
||||||
|
@ -718,7 +718,7 @@ Each line contains a JSON line that can be ingested by Elasticsearch. For exampl
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `geo.log`
|
## `geo.log` **(PREMIUM ONLY)**
|
||||||
|
|
||||||
> Introduced in 9.5.
|
> Introduced in 9.5.
|
||||||
|
|
||||||
|
|
|
@ -10091,6 +10091,11 @@ type User {
|
||||||
visibility: VisibilityScopesEnum
|
visibility: VisibilityScopesEnum
|
||||||
): SnippetConnection
|
): SnippetConnection
|
||||||
|
|
||||||
|
"""
|
||||||
|
State of the issue
|
||||||
|
"""
|
||||||
|
state: String!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Todos of the user
|
Todos of the user
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -30221,6 +30221,24 @@
|
||||||
"isDeprecated": false,
|
"isDeprecated": false,
|
||||||
"deprecationReason": null
|
"deprecationReason": null
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "state",
|
||||||
|
"description": "State of the issue",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "String",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "todos",
|
"name": "todos",
|
||||||
"description": "Todos of the user",
|
"description": "Todos of the user",
|
||||||
|
|
|
@ -1567,6 +1567,7 @@ Autogenerated return type of UpdateSnippet
|
||||||
| `avatarUrl` | String | URL of the user's avatar |
|
| `avatarUrl` | String | URL of the user's avatar |
|
||||||
| `id` | ID! | ID of the user |
|
| `id` | ID! | ID of the user |
|
||||||
| `name` | String! | Human-readable name of the user |
|
| `name` | String! | Human-readable name of the user |
|
||||||
|
| `state` | String! | State of the issue |
|
||||||
| `userPermissions` | UserPermissions! | Permissions for the current user on the resource |
|
| `userPermissions` | UserPermissions! | Permissions for the current user on the resource |
|
||||||
| `username` | String! | Username of the user. Unique within this instance of GitLab |
|
| `username` | String! | Username of the user. Unique within this instance of GitLab |
|
||||||
| `webUrl` | String! | Web URL of the user |
|
| `webUrl` | String! | Web URL of the user |
|
||||||
|
|
|
@ -184,6 +184,9 @@ This can help to quickly understand the control flow.
|
||||||
// bad
|
// bad
|
||||||
if (isThingNull) return '';
|
if (isThingNull) return '';
|
||||||
|
|
||||||
|
if (isThingNull)
|
||||||
|
return '';
|
||||||
|
|
||||||
// good
|
// good
|
||||||
if (isThingNull) {
|
if (isThingNull) {
|
||||||
return '';
|
return '';
|
||||||
|
|
|
@ -214,6 +214,11 @@ msgid_plural "%d tags"
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "%d unresolved thread"
|
||||||
|
msgid_plural "%d unresolved threads"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
msgid "%d vulnerability dismissed"
|
msgid "%d vulnerability dismissed"
|
||||||
msgid_plural "%d vulnerabilities dismissed"
|
msgid_plural "%d vulnerabilities dismissed"
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
|
@ -1827,6 +1832,9 @@ msgstr ""
|
||||||
msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
|
msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "All threads resolved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "All users"
|
msgid "All users"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -13174,7 +13182,7 @@ msgstr ""
|
||||||
msgid "Metrics|For grouping similar metrics"
|
msgid "Metrics|For grouping similar metrics"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Metrics|Go back"
|
msgid "Metrics|Go back (Esc)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Metrics|Invalid time range, please verify."
|
msgid "Metrics|Invalid time range, please verify."
|
||||||
|
@ -23434,6 +23442,9 @@ msgstr ""
|
||||||
msgid "VulnerabilityStatusTypes|Resolved"
|
msgid "VulnerabilityStatusTypes|Resolved"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Vulnerability|%{scannerName} (version %{scannerVersion})"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Vulnerability|Class"
|
msgid "Vulnerability|Class"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -23467,7 +23478,10 @@ msgstr ""
|
||||||
msgid "Vulnerability|Project"
|
msgid "Vulnerability|Project"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Vulnerability|Report Type"
|
msgid "Vulnerability|Scanner Provider"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Vulnerability|Scanner Type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Vulnerability|Severity"
|
msgid "Vulnerability|Severity"
|
||||||
|
@ -25699,11 +25713,6 @@ msgstr ""
|
||||||
msgid "this document"
|
msgid "this document"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "thread resolved"
|
|
||||||
msgid_plural "threads resolved"
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
msgid "to help your contributors communicate effectively!"
|
msgid "to help your contributors communicate effectively!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"@gitlab/svgs": "1.121.0",
|
"@gitlab/svgs": "1.121.0",
|
||||||
"@gitlab/ui": "13.6.1",
|
"@gitlab/ui": "13.6.1",
|
||||||
"@gitlab/visual-review-tools": "1.6.1",
|
"@gitlab/visual-review-tools": "1.6.1",
|
||||||
|
"@rails/actioncable": "^6.0.2-2",
|
||||||
"@sentry/browser": "^5.10.2",
|
"@sentry/browser": "^5.10.2",
|
||||||
"@sourcegraph/code-host-integration": "0.0.37",
|
"@sourcegraph/code-host-integration": "0.0.37",
|
||||||
"@toast-ui/editor": "^2.0.1",
|
"@toast-ui/editor": "^2.0.1",
|
||||||
|
|
|
@ -43,7 +43,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
context 'single thread' do
|
context 'single thread' do
|
||||||
it 'shows text with how many threads' do
|
it 'shows text with how many threads' do
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -77,7 +77,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -89,7 +89,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
expect(page).not_to have_selector('.line-resolve-btn.is-active')
|
expect(page).not_to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -189,7 +189,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -203,7 +203,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -218,7 +218,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -275,7 +275,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
expect(page).to have_content('Last updated')
|
expect(page).to have_content('Last updated')
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -305,7 +305,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
|
|
||||||
it 'shows text with how many threads' do
|
it 'shows text with how many threads' do
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/2 threads resolved')
|
expect(page).to have_content('2 unresolved threads')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -313,7 +313,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
click_button('Resolve thread', match: :first)
|
click_button('Resolve thread', match: :first)
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/2 threads resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('2/2 threads resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -336,7 +336,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('2/2 threads resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -392,7 +392,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
context 'changes tab' do
|
context 'changes tab' do
|
||||||
it 'shows text with how many threads' do
|
it 'shows text with how many threads' do
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -408,7 +408,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -423,7 +423,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -435,7 +435,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -449,7 +449,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -466,7 +466,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -489,7 +489,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -519,7 +519,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('1/1 thread resolved')
|
expect(page).to have_content('All threads resolved')
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -538,7 +538,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.line-resolve-all-container' do
|
page.within '.line-resolve-all-container' do
|
||||||
expect(page).to have_content('0/1 thread resolved')
|
expect(page).to have_content('1 unresolved thread')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -550,17 +550,17 @@ describe 'Merge request > User resolves diff notes and threads', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows resolved icon' do
|
it 'shows resolved icon' do
|
||||||
expect(page).to have_content '1/1 thread resolved'
|
expect(page).to have_content 'All threads resolved'
|
||||||
|
|
||||||
click_button 'Toggle thread'
|
click_button 'Toggle thread'
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-active')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not allow user to click resolve button' do
|
it 'does not allow user to click resolve button' do
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-disabled')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
click_button 'Toggle thread'
|
click_button 'Toggle thread'
|
||||||
|
|
||||||
expect(page).to have_selector('.line-resolve-btn.is-disabled')
|
expect(page).to have_selector('.line-resolve-btn.is-active')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { shallowMount, mount } from '@vue/test-utils';
|
import { shallowMount, mount } from '@vue/test-utils';
|
||||||
import Tracking from '~/tracking';
|
import Tracking from '~/tracking';
|
||||||
|
import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys';
|
||||||
import { GlModal, GlDropdownItem, GlDeprecatedButton } from '@gitlab/ui';
|
import { GlModal, GlDropdownItem, GlDeprecatedButton } from '@gitlab/ui';
|
||||||
import VueDraggable from 'vuedraggable';
|
import VueDraggable from 'vuedraggable';
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
@ -248,6 +249,8 @@ describe('Dashboard', () => {
|
||||||
let group;
|
let group;
|
||||||
let panel;
|
let panel;
|
||||||
|
|
||||||
|
const mockKeyup = key => window.dispatchEvent(new KeyboardEvent('keyup', { key }));
|
||||||
|
|
||||||
const MockPanel = {
|
const MockPanel = {
|
||||||
template: `<div><slot name="topLeft"/></div>`,
|
template: `<div><slot name="topLeft"/></div>`,
|
||||||
};
|
};
|
||||||
|
@ -265,6 +268,9 @@ describe('Dashboard', () => {
|
||||||
group,
|
group,
|
||||||
panel,
|
panel,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.spyOn(store, 'dispatch');
|
||||||
|
|
||||||
return wrapper.vm.$nextTick();
|
return wrapper.vm.$nextTick();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -289,17 +295,30 @@ describe('Dashboard', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('restores full dashboard by clicking `back`', () => {
|
it('restores full dashboard by clicking `back`', () => {
|
||||||
const backBtn = wrapper.find({ ref: 'goBackBtn' });
|
wrapper.find({ ref: 'goBackBtn' }).vm.$emit('click');
|
||||||
expect(backBtn.exists()).toBe(true);
|
|
||||||
|
|
||||||
jest.spyOn(store, 'dispatch');
|
|
||||||
backBtn.vm.$emit('click');
|
|
||||||
|
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(
|
expect(store.dispatch).toHaveBeenCalledWith(
|
||||||
'monitoringDashboard/clearExpandedPanel',
|
'monitoringDashboard/clearExpandedPanel',
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('restores dashboard from full screen by typing the Escape key', () => {
|
||||||
|
mockKeyup(ESC_KEY);
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(
|
||||||
|
`monitoringDashboard/clearExpandedPanel`,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('restores dashboard from full screen by typing the Escape key on IE11', () => {
|
||||||
|
mockKeyup(ESC_KEY_IE11);
|
||||||
|
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(
|
||||||
|
`monitoringDashboard/clearExpandedPanel`,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -75,15 +75,14 @@ describe('DiscussionCounter component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each`
|
it.each`
|
||||||
title | resolved | isActive | icon | groupLength
|
title | resolved | isActive | groupLength
|
||||||
${'not allResolved'} | ${false} | ${false} | ${'check-circle'} | ${3}
|
${'not allResolved'} | ${false} | ${false} | ${3}
|
||||||
${'allResolved'} | ${true} | ${true} | ${'check-circle-filled'} | ${1}
|
${'allResolved'} | ${true} | ${true} | ${1}
|
||||||
`('renders correctly if $title', ({ resolved, isActive, icon, groupLength }) => {
|
`('renders correctly if $title', ({ resolved, isActive, groupLength }) => {
|
||||||
updateStore({ resolvable: true, resolved });
|
updateStore({ resolvable: true, resolved });
|
||||||
wrapper = shallowMount(DiscussionCounter, { store, localVue });
|
wrapper = shallowMount(DiscussionCounter, { store, localVue });
|
||||||
|
|
||||||
expect(wrapper.find(`.is-active`).exists()).toBe(isActive);
|
expect(wrapper.find(`.is-active`).exists()).toBe(isActive);
|
||||||
expect(wrapper.find({ name: icon }).exists()).toBe(true);
|
|
||||||
expect(wrapper.findAll('[role="group"').length).toBe(groupLength);
|
expect(wrapper.findAll('[role="group"').length).toBe(groupLength);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
100
spec/frontend/sidebar/assignees_realtime_spec.js
Normal file
100
spec/frontend/sidebar/assignees_realtime_spec.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import ActionCable from '@rails/actioncable';
|
||||||
|
import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
|
||||||
|
import SidebarMediator from '~/sidebar/sidebar_mediator';
|
||||||
|
import Mock from './mock_data';
|
||||||
|
import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
|
||||||
|
|
||||||
|
jest.mock('@rails/actioncable', () => {
|
||||||
|
const mockConsumer = { subscriptions: { create: jest.fn() } };
|
||||||
|
return {
|
||||||
|
createConsumer: jest.fn().mockReturnValue(mockConsumer),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Assignees Realtime', () => {
|
||||||
|
let wrapper;
|
||||||
|
let mediator;
|
||||||
|
|
||||||
|
const createComponent = () => {
|
||||||
|
wrapper = shallowMount(AssigneesRealtime, {
|
||||||
|
propsData: {
|
||||||
|
issuableIid: '1',
|
||||||
|
mediator,
|
||||||
|
projectPath: 'path/to/project',
|
||||||
|
},
|
||||||
|
mocks: {
|
||||||
|
$apollo: {
|
||||||
|
query,
|
||||||
|
queries: {
|
||||||
|
project: {
|
||||||
|
refetch: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mediator = new SidebarMediator(Mock.mediator);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
wrapper = null;
|
||||||
|
SidebarMediator.singleton = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when handleFetchResult is called from smart query', () => {
|
||||||
|
it('sets assignees to the store', () => {
|
||||||
|
const data = {
|
||||||
|
project: {
|
||||||
|
issue: {
|
||||||
|
assignees: {
|
||||||
|
nodes: [{ id: 'gid://gitlab/Environments/123', avatarUrl: 'url' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const expected = [{ id: 123, avatar_url: 'url', avatarUrl: 'url' }];
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
wrapper.vm.handleFetchResult({ data });
|
||||||
|
|
||||||
|
expect(mediator.store.assignees).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when mounted', () => {
|
||||||
|
it('calls create subscription', () => {
|
||||||
|
const cable = ActionCable.createConsumer();
|
||||||
|
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
return wrapper.vm.$nextTick().then(() => {
|
||||||
|
expect(cable.subscriptions.create).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cable.subscriptions.create).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
channel: 'IssuesChannel',
|
||||||
|
iid: wrapper.props('issuableIid'),
|
||||||
|
project_path: wrapper.props('projectPath'),
|
||||||
|
},
|
||||||
|
{ received: wrapper.vm.received },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when subscription is recieved', () => {
|
||||||
|
it('refetches the GraphQL project query', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
wrapper.vm.received({ event: 'updated' });
|
||||||
|
|
||||||
|
return wrapper.vm.$nextTick().then(() => {
|
||||||
|
expect(wrapper.vm.$apollo.queries.project.refetch).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,6 +3,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees.vue';
|
import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees.vue';
|
||||||
import Assigness from '~/sidebar/components/assignees/assignees.vue';
|
import Assigness from '~/sidebar/components/assignees/assignees.vue';
|
||||||
|
import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
|
||||||
import SidebarMediator from '~/sidebar/sidebar_mediator';
|
import SidebarMediator from '~/sidebar/sidebar_mediator';
|
||||||
import SidebarService from '~/sidebar/services/sidebar_service';
|
import SidebarService from '~/sidebar/services/sidebar_service';
|
||||||
import SidebarStore from '~/sidebar/stores/sidebar_store';
|
import SidebarStore from '~/sidebar/stores/sidebar_store';
|
||||||
|
@ -12,12 +13,19 @@ describe('sidebar assignees', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let mediator;
|
let mediator;
|
||||||
let axiosMock;
|
let axiosMock;
|
||||||
|
const createComponent = (realTimeIssueSidebar = false, props) => {
|
||||||
const createComponent = () => {
|
|
||||||
wrapper = shallowMount(SidebarAssignees, {
|
wrapper = shallowMount(SidebarAssignees, {
|
||||||
propsData: {
|
propsData: {
|
||||||
|
issuableIid: '1',
|
||||||
mediator,
|
mediator,
|
||||||
field: '',
|
field: '',
|
||||||
|
projectPath: 'projectPath',
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
provide: {
|
||||||
|
glFeatures: {
|
||||||
|
realTimeIssueSidebar,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// Attaching to document is required because this component emits something from the parent element :/
|
// Attaching to document is required because this component emits something from the parent element :/
|
||||||
attachToDocument: true,
|
attachToDocument: true,
|
||||||
|
@ -30,8 +38,6 @@ describe('sidebar assignees', () => {
|
||||||
|
|
||||||
jest.spyOn(mediator, 'saveAssignees');
|
jest.spyOn(mediator, 'saveAssignees');
|
||||||
jest.spyOn(mediator, 'assignYourself');
|
jest.spyOn(mediator, 'assignYourself');
|
||||||
|
|
||||||
createComponent();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -45,6 +51,8 @@ describe('sidebar assignees', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls the mediator when saves the assignees', () => {
|
it('calls the mediator when saves the assignees', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
expect(mediator.saveAssignees).not.toHaveBeenCalled();
|
expect(mediator.saveAssignees).not.toHaveBeenCalled();
|
||||||
|
|
||||||
wrapper.vm.saveAssignees();
|
wrapper.vm.saveAssignees();
|
||||||
|
@ -53,6 +61,8 @@ describe('sidebar assignees', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls the mediator when "assignSelf" method is called', () => {
|
it('calls the mediator when "assignSelf" method is called', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
expect(mediator.assignYourself).not.toHaveBeenCalled();
|
expect(mediator.assignYourself).not.toHaveBeenCalled();
|
||||||
expect(mediator.store.assignees.length).toBe(0);
|
expect(mediator.store.assignees.length).toBe(0);
|
||||||
|
|
||||||
|
@ -63,6 +73,8 @@ describe('sidebar assignees', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides assignees until fetched', () => {
|
it('hides assignees until fetched', () => {
|
||||||
|
createComponent();
|
||||||
|
|
||||||
expect(wrapper.find(Assigness).exists()).toBe(false);
|
expect(wrapper.find(Assigness).exists()).toBe(false);
|
||||||
|
|
||||||
wrapper.vm.store.isFetching.assignees = false;
|
wrapper.vm.store.isFetching.assignees = false;
|
||||||
|
@ -71,4 +83,30 @@ describe('sidebar assignees', () => {
|
||||||
expect(wrapper.find(Assigness).exists()).toBe(true);
|
expect(wrapper.find(Assigness).exists()).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when realTimeIssueSidebar is turned on', () => {
|
||||||
|
describe('when issuableType is issue', () => {
|
||||||
|
it('finds AssigneesRealtime componeont', () => {
|
||||||
|
createComponent(true);
|
||||||
|
|
||||||
|
expect(wrapper.find(AssigneesRealtime).exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when issuableType is MR', () => {
|
||||||
|
it('does not find AssigneesRealtime componeont', () => {
|
||||||
|
createComponent(true, { issuableType: 'MR' });
|
||||||
|
|
||||||
|
expect(wrapper.find(AssigneesRealtime).exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when realTimeIssueSidebar is turned off', () => {
|
||||||
|
it('does not find AssigneesRealtime', () => {
|
||||||
|
createComponent(false, { issuableType: 'issue' });
|
||||||
|
|
||||||
|
expect(wrapper.find(AssigneesRealtime).exists()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ describe GitlabSchema.types['User'] do
|
||||||
|
|
||||||
it 'has the expected fields' do
|
it 'has the expected fields' do
|
||||||
expected_fields = %w[
|
expected_fields = %w[
|
||||||
id user_permissions snippets name username avatarUrl webUrl todos
|
id user_permissions snippets name username avatarUrl webUrl todos state
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||||
|
|
174
spec/policies/design_management/design_policy_spec.rb
Normal file
174
spec/policies/design_management/design_policy_spec.rb
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe DesignManagement::DesignPolicy do
|
||||||
|
include DesignManagementTestHelpers
|
||||||
|
|
||||||
|
include_context 'ProjectPolicy context'
|
||||||
|
|
||||||
|
let(:guest_design_abilities) { %i[read_design] }
|
||||||
|
let(:developer_design_abilities) do
|
||||||
|
%i[create_design destroy_design]
|
||||||
|
end
|
||||||
|
let(:design_abilities) { guest_design_abilities + developer_design_abilities }
|
||||||
|
|
||||||
|
let(:issue) { create(:issue, project: project) }
|
||||||
|
let(:design) { create(:design, issue: issue) }
|
||||||
|
|
||||||
|
subject(:design_policy) { described_class.new(current_user, design) }
|
||||||
|
|
||||||
|
shared_examples_for "design abilities not available" do
|
||||||
|
context "for owners" do
|
||||||
|
let(:current_user) { owner }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for admins" do
|
||||||
|
let(:current_user) { admin }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for maintainers" do
|
||||||
|
let(:current_user) { maintainer }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for developers" do
|
||||||
|
let(:current_user) { developer }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for reporters" do
|
||||||
|
let(:current_user) { reporter }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for guests" do
|
||||||
|
let(:current_user) { guest }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for anonymous users" do
|
||||||
|
let(:current_user) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples_for "design abilities available for members" do
|
||||||
|
context "for owners" do
|
||||||
|
let(:current_user) { owner }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for admins" do
|
||||||
|
let(:current_user) { admin }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for maintainers" do
|
||||||
|
let(:current_user) { maintainer }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for developers" do
|
||||||
|
let(:current_user) { developer }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for reporters" do
|
||||||
|
let(:current_user) { reporter }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(*guest_design_abilities) }
|
||||||
|
it { is_expected.to be_disallowed(*developer_design_abilities) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples_for "read-only design abilities" do
|
||||||
|
it { is_expected.to be_allowed(:read_design) }
|
||||||
|
it { is_expected.to be_disallowed(:create_design, :destroy_design) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when DesignManagement is not enabled" do
|
||||||
|
before do
|
||||||
|
enable_design_management(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like "design abilities not available"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the feature is available" do
|
||||||
|
before do
|
||||||
|
enable_design_management
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like "design abilities available for members"
|
||||||
|
|
||||||
|
context "for guests in private projects" do
|
||||||
|
let(:project) { create(:project, :private) }
|
||||||
|
let(:current_user) { guest }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(*guest_design_abilities) }
|
||||||
|
it { is_expected.to be_disallowed(*developer_design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for anonymous users in public projects" do
|
||||||
|
let(:current_user) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed(*guest_design_abilities) }
|
||||||
|
it { is_expected.to be_disallowed(*developer_design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the issue is confidential" do
|
||||||
|
let(:issue) { create(:issue, :confidential, project: project) }
|
||||||
|
|
||||||
|
it_behaves_like "design abilities available for members"
|
||||||
|
|
||||||
|
context "for guests" do
|
||||||
|
let(:current_user) { guest }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "for anonymous users" do
|
||||||
|
let(:current_user) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(*design_abilities) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the issue is locked" do
|
||||||
|
let(:current_user) { owner }
|
||||||
|
let(:issue) { create(:issue, :locked, project: project) }
|
||||||
|
|
||||||
|
it_behaves_like "read-only design abilities"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the issue has moved" do
|
||||||
|
let(:current_user) { owner }
|
||||||
|
let(:issue) { create(:issue, project: project, moved_to: create(:issue)) }
|
||||||
|
|
||||||
|
it_behaves_like "read-only design abilities"
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the project is archived" do
|
||||||
|
let(:current_user) { owner }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.update!(archived: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like "read-only design abilities"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -985,6 +985,11 @@
|
||||||
consola "^2.10.1"
|
consola "^2.10.1"
|
||||||
node-fetch "^2.6.0"
|
node-fetch "^2.6.0"
|
||||||
|
|
||||||
|
"@rails/actioncable@^6.0.2-2":
|
||||||
|
version "6.0.2-2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.0.2-2.tgz#237907f8111707950381387c273b19ac25958408"
|
||||||
|
integrity sha512-0sKStf8hnberH1TKup10PJ92JT2dVqf3gf+OT4lJ7DiYSBEuDcvICHxWsyML2oWTpjUhC4kLvUJ3pXL2JJrJuQ==
|
||||||
|
|
||||||
"@sentry/browser@^5.10.2":
|
"@sentry/browser@^5.10.2":
|
||||||
version "5.10.2"
|
version "5.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.10.2.tgz#0bbb05505c58ea998c833cffec3f922fe4b4fa58"
|
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.10.2.tgz#0bbb05505c58ea998c833cffec3f922fe4b4fa58"
|
||||||
|
|
Loading…
Reference in a new issue