diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-26 12:09:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-26 12:09:53 +0300 |
commit | 0ccabeb3f62c5fbc81f52cc16fa654404bb87874 (patch) | |
tree | 27c81cfa9d498fa0b604acaa9c4f5400743f83fd /app/assets | |
parent | 6819cb95c9c0aa63fce1d246026978df5cac9e44 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
12 files changed, 328 insertions, 151 deletions
diff --git a/app/assets/javascripts/actioncable_link.js b/app/assets/javascripts/actioncable_link.js new file mode 100644 index 00000000000..4c642db2f3c --- /dev/null +++ b/app/assets/javascripts/actioncable_link.js @@ -0,0 +1,40 @@ +import { ApolloLink, Observable } from 'apollo-link'; +import { print } from 'graphql'; +import cable from '~/actioncable_consumer'; +import { uuids } from '~/diffs/utils/uuids'; + +export default class ActionCableLink extends ApolloLink { + // eslint-disable-next-line class-methods-use-this + request(operation) { + return new Observable((observer) => { + const subscription = cable.subscriptions.create( + { + channel: 'GraphqlChannel', + query: operation.query ? print(operation.query) : null, + variables: operation.variables, + operationName: operation.operationName, + nonce: uuids()[0], + }, + { + received(data) { + if (data.errors) { + observer.error(data.errors); + } else if (data.result) { + observer.next(data.result); + } + + if (!data.more) { + observer.complete(); + } + }, + }, + ); + + return { + unsubscribe() { + subscription.unsubscribe(); + }, + }; + }); + } +} diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 22f88b1caa7..ee8384f734d 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -238,10 +238,13 @@ class GfmAutoComplete { const MEMBER_COMMAND = { ASSIGN: '/assign', UNASSIGN: '/unassign', + ASSIGN_REVIEWER: '/assign_reviewer', + UNASSIGN_REVIEWER: '/unassign_reviewer', REASSIGN: '/reassign', CC: '/cc', }; let assignees = []; + let reviewers = []; let command = ''; // Team Members @@ -286,9 +289,11 @@ class GfmAutoComplete { return null; }); - // Cache assignees list for easier filtering later + // Cache assignees & reviewers list for easier filtering later assignees = SidebarMediator.singleton?.store?.assignees?.map(createMemberSearchString) || []; + reviewers = + SidebarMediator.singleton?.store?.reviewers?.map(createMemberSearchString) || []; const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers); return match && match.length ? match[1] : null; @@ -309,6 +314,12 @@ class GfmAutoComplete { } else if (command === MEMBER_COMMAND.UNASSIGN) { // Only include members which are assigned to Issuable currently return data.filter((member) => assignees.includes(member.search)); + } else if (command === MEMBER_COMMAND.ASSIGN_REVIEWER) { + // Only include members which are not assigned as a reviewer to Issuable currently + return data.filter((member) => !reviewers.includes(member.search)); + } else if (command === MEMBER_COMMAND.UNASSIGN_REVIEWER) { + // Only include members which are not assigned as a reviewer to Issuable currently + return data.filter((member) => reviewers.includes(member.search)); } return data; diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index 1630f0d689c..cec689a44ca 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -4,6 +4,7 @@ import { ApolloLink } from 'apollo-link'; import { BatchHttpLink } from 'apollo-link-batch-http'; import { createHttpLink } from 'apollo-link-http'; import { createUploadLink } from 'apollo-upload-client'; +import ActionCableLink from '~/actioncable_link'; import { apolloCaptchaLink } from '~/captcha/apollo_captcha_link'; import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link'; import csrf from '~/lib/utils/csrf'; @@ -83,15 +84,27 @@ export default (resolvers = {}, config = {}) => { }); }); - return new ApolloClient({ - typeDefs, - link: ApolloLink.from([ + const hasSubscriptionOperation = ({ query: { definitions } }) => { + return definitions.some( + ({ kind, operation }) => kind === 'OperationDefinition' && operation === 'subscription', + ); + }; + + const appLink = ApolloLink.split( + hasSubscriptionOperation, + new ActionCableLink(), + ApolloLink.from([ requestCounterLink, performanceBarLink, new StartupJSLink(), apolloCaptchaLink, uploadsLink, ]), + ); + + return new ApolloClient({ + typeDefs, + link: appLink, cache: new InMemoryCache({ ...cacheConfig, freezeResults: assumeImmutableResults, diff --git a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue new file mode 100644 index 00000000000..091b202e10b --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_messages.vue @@ -0,0 +1,155 @@ +<script> +import { GlAlert } from '@gitlab/ui'; +import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; +import { __, s__ } from '~/locale'; +import { + COMMIT_FAILURE, + COMMIT_SUCCESS, + DEFAULT_FAILURE, + DEFAULT_SUCCESS, + LOAD_FAILURE_UNKNOWN, +} from '../../constants'; +import CodeSnippetAlert from '../code_snippet_alert/code_snippet_alert.vue'; +import { + CODE_SNIPPET_SOURCE_URL_PARAM, + CODE_SNIPPET_SOURCES, +} from '../code_snippet_alert/constants'; + +export default { + components: { + GlAlert, + CodeSnippetAlert, + }, + errorTexts: { + [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'), + [DEFAULT_FAILURE]: __('Something went wrong on our end.'), + [LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'), + }, + successTexts: { + [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), + [DEFAULT_SUCCESS]: __('Your action succeeded.'), + }, + props: { + failureType: { + type: String, + required: false, + default: null, + }, + failureReasons: { + type: Array, + required: false, + default: () => [], + }, + showFailure: { + type: Boolean, + required: false, + default: false, + }, + showSuccess: { + type: Boolean, + required: false, + default: false, + }, + successType: { + type: String, + required: false, + default: null, + }, + }, + data() { + return { + codeSnippetCopiedFrom: '', + }; + }, + computed: { + failure() { + switch (this.failureType) { + case LOAD_FAILURE_UNKNOWN: + return { + text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN], + variant: 'danger', + }; + case COMMIT_FAILURE: + return { + text: this.$options.errorTexts[COMMIT_FAILURE], + variant: 'danger', + }; + default: + return { + text: this.$options.errorTexts[DEFAULT_FAILURE], + variant: 'danger', + }; + } + }, + success() { + switch (this.successType) { + case COMMIT_SUCCESS: + return { + text: this.$options.successTexts[COMMIT_SUCCESS], + variant: 'info', + }; + default: + return { + text: this.$options.successTexts[DEFAULT_SUCCESS], + variant: 'info', + }; + } + }, + }, + created() { + this.parseCodeSnippetSourceParam(); + }, + methods: { + dismissCodeSnippetAlert() { + this.codeSnippetCopiedFrom = ''; + }, + dismissFailure() { + this.$emit('hide-failure'); + }, + dismissSuccess() { + this.$emit('hide-success'); + }, + parseCodeSnippetSourceParam() { + const [codeSnippetCopiedFrom] = getParameterValues(CODE_SNIPPET_SOURCE_URL_PARAM); + if (codeSnippetCopiedFrom && CODE_SNIPPET_SOURCES.includes(codeSnippetCopiedFrom)) { + this.codeSnippetCopiedFrom = codeSnippetCopiedFrom; + window.history.replaceState( + {}, + document.title, + removeParams([CODE_SNIPPET_SOURCE_URL_PARAM]), + ); + } + }, + }, +}; +</script> + +<template> + <div> + <code-snippet-alert + v-if="codeSnippetCopiedFrom" + :source="codeSnippetCopiedFrom" + class="gl-mb-5" + @dismiss="dismissCodeSnippetAlert" + /> + <gl-alert + v-if="showSuccess" + :variant="success.variant" + class="gl-mb-5" + @dismiss="dismissSuccess" + > + {{ success.text }} + </gl-alert> + <gl-alert + v-if="showFailure" + :variant="failure.variant" + class="gl-mb-5" + @dismiss="dismissFailure" + > + {{ failure.text }} + <ul v-if="failureReasons.length" class="gl-mb-0"> + <li v-for="reason in failureReasons" :key="reason">{{ reason }}</li> + </ul> + </gl-alert> + </div> +</template> diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js index 8d0ec6c3e2d..56862f17858 100644 --- a/app/assets/javascripts/pipeline_editor/constants.js +++ b/app/assets/javascripts/pipeline_editor/constants.js @@ -14,6 +14,7 @@ export const COMMIT_FAILURE = 'COMMIT_FAILURE'; export const COMMIT_SUCCESS = 'COMMIT_SUCCESS'; export const DEFAULT_FAILURE = 'DEFAULT_FAILURE'; +export const DEFAULT_SUCCESS = 'DEFAULT_SUCCESS'; export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN'; export const CREATE_TAB = 'CREATE_TAB'; diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index 5f9662e90e6..bf36e00c662 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -1,22 +1,15 @@ <script> -import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon } from '@gitlab/ui'; import { fetchPolicies } from '~/lib/graphql'; import httpStatusCodes from '~/lib/utils/http_status'; -import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; -import { __, s__ } from '~/locale'; +import { s__ } from '~/locale'; import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils'; -import CodeSnippetAlert from './components/code_snippet_alert/code_snippet_alert.vue'; -import { - CODE_SNIPPET_SOURCE_URL_PARAM, - CODE_SNIPPET_SOURCES, -} from './components/code_snippet_alert/constants'; + import ConfirmUnsavedChangesDialog from './components/ui/confirm_unsaved_changes_dialog.vue'; import PipelineEditorEmptyState from './components/ui/pipeline_editor_empty_state.vue'; +import PipelineEditorMessages from './components/ui/pipeline_editor_messages.vue'; import { - COMMIT_FAILURE, - COMMIT_SUCCESS, - DEFAULT_FAILURE, EDITOR_APP_STATUS_EMPTY, EDITOR_APP_STATUS_ERROR, EDITOR_APP_STATUS_LOADING, @@ -32,11 +25,10 @@ import PipelineEditorHome from './pipeline_editor_home.vue'; export default { components: { ConfirmUnsavedChangesDialog, - GlAlert, GlLoadingIcon, PipelineEditorEmptyState, PipelineEditorHome, - CodeSnippetAlert, + PipelineEditorMessages, }, inject: { ciConfigPath: { @@ -51,15 +43,14 @@ export default { ciConfigData: {}, failureType: null, failureReasons: [], - showStartScreen: false, initialCiFileContent: '', isNewCiConfigFile: false, lastCommittedContent: '', currentCiFileContent: '', - showFailureAlert: false, - showSuccessAlert: false, successType: null, - codeSnippetCopiedFrom: '', + showStartScreen: false, + showSuccess: false, + showFailure: false, }; }, @@ -152,50 +143,12 @@ export default { isEmpty() { return this.currentCiFileContent === ''; }, - failure() { - switch (this.failureType) { - case LOAD_FAILURE_UNKNOWN: - return { - text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN], - variant: 'danger', - }; - case COMMIT_FAILURE: - return { - text: this.$options.errorTexts[COMMIT_FAILURE], - variant: 'danger', - }; - default: - return { - text: this.$options.errorTexts[DEFAULT_FAILURE], - variant: 'danger', - }; - } - }, - success() { - switch (this.successType) { - case COMMIT_SUCCESS: - return { - text: this.$options.successTexts[COMMIT_SUCCESS], - variant: 'info', - }; - default: - return null; - } - }, }, i18n: { tabEdit: s__('Pipelines|Write pipeline configuration'), tabGraph: s__('Pipelines|Visualize'), tabLint: s__('Pipelines|Lint'), }, - errorTexts: { - [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'), - [DEFAULT_FAILURE]: __('Something went wrong on our end.'), - [LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'), - }, - successTexts: { - [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), - }, watch: { isEmpty(flag) { if (flag) { @@ -203,9 +156,6 @@ export default { } }, }, - created() { - this.parseCodeSnippetSourceParam(); - }, methods: { handleBlobContentError(error = {}) { const { networkError } = error; @@ -223,12 +173,11 @@ export default { this.reportFailure(LOAD_FAILURE_UNKNOWN); } }, - - dismissFailure() { - this.showFailureAlert = false; + hideFailure() { + this.showFailure = false; }, - dismissSuccess() { - this.showSuccessAlert = false; + hideSuccess() { + this.showSuccess = false; }, async refetchContent() { this.$apollo.queries.initialCiFileContent.skip = false; @@ -238,13 +187,13 @@ export default { this.setAppStatus(EDITOR_APP_STATUS_ERROR); window.scrollTo({ top: 0, behavior: 'smooth' }); - this.showFailureAlert = true; + this.showFailure = true; this.failureType = type; this.failureReasons = reasons; }, reportSuccess(type) { window.scrollTo({ top: 0, behavior: 'smooth' }); - this.showSuccessAlert = true; + this.showSuccess = true; this.successType = type; }, resetContent() { @@ -277,20 +226,6 @@ export default { // if the user has made changes to the file that are unsaved. this.lastCommittedContent = this.currentCiFileContent; }, - parseCodeSnippetSourceParam() { - const [codeSnippetCopiedFrom] = getParameterValues(CODE_SNIPPET_SOURCE_URL_PARAM); - if (codeSnippetCopiedFrom && CODE_SNIPPET_SOURCES.includes(codeSnippetCopiedFrom)) { - this.codeSnippetCopiedFrom = codeSnippetCopiedFrom; - window.history.replaceState( - {}, - document.title, - removeParams([CODE_SNIPPET_SOURCE_URL_PARAM]), - ); - } - }, - dismissCodeSnippetAlert() { - this.codeSnippetCopiedFrom = ''; - }, }, }; </script> @@ -303,31 +238,15 @@ export default { @createEmptyConfigFile="setNewEmptyCiConfigFile" /> <div v-else> - <code-snippet-alert - v-if="codeSnippetCopiedFrom" - :source="codeSnippetCopiedFrom" - class="gl-mb-5" - @dismiss="dismissCodeSnippetAlert" + <pipeline-editor-messages + :failure-type="failureType" + :failure-reasons="failureReasons" + :show-failure="showFailure" + :show-success="showSuccess" + :success-type="successType" + @hide-success="hideSuccess" + @hide-failure="hideFailure" /> - <gl-alert - v-if="showSuccessAlert" - :variant="success.variant" - class="gl-mb-5" - @dismiss="dismissSuccess" - > - {{ success.text }} - </gl-alert> - <gl-alert - v-if="showFailureAlert" - :variant="failure.variant" - class="gl-mb-5" - @dismiss="dismissFailure" - > - {{ failure.text }} - <ul v-if="failureReasons.length" class="gl-mb-0"> - <li v-for="reason in failureReasons" :key="reason">{{ reason }}</li> - </ul> - </gl-alert> <pipeline-editor-home :ci-config-data="ciConfigData" :ci-file-content="currentCiFileContent" diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue b/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue index f98798582c1..e7ef731eed8 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue @@ -1,6 +1,7 @@ <script> -import actionCable from '~/actioncable_consumer'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import produce from 'immer'; +import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { IssuableType } from '~/issue_show/constants'; import { assigneesQueries } from '~/sidebar/constants'; export default { @@ -12,60 +13,62 @@ export default { required: false, default: null, }, - issuableIid: { + issuableType: { type: String, required: true, }, - projectPath: { - type: String, + issuableId: { + type: Number, required: true, }, - issuableType: { - type: String, + queryVariables: { + type: Object, required: true, }, }, + computed: { + issuableClass() { + return Object.keys(IssuableType).find((key) => IssuableType[key] === this.issuableType); + }, + }, apollo: { - workspace: { + issuable: { query() { return assigneesQueries[this.issuableType].query; }, variables() { - return { - iid: this.issuableIid, - fullPath: this.projectPath, - }; + return this.queryVariables; + }, + update(data) { + return data.workspace?.issuable; }, - result(data) { - if (this.mediator) { - this.handleFetchResult(data); - } + subscribeToMore: { + document() { + return assigneesQueries[this.issuableType].subscription; + }, + variables() { + return { + issuableId: convertToGraphQLId(this.issuableClass, this.issuableId), + }; + }, + updateQuery(prev, { subscriptionData }) { + if (prev && subscriptionData?.data?.issuableAssigneesUpdated) { + const data = produce(prev, (draftData) => { + draftData.workspace.issuable.assignees.nodes = + subscriptionData.data.issuableAssigneesUpdated.assignees.nodes; + }); + if (this.mediator) { + this.handleFetchResult(data); + } + return data; + } + return prev; + }, }, }, }, - mounted() { - this.initActionCablePolling(); - }, - beforeDestroy() { - this.$options.subscription.unsubscribe(); - }, methods: { - received(data) { - if (data.event === 'updated') { - this.$apollo.queries.workspace.refetch(); - } - }, - initActionCablePolling() { - this.$options.subscription = actionCable.subscriptions.create( - { - channel: 'IssuesChannel', - project_path: this.projectPath, - iid: this.issuableIid, - }, - { received: this.received }, - ); - }, - handleFetchResult({ data }) { + handleFetchResult(data) { const { nodes } = data.workspace.issuable.assignees; const assignees = nodes.map((n) => ({ diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue index e15ea595190..ca95599742a 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue @@ -44,6 +44,10 @@ export default { type: String, required: true, }, + issuableId: { + type: Number, + required: true, + }, assigneeAvailabilityStatus: { type: Object, required: false, @@ -61,6 +65,12 @@ export default { // Note: Realtime is only available on issues right now, future support for MR wil be built later. return this.glFeatures.realTimeIssueSidebar && this.issuableType === 'issue'; }, + queryVariables() { + return { + iid: this.issuableIid, + fullPath: this.projectPath, + }; + }, relativeUrlRoot() { return gon.relative_url_root ?? ''; }, @@ -121,9 +131,9 @@ export default { <div> <assignees-realtime v-if="shouldEnableRealtime" - :issuable-iid="issuableIid" - :project-path="projectPath" :issuable-type="issuableType" + :issuable-id="issuableId" + :query-variables="queryVariables" :mediator="mediator" /> <assignee-title diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue index 78cac989850..2fc25151d1c 100644 --- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue +++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue @@ -73,6 +73,11 @@ export default { return [IssuableType.Issue, IssuableType.MergeRequest].includes(value); }, }, + issuableId: { + type: Number, + required: false, + default: null, + }, multipleAssignees: { type: Boolean, required: false, @@ -340,9 +345,9 @@ export default { <div data-testid="assignees-widget"> <sidebar-assignees-realtime v-if="shouldEnableRealtime" - :project-path="fullPath" - :issuable-iid="iid" :issuable-type="issuableType" + :issuable-id="issuableId" + :query-variables="queryVariables" /> <sidebar-editable-item ref="toggle" diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js index 80e07d556bf..58e4b0348ca 100644 --- a/app/assets/javascripts/sidebar/constants.js +++ b/app/assets/javascripts/sidebar/constants.js @@ -1,5 +1,6 @@ import { IssuableType } from '~/issue_show/constants'; import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql'; +import issuableAssigneesSubscription from '~/sidebar/queries/issuable_assignees.subscription.graphql'; import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql'; import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql'; import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql'; @@ -17,6 +18,7 @@ export const ASSIGNEES_DEBOUNCE_DELAY = 250; export const assigneesQueries = { [IssuableType.Issue]: { query: getIssueParticipants, + subscription: issuableAssigneesSubscription, mutation: updateAssigneesMutation, }, [IssuableType.MergeRequest]: { diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js index 1304e84814b..52a1efa04e4 100644 --- a/app/assets/javascripts/sidebar/mount_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_sidebar.js @@ -53,7 +53,7 @@ function mountAssigneesComponentDeprecated(mediator) { if (!el) return; - const { iid, fullPath } = getSidebarOptions(); + const { id, iid, fullPath } = getSidebarOptions(); const assigneeAvailabilityStatus = getSidebarAssigneeAvailabilityData(); // eslint-disable-next-line no-new new Vue({ @@ -74,6 +74,7 @@ function mountAssigneesComponentDeprecated(mediator) { isInIssuePage() || isInIncidentPage() || isInDesignPage() ? IssuableType.Issue : IssuableType.MergeRequest, + issuableId: id, assigneeAvailabilityStatus, }, }), @@ -85,7 +86,7 @@ function mountAssigneesComponent() { if (!el) return; - const { iid, fullPath, editable, projectMembersPath } = getSidebarOptions(); + const { id, iid, fullPath, editable, projectMembersPath } = getSidebarOptions(); // eslint-disable-next-line no-new new Vue({ el, @@ -108,6 +109,7 @@ function mountAssigneesComponent() { isInIssuePage() || isInIncidentPage() || isInDesignPage() ? IssuableType.Issue : IssuableType.MergeRequest, + issuableId: id, multipleAssignees: !el.dataset.maxAssignees, }, scopedSlots: { diff --git a/app/assets/javascripts/sidebar/queries/issuable_assignees.subscription.graphql b/app/assets/javascripts/sidebar/queries/issuable_assignees.subscription.graphql new file mode 100644 index 00000000000..47ce094418c --- /dev/null +++ b/app/assets/javascripts/sidebar/queries/issuable_assignees.subscription.graphql @@ -0,0 +1,16 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" + +subscription issuableAssigneesUpdated($issuableId: IssuableID!) { + issuableAssigneesUpdated(issuableId: $issuableId) { + ... on Issue { + assignees { + nodes { + ...User + status { + availability + } + } + } + } + } +} |