提交 8eef083c 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 e1d53af7
......@@ -112,10 +112,10 @@ downtime_check:
.rspec-base-pg11:
extends:
- .rspec-base
- .rails:rules:ee-and-foss
- .use-pg11
.rspec-base-migration:
extends: .rails:rules:ee-and-foss-migration
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
......@@ -129,26 +129,34 @@ rspec migration pg11:
parallel: 5
rspec unit pg11:
extends: .rspec-base-pg11
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-unit
parallel: 20
rspec integration pg11:
extends: .rspec-base-pg11
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-integration
parallel: 8
rspec system pg11:
extends: .rspec-base-pg11
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-system
parallel: 24
rspec fast_spec_helper:
extends: .rspec-base-pg11
extends:
- .rspec-base-pg11
- .rails:rules:ee-and-foss-fast_spec_helper
script:
- bin/rspec spec/fast_spec_helper.rb
.db-job-base:
extends:
- .rails-job-base
- .rails:rules:ee-and-foss
- .rails:rules:ee-and-foss-migration
- .use-pg11
stage: test
needs: ["setup-test-env"]
......@@ -253,62 +261,70 @@ rspec:coverage:
##################################################
# EE: default refs (MRs, master, schedules) jobs #
.rspec-base-ee:
extends:
- .rspec-base
- .rails:rules:ee-only
.rspec-base-pg11-as-if-foss:
extends:
- .rspec-base
- .rails:rules:as-if-foss
- .as-if-foss
- .use-pg11
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets as-if-foss"]
.rspec-ee-base-pg11:
extends:
- .rspec-base-ee
- .use-pg11-ee
rspec migration pg11-as-if-foss:
extends:
- .rspec-base-pg11-as-if-foss
- .rspec-base-migration
- .rails:rules:as-if-foss-migration
parallel: 5
rspec unit pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-unit
parallel: 20
rspec integration pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-integration
parallel: 8
rspec system pg11-as-if-foss:
extends: .rspec-base-pg11-as-if-foss
extends:
- .rspec-base-pg11-as-if-foss
- .rails:rules:as-if-foss-system
parallel: 24
.rspec-ee-base-pg11:
extends:
- .rspec-base
- .use-pg11-ee
rspec-ee migration pg11:
extends:
- .rspec-ee-base-pg11
- .rspec-base-migration
- .rails:rules:ee-only-migration
parallel: 2
rspec-ee unit pg11:
extends: .rspec-ee-base-pg11
extends:
- .rspec-ee-base-pg11
- .rails:rules:ee-only-unit
parallel: 10
rspec-ee integration pg11:
extends: .rspec-ee-base-pg11
extends:
- .rspec-ee-base-pg11
- .rails:rules:ee-only-integration
parallel: 4
rspec-ee system pg11:
extends: .rspec-ee-base-pg11
extends:
- .rspec-ee-base-pg11
- .rails:rules:ee-only-system
parallel: 6
.rspec-ee-base-geo:
extends: .rspec-base-ee
extends: .rspec-base
script:
- run_timed_command "scripts/gitaly-test-build"
- run_timed_command "scripts/gitaly-test-spawn"
......@@ -322,19 +338,25 @@ rspec-ee system pg11:
- .use-pg11-ee
rspec-ee unit pg11 geo:
extends: .rspec-ee-base-geo-pg11
extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-unit
parallel: 2
rspec-ee integration pg11 geo:
extends: .rspec-ee-base-geo-pg11
extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-integration
rspec-ee system pg11 geo:
extends: .rspec-ee-base-geo-pg11
extends:
- .rspec-ee-base-geo-pg11
- .rails:rules:ee-only-system
db:rollback geo:
extends:
- db:rollback
- .rails:rules:ee-only
- .rails:rules:ee-only-migration
script:
- bundle exec rake geo:db:migrate VERSION=20170627195211
- bundle exec rake geo:db:migrate
......
......@@ -92,6 +92,17 @@
- "vendor/assets/**/*"
- "{,ee/}{app/assets,app/helpers,app/presenters,app/views,locale,public,symbol}/**/*"
.backend-patterns: &backend-patterns
- "Gemfile{,.lock}"
- "Rakefile"
- "config.ru"
# List explicitly all the app/ dirs that aren't backend (i.e. all except app/assets).
- "{,ee/}{app/channels,app/controllers,app/finders,app/graphql,app/helpers,app/mailers,app/models,app/policies,app/presenters,app/serializers,app/services,app/uploaders,app/validators,app/views,app/workers}/**/*"
- "{,ee/}{bin,cable,config,db,lib}/**/*"
.db-patterns: &db-patterns
- "{,ee/}{db}/**/*"
.backstage-patterns: &backstage-patterns
- "Dangerfile"
- "danger/**/*"
......@@ -382,24 +393,86 @@
###############
# Rails rules #
###############
.rails:rules:ee-and-foss:
.rails:rules:ee-and-foss-migration:
rules:
- <<: *if-default-refs
changes: *code-backstage-patterns
- changes: *db-patterns
.rails:rules:ee-and-foss-unit:
rules:
- changes: *backend-patterns
.rails:rules:ee-and-foss-integration:
rules:
- changes: *backend-patterns
.rails:rules:ee-and-foss-system:
rules:
- changes: *code-backstage-patterns
.rails:rules:ee-and-foss-fast_spec_helper:
rules:
- changes: ["config/**/*"]
.rails:rules:default-refs-code-backstage-qa:
rules:
- <<: *if-default-refs
changes: *code-backstage-qa-patterns
.rails:rules:ee-only:
.rails:rules:ee-only-migration:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-refs
changes: *code-backstage-patterns
- changes: *db-patterns
.rails:rules:ee-only-unit:
rules:
- <<: *if-not-ee
when: never
- changes: *backend-patterns
.rails:rules:ee-only-integration:
rules:
- <<: *if-not-ee
when: never
- changes: *backend-patterns
.rails:rules:ee-only-system:
rules:
- <<: *if-not-ee
when: never
- changes: *code-backstage-patterns
.rails:rules:as-if-foss-migration:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
changes: *db-patterns
- <<: *if-merge-request-title-as-if-foss
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-unit:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss-integration:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
- <<: *if-merge-request
changes: *ci-patterns
.rails:rules:as-if-foss:
.rails:rules:as-if-foss-system:
rules:
- <<: *if-not-ee
when: never
......
<script>
import { ApolloMutation } from 'vue-apollo';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import destroyDesignMutation from '../graphql/mutations/destroyDesign.mutation.graphql';
import destroyDesignMutation from '../graphql/mutations/destroy_design.mutation.graphql';
import { updateStoreAfterDesignsDelete } from '../utils/cache_update';
export default {
......
......@@ -5,9 +5,9 @@ import { s__ } from '~/locale';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import allVersionsMixin from '../../mixins/all_versions';
import createNoteMutation from '../../graphql/mutations/createNote.mutation.graphql';
import createNoteMutation from '../../graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '../../graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import getDesignQuery from '../../graphql/queries/getDesign.query.graphql';
import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
import activeDiscussionQuery from '../../graphql/queries/active_discussion.query.graphql';
import DesignNote from './design_note.vue';
import DesignReplyForm from './design_reply_form.vue';
......
......@@ -6,7 +6,7 @@ import timeagoMixin from '~/vue_shared/mixins/timeago';
import Pagination from './pagination.vue';
import DeleteButton from '../delete_button.vue';
import permissionsQuery from '../../graphql/queries/design_permissions.query.graphql';
import appDataQuery from '../../graphql/queries/appData.query.graphql';
import appDataQuery from '../../graphql/queries/app_data.query.graphql';
import { DESIGNS_ROUTE_NAME } from '../../router/constants';
export default {
......
<script>
import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import createFlash from '~/flash';
import uploadDesignMutation from '../../graphql/mutations/uploadDesign.mutation.graphql';
import uploadDesignMutation from '../../graphql/mutations/upload_design.mutation.graphql';
import { UPLOAD_DESIGN_INVALID_FILETYPE_ERROR } from '../../utils/error_messages';
import { isValidDesignFile } from '../../utils/design_management_utils';
import { VALID_DATA_TRANSFER_TYPE, VALID_DESIGN_FILE_MIMETYPE } from '../../constants';
......
#import "./designNote.fragment.graphql"
#import "./designList.fragment.graphql"
#import "./diffRefs.fragment.graphql"
#import "./design_note.fragment.graphql"
#import "./design_list.fragment.graphql"
#import "./diff_refs.fragment.graphql"
#import "./discussion_resolved_status.fragment.graphql"
fragment DesignItem on Design {
......
#import "./diffRefs.fragment.graphql"
#import "./diff_refs.fragment.graphql"
#import "~/graphql_shared/fragments/author.fragment.graphql"
#import "./note_permissions.fragment.graphql"
......
#import "../fragments/designNote.fragment.graphql"
#import "../fragments/design_note.fragment.graphql"
mutation createImageDiffNote($input: CreateImageDiffNoteInput!) {
createImageDiffNote(input: $input) {
......
#import "../fragments/designNote.fragment.graphql"
#import "../fragments/design_note.fragment.graphql"
mutation createNote($input: CreateNoteInput!) {
createNote(input: $input) {
......
#import "../fragments/designNote.fragment.graphql"
#import "../fragments/design_note.fragment.graphql"
#import "../fragments/discussion_resolved_status.fragment.graphql"
mutation toggleResolveDiscussion($id: ID!, $resolve: Boolean!) {
......
#import "../fragments/designNote.fragment.graphql"
#import "../fragments/design_note.fragment.graphql"
mutation updateImageDiffNote($input: UpdateImageDiffNoteInput!) {
updateImageDiffNote(input: $input) {
......
#import "../fragments/designNote.fragment.graphql"
#import "../fragments/design_note.fragment.graphql"
mutation updateNote($input: UpdateNoteInput!) {
updateNote(input: $input) {
......
#import "../fragments/designList.fragment.graphql"
#import "../fragments/design_list.fragment.graphql"
#import "../fragments/version.fragment.graphql"
query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) {
......
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import appDataQuery from '../graphql/queries/appData.query.graphql';
import appDataQuery from '../graphql/queries/app_data.query.graphql';
import { findVersionId } from '../utils/design_management_utils';
export default {
......
......@@ -11,10 +11,10 @@ import DesignScaler from '../../components/design_scaler.vue';
import DesignPresentation from '../../components/design_presentation.vue';
import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
import DesignSidebar from '../../components/design_sidebar.vue';
import getDesignQuery from '../../graphql/queries/getDesign.query.graphql';
import appDataQuery from '../../graphql/queries/appData.query.graphql';
import createImageDiffNoteMutation from '../../graphql/mutations/createImageDiffNote.mutation.graphql';
import updateImageDiffNoteMutation from '../../graphql/mutations/updateImageDiffNote.mutation.graphql';
import getDesignQuery from '../../graphql/queries/get_design.query.graphql';
import appDataQuery from '../../graphql/queries/app_data.query.graphql';
import createImageDiffNoteMutation from '../../graphql/mutations/create_image_diff_note.mutation.graphql';
import updateImageDiffNoteMutation from '../../graphql/mutations/update_image_diff_note.mutation.graphql';
import updateActiveDiscussionMutation from '../../graphql/mutations/update_active_discussion.mutation.graphql';
import {
extractDiscussions,
......
......@@ -8,7 +8,7 @@ import Design from '../components/list/item.vue';
import DesignDestroyer from '../components/design_destroyer.vue';
import DesignVersionDropdown from '../components/upload/design_version_dropdown.vue';
import DesignDropzone from '../components/upload/design_dropzone.vue';
import uploadDesignMutation from '../graphql/mutations/uploadDesign.mutation.graphql';
import uploadDesignMutation from '../graphql/mutations/upload_design.mutation.graphql';
import permissionsQuery from '../graphql/queries/design_permissions.query.graphql';
import getDesignListQuery from '../graphql/queries/get_design_list.query.graphql';
import allDesignsMixin from '../mixins/all_designs';
......
......@@ -17,7 +17,7 @@ import {
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import NoteableWarning from '../../vue_shared/components/notes/noteable_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
......@@ -28,8 +28,7 @@ import issuableStateMixin from '../mixins/issuable_state';
export default {
name: 'CommentForm',
components: {
issueWarning,
epicWarning: () => import('ee_component/vue_shared/components/epic/epic_warning.vue'),
NoteableWarning,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
......@@ -350,14 +349,15 @@ export default {
<form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form">
<div class="error-alert"></div>
<issue-warning
v-if="hasWarning(getNoteableData) && isIssueType"
<noteable-warning
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
:locked-issue-docs-path="lockedIssueDocsPath"
:confidential-issue-docs-path="confidentialIssueDocsPath"
:noteable-type="noteableType"
:locked-noteable-docs-path="lockedIssueDocsPath"
:confidential-noteable-docs-path="confidentialIssueDocsPath"
/>
<epic-warning :is-confidential="isConfidential(getNoteableData)" />
<markdown-field
ref="markdownField"
:is-submitting="isSubmitting"
......@@ -374,16 +374,14 @@ export default {
dir="auto"
:disabled="isSubmitting"
name="note[note]"
class="note-textarea js-vue-comment-form js-note-text
js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()"
@keydown.ctrl.enter="handleSave()"
>
</textarea>
></textarea>
</markdown-field>
<gl-alert
v-if="isToggleBlockedIssueWarning"
......@@ -417,13 +415,11 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
</gl-alert>
<div class="note-form-actions">
<div
class="btn-group
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn-group append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<button
:disabled="isSubmitButtonDisabled"
class="btn btn-success js-comment-button js-comment-submit-button
qa-comment-button"
class="btn btn-success js-comment-button js-comment-submit-button qa-comment-button"
type="submit"
:data-track-label="trackingLabel"
data-track-event="click_button"
......@@ -440,7 +436,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
data-toggle="dropdown"
:aria-label="__('Open comment type dropdown')"
>
<i aria-hidden="true" class="fa fa-caret-down toggle-icon"> </i>
<i aria-hidden="true" class="fa fa-caret-down toggle-icon"></i>
</button>
<ul class="note-type-dropdown dropdown-open-top dropdown-menu">
......@@ -450,7 +446,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-transparent"
@click.prevent="setNoteType('comment')"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<i aria-hidden="true" class="fa fa-check icon"></i>
<div class="description">
<strong>{{ __('Comment') }}</strong>
<p>
......@@ -470,7 +466,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-transparent qa-discussion-option"
@click.prevent="setNoteType('discussion')"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<i aria-hidden="true" class="fa fa-check icon"></i>
<div class="description">
<strong>{{ __('Start thread') }}</strong>
<p>{{ startDiscussionDescription }}</p>
......
......@@ -2,7 +2,7 @@
import { mapGetters, mapActions, mapState } from 'vuex';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import NoteableWarning from '../../vue_shared/components/notes/noteable_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
......@@ -12,7 +12,7 @@ import { getDraft, updateDraft } from '~/lib/utils/autosave';
export default {
name: 'NoteForm',
components: {
issueWarning,
NoteableWarning,
markdownField,
},
mixins: [issuableStateMixin, resolvable],
......@@ -303,12 +303,12 @@ export default {
></div>
<div class="flash-container timeline-content"></div>
<form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form">
<issue-warning
<noteable-warning
v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)"
:locked-issue-docs-path="lockedIssueDocsPath"
:confidential-issue-docs-path="confidentialIssueDocsPath"
:locked-noteable-docs-path="lockedIssueDocsPath"
:confidential-noteable-docs-path="confidentialIssueDocsPath"
/>
<markdown-field
......
......@@ -8,6 +8,18 @@ function buildDocsLinkStart(path) {
return `<a href="${escape(path)}" target="_blank" rel="noopener noreferrer">`;
}
const NoteableType = {
Issue: 'issue',
Epic: 'epic',
MergeRequest: 'merge_request',
};
const NoteableTypeText = {
[NoteableType.Issue]: __('issue'),
[NoteableType.Epic]: __('epic'),
[NoteableType.MergeRequest]: __('merge request'),
};
export default {
components: {
icon,
......@@ -24,12 +36,17 @@ export default {
default: false,
required: false,
},
lockedIssueDocsPath: {
noteableType: {
type: String,
required: false,
default: NoteableType.Issue,
},
lockedNoteableDocsPath: {
type: String,
required: false,
default: '',
},
confidentialIssueDocsPath: {
confidentialNoteableDocsPath: {
type: String,
required: false,
default: '',
......@@ -45,19 +62,33 @@ export default {
isLockedAndConfidential() {
return this.isConfidential && this.isLocked;
},
noteableTypeText() {
return NoteableTypeText[this.noteableType];
},
confidentialAndLockedDiscussionText() {
return sprintf(
__(
'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
'This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
),
{
confidentialLinkStart: buildDocsLinkStart(this.confidentialIssueDocsPath),
lockedLinkStart: buildDocsLinkStart(this.lockedIssueDocsPath),
noteableTypeText: this.noteableTypeText,
confidentialLinkStart: buildDocsLinkStart(this.confidentialNoteableDocsPath),
lockedLinkStart: buildDocsLinkStart(this.lockedNoteableDocsPath),
linkEnd: '</a>',
},
false,
);
},
confidentialContextText() {
return sprintf(__('This is a confidential %{noteableTypeText}.'), {
noteableTypeText: this.noteableTypeText,
});
},
lockedContextText() {
return sprintf(__('This %{noteableTypeText} is locked.'), {
noteableTypeText: this.noteableTypeText,
});
},
},
};
</script>
......@@ -73,19 +104,15 @@ export default {
</span>
<span v-else-if="isConfidential" ref="confidential">
{{ __('This is a confidential issue.') }}
{{ confidentialContextText }}
{{ __('People without permission will never get a notification.') }}
<gl-link :href="confidentialIssueDocsPath" target="_blank">
{{ __('Learn more') }}
</gl-link>
<gl-link :href="confidentialNoteableDocsPath" target="_blank">{{ __('Learn more') }}</gl-link>
</span>
<span v-else-if="isLocked" ref="locked">
{{ __('This issue is locked.') }}
{{ lockedContextText }}
{{ __('Only project members can comment.') }}
<gl-link :href="lockedIssueDocsPath" target="_blank">
{{ __('Learn more') }}
</gl-link>
<gl-link :href="lockedNoteableDocsPath" target="_blank">{{ __('Learn more') }}</gl-link>
</span>
</div>
</template>
......@@ -40,14 +40,18 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
params.require(:personal_access_token).permit(:name, :expires_at, scopes: [])
end
# rubocop: disable CodeReuse/ActiveRecord
def set_index_vars
@scopes = Gitlab::Auth.available_scopes_for(current_user)
@inactive_personal_access_tokens = finder(state: 'inactive').execute
@active_personal_access_tokens = finder(state: 'active').execute.order(:expires_at)
@active_personal_access_tokens = active_personal_access_tokens
@new_personal_access_token = PersonalAccessToken.redis_getdel(current_user.id)
end
# rubocop: enable CodeReuse/ActiveRecord
def active_personal_access_tokens
finder(state: 'active', sort: 'expires_at_asc').execute
end
end
Profiles::PersonalAccessTokensController.prepend_if_ee('EE::Profiles::PersonalAccessTokensController')
......@@ -51,6 +51,8 @@ class PersonalAccessTokensFinder
tokens.active
when 'inactive'
tokens.inactive
when 'active_or_expired'
tokens.not_revoked.expired.or(tokens.active)
else
tokens
end
......
......@@ -47,11 +47,6 @@ module Types
null: false,
description: 'Fields related to design management'
field :user, Types::UserType,
null: true,
description: 'Find a user',
resolver: Resolvers::UserResolver
field :users, Types::UserType.connection_type,
null: true,
description: 'Find users',
......@@ -61,8 +56,9 @@ module Types
description: 'Text to echo back',
resolver: Resolvers::EchoResolver
field :user, Types::UserType, null: true,
description: 'Find a user on this instance',
field :user, Types::UserType,
null: true,
description: 'Find a user',
resolver: Resolvers::UserResolver
def design_management
......
......@@ -1374,9 +1374,9 @@ class MergeRequest < ApplicationRecord
# TODO: consider renaming this as with exposed artifacts we generate reports,
# not always compare
# issue: https://gitlab.com/gitlab-org/gitlab/issues/34224
def compare_reports(service_class, current_user = nil)
with_reactive_cache(service_class.name, current_user&.id) do |data|
unless service_class.new(project, current_user, id: id)
def compare_reports(service_class, current_user = nil, report_type = nil )
with_reactive_cache(service_class.name, current_user&.id, report_type) do |data|
unless service_class.new(project, current_user, id: id, report_type: report_type)
.latest?(base_pipeline, actual_head_pipeline, data)
raise InvalidateReactiveCache
end
......@@ -1385,7 +1385,7 @@ class MergeRequest < ApplicationRecord
end || { status: :parsing }
end
def calculate_reactive_cache(identifier, current_user_id = nil, *args)
def calculate_reactive_cache(identifier, current_user_id = nil, report_type = nil, *args)
service_class = identifier.constantize
# TODO: the type check should change to something that includes exposed artifacts service
......@@ -1393,7 +1393,7 @@ class MergeRequest < ApplicationRecord
raise NameError, service_class unless service_class < Ci::CompareReportsBaseService
current_user = User.find_by(id: current_user_id)
service_class.new(project, current_user, id: id).execute(base_pipeline, actual_head_pipeline)
service_class.new(project, current_user, id: id, report_type: report_type).execute(base_pipeline, actual_head_pipeline)
end
def all_commits
......
......@@ -22,6 +22,8 @@ class PersonalAccessToken < ApplicationRecord
scope :inactive, -> { where("revoked = true OR expires_at < NOW()") }
scope :with_impersonation, -> { where(impersonation: true) }
scope :without_impersonation, -> { where(impersonation: false) }
scope :revoked, -> { where(revoked: true) }
scope :not_revoked, -> { where(revoked: [false, nil]) }
scope :for_user, -> (user) { where(user: user) }
scope :preload_users, -> { preload(:user) }
scope :order_expires_at_asc, -> { reorder(expires_at: :asc) }
......
......@@ -26,8 +26,12 @@
%td= token.created_at.to_date.to_s(:medium)
%td
- if token.expires?
%span{ class: ('text-warning' if token.expires_soon?) }
= _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
- if token.expires_at.past? || token.expires_at.today?
%span{ class: 'text-danger has-tooltip', title: _('Expiration not enforced') }
= _('Expired')
- else
%span{ class: ('text-warning' if token.expires_soon?) }
= _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
- else
%span.token-never-expires-label= _('Never')
%td= token.scopes.present? ? token.scopes.join(', ') : _('<no scopes selected>')
......
---
title: Use Keys::DestroyService for deleting an SSH key when a user deletes a key via the API
merge_request: 34718
author: Rajendra Kadam
type: fixed
......@@ -9942,7 +9942,7 @@ type Query {
): SnippetConnection
"""
Find a user on this instance
Find a user
"""
user(
"""
......
......@@ -29206,7 +29206,7 @@
},
{
"name": "user",
"description": "Find a user on this instance",
"description": "Find a user",
"args": [
{
"name": "id",
......@@ -107,8 +107,8 @@ You can add a command to your `.gitlab-ci.yml` file to
| `CI_PROJECT_VISIBILITY` | 10.3 | all | The project visibility (internal, private, public) |
| `CI_REGISTRY` | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry. This variable will include a `:port` value if one has been specified in the registry configuration. |
| `CI_REGISTRY_IMAGE` | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project |
| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to use to push containers to the GitLab Container Registry |
| `CI_REGISTRY_USER` | 9.0 | all | The username to use to push containers to the GitLab Container Registry |
| `CI_REGISTRY_PASSWORD` | 9.0 | all | The password to use to push containers to the GitLab Container Registry, for the current project. |
| `CI_REGISTRY_USER` | 9.0 | all | The username to use to push containers to the GitLab Container Registry, for the current project. |
| `CI_REPOSITORY_URL` | 9.0 | all | The URL to clone the Git repository |
| `CI_RUNNER_DESCRIPTION` | 8.10 | 0.5 | The description of the runner as saved in GitLab |
| `CI_RUNNER_EXECUTABLE_ARCH` | all | 10.6 | The OS/architecture of the GitLab Runner executable (note that this is not necessarily the same as the environment of the executor) |
......
......@@ -14,12 +14,12 @@ following format:
---
title: "Change[log]s"
merge_request: 1972
author: Black Sabbath
author: Black Sabbath @bsabbath
type: added
```
The `merge_request` value is a reference to a merge request that adds this
entry, and the `author` key is used to give attribution to community
entry, and the `author` key (format: `<full name> <GitLab username>`) is used to give attribution to community
contributors. **Both are optional**.
The `type` field maps the category of the change,
valid options are: added, fixed, changed, deprecated, removed, security, performance, other. **Type field is mandatory**.
......
......@@ -810,6 +810,14 @@ class BuildMetadata
end
```
When using a `JSONB` column, use the [JsonSchemaValidator](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/validators/json_schema_validator.rb) to keep control of the data being inserted over time.
```ruby
class BuildMetadata
validates: :config_options, json_schema: { filename: 'build_metadata_config_option' }
end
```
## Testing
See the [Testing Rails migrations](testing_guide/testing_migrations_guide.md) style guide.
......
......@@ -63,7 +63,10 @@ Dark theme currently only works with the 'Dark' syntax highlighting.
NOTE: **Note:**
GitLab uses the [rouge Ruby library](http://rouge.jneen.net/ "Rouge website")
for syntax highlighting. For a list of supported languages visit the rouge website.
for syntax highlighting outside of any Editor context. The WebIDE (like Snippets)
uses [Monaco Editor](https://microsoft.github.io/monaco-editor/) and it's provided [Monarch](https://microsoft.github.io/monaco-editor/monarch.html) library for
syntax highlighting. For a list of supported languages, visit the documentation of
the respective libraries.
Changing this setting allows you to customize the color theme when viewing any
syntax highlighted code on GitLab.
......@@ -80,8 +83,9 @@ The default syntax theme is White, and you can choose among 5 different themes:
[Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2389) in 13.0, the theme
you choose also applies to the [Web IDE](../project/web_ide/index.md)'s code editor and [Snippets](../snippets.md).
The themes are available only in the Web IDE file editor, except for the [dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/209808),
which applies to the entire Web IDE screen.
The themes are available only in the Web IDE file editor, except for the [dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/209808) and
the [solarized dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/219228),
which apply to the entire Web IDE screen.
## Behavior
......
# Syntax Highlighting
GitLab provides syntax highlighting on all files and snippets through the [Rouge](https://rubygems.org/gems/rouge) rubygem. It will try to guess what language to use based on the file extension, which most of the time is sufficient.
GitLab provides syntax highlighting on all files through the [Rouge](https://rubygems.org/gems/rouge) Ruby gem. It will try to guess what language to use based on the file extension, which most of the time is sufficient.
NOTE: **Note:**
The [Web IDE](web_ide/index.md) and [Snippets](../snippets.md) use [Monaco Editor](https://microsoft.github.io/monaco-editor/)
for text editing, which internally uses the [Monarch](https://microsoft.github.io/monaco-editor/monarch.html)
library for syntax highlighting.
If GitLab is guessing wrong, you can override its choice of language using the `gitlab-language` attribute in `.gitattributes`. For example, if you are working in a Prolog project and using the `.pl` file extension (which would normally be highlighted as Perl), you can add the following to your `.gitattributes` file:
......@@ -27,3 +32,6 @@ To disable highlighting entirely, use `gitlab-language=text`. Lots more fun shen
```
Please note that these configurations will only take effect when the `.gitattributes` file is in your default branch (usually `master`).
NOTE: **Note:**
The Web IDE does not support `.gitattribute` files, but it's [planned for a future release](https://gitlab.com/gitlab-org/gitlab/-/issues/22014).
......@@ -184,17 +184,23 @@ so that everyone involved can participate in the discussion.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13049) in GitLab 13.1.
Discussion threads can be resolved on Designs. You can mark a thread as resolved
or unresolved by clicking the **Resolve thread** icon at the first comment of the
discussion.
Discussion threads can be resolved on Designs.
![Resolve thread icon](img/resolve_design-discussion_icon_v13_1.png)
There are two ways to resolve/unresolve a Design thread:
Pinned comments can also be resolved or unresolved in their threads.
When replying to a comment, you will see a checkbox that you can click in order to resolve or unresolve
the thread once published.
1. You can mark a thread as resolved or unresolved by clicking the checkmark icon for **Resolve thread** in the top-right corner of the first comment of the discussion:
![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
![Resolve thread icon](img/resolve_design-discussion_icon_v13_1.png)
1. Design threads can also be resolved or unresolved in their threads by using a checkbox.
When replying to a comment, you will see a checkbox that you can click in order to resolve or unresolve
the thread once published:
![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
Note that your resolved comment pins will disappear from the Design to free up space for new discussions.
However, if you need to revisit or find a resolved discussion, all of your resolved threads will be
available in the **Resolved Comment** area at the bottom of the right sidebar.
## Referring to designs in Markdown
......
......@@ -38,24 +38,29 @@ The Web IDE currently provides:
Because the Web IDE is based on the [Monaco Editor](https://microsoft.github.io/monaco-editor/),
you can find a more complete list of supported languages in the
[Monaco languages](https://github.com/Microsoft/monaco-languages) repository.
[Monaco languages](https://github.com/Microsoft/monaco-languages) repository. Under the hood,
Monaco uses the [Monarch](https://microsoft.github.io/monaco-editor/monarch.html) library for syntax highlighting.
If you are missing Syntax Highlighting support for any language, we prepared a short guide on how to [add support for a missing language Syntax Highlighting.](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/ide/lib/languages/README.md)
NOTE: **Note:**
Single file editing is based on the [Ace Editor](https://ace.c9.io).
### Themes
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2389) in GitLab 13.0.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2389) in GitLab in 13.0.
> - Full Solarized Dark Theme [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/219228) in GitLab 13.1.
All the themes GitLab supports for syntax highlighting are added to the Web IDE's code editor.
You can pick a theme from your [profile preferences](../../profile/preferences.md).
The themes are available only in the Web IDE file editor, except for the [dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/209808),
which applies to the entire Web IDE screen.
The themes are available only in the Web IDE file editor, except for the [dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/209808) and
the [solarized dark theme](https://gitlab.com/gitlab-org/gitlab/-/issues/219228),
which apply to the entire Web IDE screen.
| Solarized Light Theme | Dark Theme |
|---------------------------------------------------------------|-----------------------------------------|
| ![Solarized Light Theme](img/solarized_light_theme_v13.0.png) | ![Dark Theme](img/dark_theme_v13.0.png) |
| Solarized Light Theme | Solarized Dark Theme | Dark Theme |
|---------------------------------------------------------------|-------------------------------------------------------------|-----------------------------------------|
| ![Solarized Light Theme](img/solarized_light_theme_v13_0.png) | ![Solarized Dark Theme](img/solarized_dark_theme_v13_1.png) | ![Dark Theme](img/dark_theme_v13_0.png) |
## Configure the Web IDE
......
......@@ -750,7 +750,10 @@ module API
key = current_user.keys.find_by(id: params[:key_id])
not_found!('Key') unless key
destroy_conditionally!(key)
destroy_conditionally!(key) do |key|
destroy_service = ::Keys::DestroyService.new(current_user)
destroy_service.execute(key)
end
end
# rubocop: enable CodeReuse/ActiveRecord
......
......@@ -16,7 +16,7 @@ module Gitlab
end
# Namespace
class Namespace < ApplicationRecord
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
self.inheritance_column = :_type_disabled
......
......@@ -15,7 +15,7 @@ module Gitlab
allow_failure type when start_in artifacts cache
dependencies before_script needs after_script
environment coverage retry parallel interruptible timeout
resource_group release].freeze
resource_group release secrets].freeze
REQUIRED_BY_NEEDS = %i[stage].freeze
......@@ -191,3 +191,5 @@ module Gitlab
end
end
end
::Gitlab::Ci::Config::Entry::Job.prepend_if_ee('::EE::Gitlab::Ci::Config::Entry::Job')
......@@ -44,3 +44,5 @@ module Gitlab
end
end
end
::Gitlab::Ci::Features.prepend_if_ee('::EE::Gitlab::Ci::Features')
......@@ -9257,6 +9257,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
msgid "Expiration not enforced"
msgstr ""
msgid "Expiration policy for the Container Registry is a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
msgstr ""
......@@ -22834,6 +22837,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr ""
msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
msgstr ""
msgid "This %{noteableTypeText} is locked."
msgstr ""
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr ""
......@@ -22981,10 +22990,7 @@ msgstr ""
msgid "This is a Work in Progress"
msgstr ""
msgid "This is a confidential epic."
msgstr ""
msgid "This is a confidential issue."
msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a delayed job to run in %{remainingTime}"
......@@ -23011,9 +23017,6 @@ msgstr ""
msgid "This is your current session"
msgstr ""
msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
msgstr ""
msgid "This issue is confidential"
msgstr ""
......@@ -23023,9 +23026,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic"
msgstr ""
msgid "This issue is locked."
msgstr ""
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr ""
......@@ -26849,6 +26849,9 @@ msgstr ""
msgid "entries cannot contain HTML tags"
msgstr ""
msgid "epic"
msgstr ""
msgid "error"
msgstr ""
......
......@@ -8,6 +8,7 @@ RSpec.describe 'Database schema' do
let(:connection) { ActiveRecord::Base.connection }
let(:tables) { connection.tables }
let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb }
# Use if you are certain that this column should not have a foreign key
# EE: edit the ee/spec/db/schema_support.rb
......@@ -169,8 +170,54 @@ RSpec.describe 'Database schema' do
end
end
# These pre-existing columns does not use a schema validation yet
IGNORED_JSONB_COLUMNS = {
"ApplicationSetting" => %w[repository_storages_weighted],
"AlertManagement::Alert" => %w[payload],
"Ci::BuildMetadata" => %w[config_options config_variables],
"Geo::Event" => %w[payload],
"GeoNodeStatus" => %w[status],
"Operations::FeatureFlagScope" => %w[strategies],
"Operations::FeatureFlags::Strategy" => %w[parameters],
"Packages::Composer::Metadatum" => %w[composer_json],
"Releases::Evidence" => %w[summary]
}.freeze
# We are skipping GEO models for now as it adds up complexity
describe 'for jsonb columns' do
it 'uses json schema validator' do
columns_name_with_jsonb.each do |hash|
next if models_by_table_name[hash["table_name"]].nil?
models_by_table_name[hash["table_name"]].each do |model|
jsonb_columns = [hash["column_name"]] - ignored_jsonb_columns(model.name)
expect(model).to validate_jsonb_schema(jsonb_columns)
end
end
end
end
private
def retrieve_columns_name_with_jsonb
sql = <<~SQL
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_catalog = '#{ApplicationRecord.connection_config[:database]}'
AND table_schema = 'public'
AND table_name NOT LIKE 'pg_%'
AND data_type = 'jsonb'
ORDER BY table_name, column_name, data_type
SQL
ApplicationRecord.connection.select_all(sql).to_a
end
def models_by_table_name
@models_by_table_name ||= ApplicationRecord.descendants.reject(&:abstract_class).group_by(&:table_name)
end
def ignored_fk_columns(column)
IGNORED_FK_COLUMNS.fetch(column, [])
end
......@@ -178,4 +225,8 @@ RSpec.describe 'Database schema' do
def ignored_limit_enums(model)
IGNORED_LIMIT_ENUMS.fetch(model, [])
end
def ignored_jsonb_columns(model)
IGNORED_JSONB_COLUMNS.fetch(model, [])
end
end
......@@ -218,6 +218,24 @@ RSpec.describe PersonalAccessTokensFinder do
end
end
describe 'with active or expired state' do
before do
params[:state] = 'active_or_expired'
end
it 'includes active tokens' do
is_expected.to include(active_personal_access_token, active_impersonation_token)
end
it 'includes expired tokens' do
is_expected.to include(expired_personal_access_token, expired_impersonation_token)
end
it 'does not include revoked tokens' do
is_expected.not_to include(revoked_personal_access_token, revoked_impersonation_token)
end
end
describe 'with id' do
subject { finder(params).find_by_id(active_personal_access_token.id) }
......
......@@ -4,7 +4,7 @@ import notes from '../../mock_data/notes';
import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
import createNoteMutation from '~/design_management/graphql/mutations/createNote.mutation.graphql';
import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '~/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue';
......
......@@ -6,7 +6,7 @@ import createFlash from '~/flash';
import DesignIndex from '~/design_management/pages/design/index.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
import DesignPresentation from '~/design_management/components/design_presentation.vue';
import createImageDiffNoteMutation from '~/design_management/graphql/mutations/createImageDiffNote.mutation.graphql';
import createImageDiffNoteMutation from '~/design_management/graphql/mutations/create_image_diff_note.mutation.graphql';
import design from '../../mock_data/design';
import mockResponseWithDesigns from '../../mock_data/designs';
import mockResponseNoDesigns from '../../mock_data/no_designs';
......
......@@ -3,7 +3,7 @@ import { ApolloMutation } from 'vue-apollo';
import VueRouter from 'vue-router';
import { GlEmptyState } from '@gitlab/ui';
import Index from '~/design_management/pages/index.vue';
import uploadDesignQuery from '~/design_management/graphql/mutations/uploadDesign.mutation.graphql';
import uploadDesignQuery from '~/design_management/graphql/mutations/upload_design.mutation.graphql';
import DesignDestroyer from '~/design_management/components/design_destroyer.vue';
import DesignDropzone from '~/design_management/components/upload/design_dropzone.vue';
import DeleteButton from '~/design_management/components/delete_button.vue';
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Issue Warning Component when issue is confidential but not locked renders information about confidential issue 1`] = `
exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
<span>
This issue is locked.
Only project members can comment.
<gl-link-stub
href="locked-path"
target="_blank"
>
Learn more
</gl-link-stub>
</span>
`;
exports[`Issue Warning Component when noteable is confidential but not locked renders information about confidential issue 1`] = `
<span>
This is a confidential issue.
......@@ -10,14 +25,12 @@ exports[`Issue Warning Component when issue is confidential but not locked rende
href="confidential-path"
target="_blank"
>
Learn more
Learn more
</gl-link-stub>
</span>
`;
exports[`Issue Warning Component when issue is locked and confidential renders information about locked and confidential issue 1`] = `
exports[`Issue Warning Component when noteable is locked and confidential renders information about locked and confidential noteable 1`] = `
<span>
<span>
This issue is
......@@ -43,20 +56,3 @@ exports[`Issue Warning Component when issue is locked and confidential renders i
</span>
`;
exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
<span>
This issue is locked.
Only project members can comment.
<gl-link-stub
href="locked-path"
target="_blank"
>
Learn more
</gl-link-stub>
</span>
`;
import { shallowMount } from '@vue/test-utils';
import IssueWarning from '~/vue_shared/components/issue/issue_warning.vue';
import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
import Icon from '~/vue_shared/components/icon.vue';
describe('Issue Warning Component', () => {
let wrapper;
const findIcon = () => wrapper.find(Icon);
const findLockedBlock = () => wrapper.find({ ref: 'locked' });
const findConfidentialBlock = () => wrapper.find({ ref: 'confidential' });
const findLockedAndConfidentialBlock = () => wrapper.find({ ref: 'lockedAndConfidential' });
const findIcon = (w = wrapper) => w.find(Icon);
const findLockedBlock = (w = wrapper) => w.find({ ref: 'locked' });
const findConfidentialBlock = (w = wrapper) => w.find({ ref: 'confidential' });
const findLockedAndConfidentialBlock = (w = wrapper) => w.find({ ref: 'lockedAndConfidential' });
const createComponent = props => {
wrapper = shallowMount(IssueWarning, {
const createComponent = props =>
shallowMount(NoteableWarning, {
propsData: {
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
describe('when issue is locked but not confidential', () => {
beforeEach(() => {
createComponent({
wrapper = createComponent({
isLocked: true,
lockedIssueDocsPath: 'locked-path',
lockedNoteableDocsPath: 'locked-path',
isConfidential: false,
});
});
......@@ -50,12 +51,12 @@ describe('Issue Warning Component', () => {
});
});
describe('when issue is confidential but not locked', () => {
describe('when noteable is confidential but not locked', () => {
beforeEach(() => {
createComponent({
wrapper = createComponent({
isLocked: false,
isConfidential: true,
confidentialIssueDocsPath: 'confidential-path',
confidentialNoteableDocsPath: 'confidential-path',
});
});
......@@ -68,24 +69,24 @@ describe('Issue Warning Component', () => {
expect(wrapper.find(Icon).exists()).toBe(true);
});
it('does not render information about locked issue', () => {
it('does not render information about locked noteable', () => {
expect(findLockedBlock().exists()).toBe(false);
});
it('does not render information about locked and confidential issue', () => {
it('does not render information about locked and confidential noteable', () => {
expect(findLockedAndConfidentialBlock().exists()).toBe(false);
});
});
describe('when issue is locked and confidential', () => {
describe('when noteable is locked and confidential', () => {
beforeEach(() => {
createComponent({
wrapper = createComponent({
isLocked: true,
isConfidential: true,
});
});
it('renders information about locked and confidential issue', () => {
it('renders information about locked and confidential noteable', () => {
expect(findLockedAndConfidentialBlock().exists()).toBe(true);
expect(findLockedAndConfidentialBlock().element).toMatchSnapshot();
});
......@@ -94,12 +95,99 @@ describe('Issue Warning Component', () => {
expect(wrapper.find(Icon).exists()).toBe(false);
});
it('does not render information about locked issue', () => {
it('does not render information about locked noteable', () => {
expect(findLockedBlock().exists()).toBe(false);
});
it('does not render information about confidential issue', () => {
it('does not render information about confidential noteable', () => {
expect(findConfidentialBlock().exists()).toBe(false);
});
});
describe('when noteableType prop is defined', () => {
let wrapperLocked;
let wrapperConfidential;
let wrapperLockedAndConfidential;
beforeEach(() => {
wrapperLocked = createComponent({
isLocked: true,
isConfidential: false,
});
wrapperConfidential = createComponent({
isLocked: false,
isConfidential: true,
});
wrapperLockedAndConfidential = createComponent({
isLocked: true,
isConfidential: true,
});
});
afterEach(() => {
wrapperLocked.destroy();
wrapperConfidential.destroy();
wrapperLockedAndConfidential.destroy();
});
it('renders confidential & locked messages with noteable "issue"', () => {
expect(findLockedBlock(wrapperLocked).text()).toContain('This issue is locked.');
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential issue.',
);
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This issue is confidential and locked.',
);
});
it('renders confidential & locked messages with noteable "epic"', async () => {
wrapperLocked.setProps({
noteableType: 'epic',
});
wrapperConfidential.setProps({
noteableType: 'epic',
});
wrapperLockedAndConfidential.setProps({
noteableType: 'epic',
});
await wrapperLocked.vm.$nextTick();
expect(findLockedBlock(wrapperLocked).text()).toContain('This epic is locked.');
await wrapperConfidential.vm.$nextTick();
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential epic.',
);
await wrapperLockedAndConfidential.vm.$nextTick();
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This epic is confidential and locked.',
);
});
it('renders confidential & locked messages with noteable "merge request"', async () => {
wrapperLocked.setProps({
noteableType: 'merge_request',
});
wrapperConfidential.setProps({
noteableType: 'merge_request',
});
wrapperLockedAndConfidential.setProps({
noteableType: 'merge_request',
});
await wrapperLocked.vm.$nextTick();
expect(findLockedBlock(wrapperLocked).text()).toContain('This merge request is locked.');
await wrapperConfidential.vm.$nextTick();
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential merge request.',
);
await wrapperLockedAndConfidential.vm.$nextTick();
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This merge request is confidential and locked.',
);
});
});
});
......@@ -33,7 +33,7 @@ describe Gitlab::Ci::Config::Entry::Job do
inherit]
end
it { is_expected.to match_array result }
it { is_expected.to include(*result) }
end
end
......
......@@ -187,6 +187,20 @@ describe PersonalAccessToken do
expect(described_class.without_impersonation).to contain_exactly(personal_access_token)
end
end
describe 'revoke scopes' do
let_it_be(:revoked_token) { create(:personal_access_token, :revoked) }
let_it_be(:non_revoked_token) { create(:personal_access_token, revoked: false) }
let_it_be(:non_revoked_token2) { create(:personal_access_token, revoked: nil) }
describe '.revoked' do
it { expect(described_class.revoked).to contain_exactly(revoked_token) }
end
describe '.not_revoked' do
it { expect(described_class.not_revoked).to contain_exactly(non_revoked_token, non_revoked_token2) }
end
end
end
describe '.simple_sorts' do
......
# frozen_string_literal: true
RSpec::Matchers.define :validate_jsonb_schema do |jsonb_columns|
match do |actual|
next true if jsonb_columns.blank?
expect(actual.validators).to include(a_kind_of(JsonSchemaValidator))
end
failure_message do
<<~FAILURE_MESSAGE
Expected #{actual.name} to validate the schema of #{jsonb_columns.join(', ')}.
Use JsonSchemaValidator in your model when using a jsonb column.
See doc/development/migration_style_guide.html#storing-json-in-database for more information.
To fix this, please add `validates :#{jsonb_columns.first}, json_schema: { filename: "filename" }` in your model file, for example:
class #{actual.name}
validates :#{jsonb_columns.first}, json_schema: { filename: "filename" }
end
FAILURE_MESSAGE
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册