Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-03 06:08:30 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-03 06:08:30 +0300
commit00c0098252ac7b303061175ff4c3e002c07407d2 (patch)
treed3882967cb0737c15ed2916f8bcc3ee9a7920fef /app/assets/javascripts/comment_templates
parentc8f8f2a4966ab5ccbee8477544987ef9bdbcd55f (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/comment_templates')
-rw-r--r--app/assets/javascripts/comment_templates/components/app.vue23
-rw-r--r--app/assets/javascripts/comment_templates/components/form.vue182
-rw-r--r--app/assets/javascripts/comment_templates/components/list.vue67
-rw-r--r--app/assets/javascripts/comment_templates/components/list_item.vue116
-rw-r--r--app/assets/javascripts/comment_templates/index.js31
-rw-r--r--app/assets/javascripts/comment_templates/pages/edit.vue68
-rw-r--r--app/assets/javascripts/comment_templates/pages/index.vue67
-rw-r--r--app/assets/javascripts/comment_templates/queries/create_saved_reply.mutation.graphql10
-rw-r--r--app/assets/javascripts/comment_templates/queries/delete_saved_reply.mutation.graphql5
-rw-r--r--app/assets/javascripts/comment_templates/queries/get_saved_reply.query.graphql10
-rw-r--r--app/assets/javascripts/comment_templates/queries/saved_replies.query.graphql19
-rw-r--r--app/assets/javascripts/comment_templates/queries/update_saved_reply.mutation.graphql10
-rw-r--r--app/assets/javascripts/comment_templates/routes.js15
13 files changed, 623 insertions, 0 deletions
diff --git a/app/assets/javascripts/comment_templates/components/app.vue b/app/assets/javascripts/comment_templates/components/app.vue
new file mode 100644
index 00000000000..9e0d2cc73ec
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/components/app.vue
@@ -0,0 +1,23 @@
+<script>
+export default {};
+</script>
+
+<template>
+ <div class="row gl-mt-5">
+ <div class="col-lg-4">
+ <h4 class="gl-mt-0">
+ {{ __('Comment templates') }}
+ </h4>
+ <p>
+ {{
+ __(
+ 'Comment templates can be used when creating comments inside issues, merge requests, and epics.',
+ )
+ }}
+ </p>
+ </div>
+ <div class="col-lg-8">
+ <keep-alive><router-view /></keep-alive>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/comment_templates/components/form.vue b/app/assets/javascripts/comment_templates/components/form.vue
new file mode 100644
index 00000000000..47efccc3d0c
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/components/form.vue
@@ -0,0 +1,182 @@
+<script>
+import { GlButton, GlForm, GlFormGroup, GlFormInput, GlAlert } from '@gitlab/ui';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { logError } from '~/lib/logger';
+import { __ } from '~/locale';
+import createSavedReplyMutation from '../queries/create_saved_reply.mutation.graphql';
+import updateSavedReplyMutation from '../queries/update_saved_reply.mutation.graphql';
+
+export default {
+ components: {
+ GlButton,
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ GlAlert,
+ MarkdownField,
+ },
+ props: {
+ id: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ name: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ content: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ errors: [],
+ saving: false,
+ showValidation: false,
+ updateCommentTemplate: {
+ name: this.name,
+ content: this.content,
+ },
+ };
+ },
+ computed: {
+ isNameValid() {
+ if (this.showValidation) return Boolean(this.updateCommentTemplate.name);
+
+ return true;
+ },
+ isContentValid() {
+ if (this.showValidation) return Boolean(this.updateCommentTemplate.content);
+
+ return true;
+ },
+ isValid() {
+ return this.isNameValid && this.isContentValid;
+ },
+ },
+ methods: {
+ onSubmit() {
+ this.showValidation = true;
+
+ if (!this.isValid) return;
+
+ this.errors = [];
+ this.saving = true;
+
+ this.$apollo
+ .mutate({
+ mutation: this.id ? updateSavedReplyMutation : createSavedReplyMutation,
+ variables: {
+ id: this.id,
+ name: this.updateCommentTemplate.name,
+ content: this.updateCommentTemplate.content,
+ },
+ update: (store, { data: { savedReplyMutation } }) => {
+ if (savedReplyMutation.errors.length) {
+ this.errors = savedReplyMutation.errors.map((e) => e);
+ } else {
+ this.$emit('saved');
+ this.updateCommentTemplate = { name: '', content: '' };
+ this.showValidation = false;
+ }
+ },
+ })
+ .catch((error) => {
+ const errors = error.graphQLErrors;
+
+ if (errors?.length) {
+ this.errors = errors.map((e) => e.message);
+ } else {
+ // Let's be sure to log the original error so it isn't just swallowed.
+ // Also, we don't want to translate console messages.
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ logError('Unexpected error while saving reply', error);
+
+ this.errors = [__('An unexpected error occurred. Please try again.')];
+ }
+ })
+ .finally(() => {
+ this.saving = false;
+ });
+ },
+ },
+ restrictedToolbarItems: ['full-screen'],
+ markdownDocsPath: helpPagePath('user/markdown'),
+};
+</script>
+
+<template>
+ <gl-form
+ class="new-note common-note-form gl-mb-6"
+ data-testid="comment-template-form"
+ @submit.prevent="onSubmit"
+ >
+ <gl-alert
+ v-for="error in errors"
+ :key="error"
+ variant="danger"
+ class="gl-mb-3"
+ :dismissible="false"
+ >
+ {{ error }}
+ </gl-alert>
+ <gl-form-group
+ :label="__('Name')"
+ :state="isNameValid"
+ :invalid-feedback="__('Please enter a name for the comment template.')"
+ data-testid="comment-template-name-form-group"
+ >
+ <gl-form-input
+ v-model="updateCommentTemplate.name"
+ :placeholder="__('Enter a name for your comment template')"
+ data-testid="comment-template-name-input"
+ />
+ </gl-form-group>
+ <gl-form-group
+ :label="__('Content')"
+ :state="isContentValid"
+ :invalid-feedback="__('Please enter the comment template content.')"
+ data-testid="comment-template-content-form-group"
+ >
+ <markdown-field
+ :enable-preview="false"
+ :is-submitting="saving"
+ :add-spacing-classes="false"
+ :textarea-value="updateCommentTemplate.content"
+ :markdown-docs-path="$options.markdownDocsPath"
+ :restricted-tool-bar-items="$options.restrictedToolbarItems"
+ :force-autosize="false"
+ class="js-no-autosize gl-border-gray-400!"
+ >
+ <template #textarea>
+ <textarea
+ v-model="updateCommentTemplate.content"
+ dir="auto"
+ class="note-textarea js-gfm-input js-autosize markdown-area"
+ data-supports-quick-actions="false"
+ :aria-label="__('Content')"
+ :placeholder="__('Write comment template content hereā€¦')"
+ data-testid="comment-template-content-input"
+ @keydown.meta.enter="onSubmit"
+ @keydown.ctrl.enter="onSubmit"
+ ></textarea>
+ </template>
+ </markdown-field>
+ </gl-form-group>
+ <gl-button
+ variant="confirm"
+ class="gl-mr-3 js-no-auto-disable"
+ type="submit"
+ :loading="saving"
+ data-testid="comment-template-form-submit-btn"
+ >
+ {{ __('Save') }}
+ </gl-button>
+ <gl-button v-if="id" :to="{ path: '/' }">{{ __('Cancel') }}</gl-button>
+ </gl-form>
+</template>
diff --git a/app/assets/javascripts/comment_templates/components/list.vue b/app/assets/javascripts/comment_templates/components/list.vue
new file mode 100644
index 00000000000..52bebfd050c
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/components/list.vue
@@ -0,0 +1,67 @@
+<script>
+import { GlKeysetPagination, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
+import ListItem from './list_item.vue';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlKeysetPagination,
+ GlSprintf,
+ ListItem,
+ },
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ savedReplies: {
+ type: Array,
+ required: true,
+ },
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
+ count: {
+ type: Number,
+ required: true,
+ },
+ },
+ methods: {
+ prevPage() {
+ this.$emit('input', {
+ before: this.pageInfo.beforeCursor,
+ });
+ },
+ nextPage() {
+ this.$emit('input', {
+ after: this.pageInfo.endCursor,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-border-t gl-pt-4">
+ <gl-loading-icon v-if="loading" size="lg" />
+ <template v-else>
+ <h5 class="gl-font-lg" data-testid="title">
+ <gl-sprintf :message="__('My comment templates (%{count})')">
+ <template #count>{{ count }}</template>
+ </gl-sprintf>
+ </h5>
+ <ul class="gl-list-style-none gl-p-0 gl-m-0">
+ <list-item v-for="template in savedReplies" :key="template.id" :template="template" />
+ </ul>
+ <gl-keyset-pagination
+ v-if="pageInfo.hasPreviousPage || pageInfo.hasNextPage"
+ v-bind="pageInfo"
+ class="gl-mt-4"
+ @prev="prevPage"
+ @next="nextPage"
+ />
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/comment_templates/components/list_item.vue b/app/assets/javascripts/comment_templates/components/list_item.vue
new file mode 100644
index 00000000000..d763700db42
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/components/list_item.vue
@@ -0,0 +1,116 @@
+<script>
+import { uniqueId } from 'lodash';
+import { GlDisclosureDropdown, GlTooltip, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui';
+import { __ } from '~/locale';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import deleteSavedReplyMutation from '../queries/delete_saved_reply.mutation.graphql';
+
+export default {
+ components: {
+ GlDisclosureDropdown,
+ GlTooltip,
+ GlModal,
+ GlSprintf,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ props: {
+ template: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isDeleting: false,
+ modalId: uniqueId('delete-comment-template-'),
+ toggleId: uniqueId('actions-toggle-'),
+ };
+ },
+ computed: {
+ id() {
+ return getIdFromGraphQLId(this.template.id);
+ },
+ dropdownItems() {
+ return [
+ {
+ text: __('Edit'),
+ action: () => this.$router.push({ name: 'edit', params: { id: this.id } }),
+ extraAttrs: {
+ 'data-testid': 'comment-template-edit-btn',
+ },
+ },
+ {
+ text: __('Delete'),
+ action: () => this.$refs['delete-modal'].show(),
+ extraAttrs: {
+ 'data-testid': 'comment-template-delete-btn',
+ class: 'gl-text-red-500!',
+ },
+ },
+ ];
+ },
+ },
+ methods: {
+ onDelete() {
+ this.isDeleting = true;
+
+ this.$apollo.mutate({
+ mutation: deleteSavedReplyMutation,
+ variables: {
+ id: this.template.id,
+ },
+ update: (cache) => {
+ const cacheId = cache.identify(this.template);
+ cache.evict({ id: cacheId });
+ },
+ });
+ },
+ },
+ actionPrimary: { text: __('Delete'), attributes: { variant: 'danger' } },
+ actionSecondary: { text: __('Cancel'), attributes: { variant: 'default' } },
+};
+</script>
+
+<template>
+ <li class="gl-pt-4 gl-pb-5 gl-border-b">
+ <div class="gl-display-flex gl-align-items-center">
+ <h6 class="gl-mr-3 gl-my-0" data-testid="comment-template-name">{{ template.name }}</h6>
+ <div class="gl-ml-auto">
+ <gl-disclosure-dropdown
+ :items="dropdownItems"
+ :toggle-id="toggleId"
+ icon="ellipsis_v"
+ no-caret
+ text-sr-only
+ placement="right"
+ :toggle-text="__('Comment template actions')"
+ :loading="isDeleting"
+ category="tertiary"
+ />
+ <gl-tooltip :target="toggleId">
+ {{ __('Comment template actions') }}
+ </gl-tooltip>
+ </div>
+ </div>
+ <div class="gl-mt-3 gl-font-monospace">{{ template.content }}</div>
+ <gl-modal
+ ref="delete-modal"
+ :title="__('Delete comment template')"
+ :action-primary="$options.actionPrimary"
+ :action-secondary="$options.actionSecondary"
+ :modal-id="modalId"
+ size="sm"
+ @primary="onDelete"
+ >
+ <gl-sprintf
+ :message="__('Are you sure you want to delete %{name}? This action cannot be undone.')"
+ >
+ <template #name
+ ><strong>{{ template.name }}</strong></template
+ >
+ </gl-sprintf>
+ </gl-modal>
+ </li>
+</template>
diff --git a/app/assets/javascripts/comment_templates/index.js b/app/assets/javascripts/comment_templates/index.js
new file mode 100644
index 00000000000..8cd763e7a9e
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/index.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import routes from './routes';
+import App from './components/app.vue';
+
+export const initCommentTemplates = () => {
+ Vue.use(VueApollo);
+ Vue.use(VueRouter);
+
+ const el = document.getElementById('js-comment-templates-root');
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+ const router = new VueRouter({
+ base: el.dataset.basePath,
+ mode: 'history',
+ routes,
+ });
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ router,
+ apolloProvider,
+ render(h) {
+ return h(App);
+ },
+ });
+};
diff --git a/app/assets/javascripts/comment_templates/pages/edit.vue b/app/assets/javascripts/comment_templates/pages/edit.vue
new file mode 100644
index 00000000000..343efdccefa
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/pages/edit.vue
@@ -0,0 +1,68 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { fetchPolicies } from '~/lib/graphql';
+import { createAlert } from '~/alert';
+import { __ } from '~/locale';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { TYPE_USERS_SAVED_REPLY } from '~/graphql_shared/constants';
+import CreateForm from '../components/form.vue';
+import getSavedReply from '../queries/get_saved_reply.query.graphql';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ CreateForm,
+ },
+ apollo: {
+ savedReply: {
+ fetchPolicy: fetchPolicies.NETWORK_ONLY,
+ query: getSavedReply,
+ variables() {
+ return {
+ id: convertToGraphQLId(TYPE_USERS_SAVED_REPLY, this.$route.params.id),
+ };
+ },
+ update: (r) => r.currentUser.savedReply,
+ skip() {
+ return !this.$route.params.id;
+ },
+ result({
+ data: {
+ currentUser: { savedReply },
+ },
+ }) {
+ if (!savedReply) {
+ createAlert({ message: __('Unable to find comment template') });
+ this.redirectToRoot();
+ }
+ },
+ },
+ },
+ data() {
+ return {
+ savedReply: null,
+ };
+ },
+ methods: {
+ redirectToRoot() {
+ this.$router.push({ path: '/' });
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h5 class="gl-mt-0 gl-font-lg">
+ {{ __('Edit comment template') }}
+ </h5>
+ <gl-loading-icon v-if="$apollo.queries.savedReply.loading" size="lg" />
+ <create-form
+ v-else-if="savedReply"
+ :id="savedReply.id"
+ :name="savedReply.name"
+ :content="savedReply.content"
+ @saved="redirectToRoot"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/comment_templates/pages/index.vue b/app/assets/javascripts/comment_templates/pages/index.vue
new file mode 100644
index 00000000000..72a94dafc58
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/pages/index.vue
@@ -0,0 +1,67 @@
+<script>
+import { fetchPolicies } from '~/lib/graphql';
+import CreateForm from '../components/form.vue';
+import savedRepliesQuery from '../queries/saved_replies.query.graphql';
+import List from '../components/list.vue';
+
+export default {
+ apollo: {
+ savedReplies: {
+ fetchPolicy: fetchPolicies.NETWORK_ONLY,
+ query: savedRepliesQuery,
+ update: (r) => r.currentUser?.savedReplies?.nodes,
+ variables() {
+ return {
+ ...this.pagination,
+ };
+ },
+ result({ data }) {
+ const pageInfo = data.currentUser?.savedReplies?.pageInfo;
+
+ this.count = data.currentUser?.savedReplies?.count;
+
+ if (pageInfo) {
+ this.pageInfo = pageInfo;
+ }
+ },
+ },
+ },
+ components: {
+ CreateForm,
+ List,
+ },
+ data() {
+ return {
+ savedReplies: [],
+ count: 0,
+ pageInfo: {},
+ pagination: {},
+ };
+ },
+ methods: {
+ refetchSavedReplies() {
+ this.pagination = {};
+ this.$apollo.queries.savedReplies.refetch();
+ },
+ changePage(pageInfo) {
+ this.pagination = pageInfo;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h5 class="gl-mt-0 gl-font-lg">
+ {{ __('Add new comment template') }}
+ </h5>
+ <create-form @saved="refetchSavedReplies" />
+ <list
+ :loading="$apollo.queries.savedReplies.loading"
+ :saved-replies="savedReplies"
+ :page-info="pageInfo"
+ :count="count"
+ @input="changePage"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/comment_templates/queries/create_saved_reply.mutation.graphql b/app/assets/javascripts/comment_templates/queries/create_saved_reply.mutation.graphql
new file mode 100644
index 00000000000..c4e632d0f16
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/queries/create_saved_reply.mutation.graphql
@@ -0,0 +1,10 @@
+mutation savedReplyCreate($name: String!, $content: String!) {
+ savedReplyMutation: savedReplyCreate(input: { name: $name, content: $content }) {
+ errors
+ savedReply {
+ id
+ name
+ content
+ }
+ }
+}
diff --git a/app/assets/javascripts/comment_templates/queries/delete_saved_reply.mutation.graphql b/app/assets/javascripts/comment_templates/queries/delete_saved_reply.mutation.graphql
new file mode 100644
index 00000000000..76571ba628c
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/queries/delete_saved_reply.mutation.graphql
@@ -0,0 +1,5 @@
+mutation deleteSavedReply($id: UsersSavedReplyID!) {
+ savedReplyDestroy(input: { id: $id }) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/comment_templates/queries/get_saved_reply.query.graphql b/app/assets/javascripts/comment_templates/queries/get_saved_reply.query.graphql
new file mode 100644
index 00000000000..66f5f43af49
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/queries/get_saved_reply.query.graphql
@@ -0,0 +1,10 @@
+query getSavedReply($id: UsersSavedReplyID!) {
+ currentUser {
+ id
+ savedReply(id: $id) {
+ id
+ name
+ content
+ }
+ }
+}
diff --git a/app/assets/javascripts/comment_templates/queries/saved_replies.query.graphql b/app/assets/javascripts/comment_templates/queries/saved_replies.query.graphql
new file mode 100644
index 00000000000..d8e76b5e2a8
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/queries/saved_replies.query.graphql
@@ -0,0 +1,19 @@
+query savedReplies($after: String = "", $before: String = "") {
+ currentUser {
+ id
+ savedReplies(after: $after, before: $before) {
+ nodes {
+ id
+ name
+ content
+ }
+ count
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ endCursor
+ startCursor
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/comment_templates/queries/update_saved_reply.mutation.graphql b/app/assets/javascripts/comment_templates/queries/update_saved_reply.mutation.graphql
new file mode 100644
index 00000000000..14a47d7bc9c
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/queries/update_saved_reply.mutation.graphql
@@ -0,0 +1,10 @@
+mutation savedReplyUpdate($id: UsersSavedReplyID!, $name: String!, $content: String!) {
+ savedReplyMutation: savedReplyUpdate(input: { id: $id, name: $name, content: $content }) {
+ errors
+ savedReply {
+ id
+ name
+ content
+ }
+ }
+}
diff --git a/app/assets/javascripts/comment_templates/routes.js b/app/assets/javascripts/comment_templates/routes.js
new file mode 100644
index 00000000000..7687c6f335a
--- /dev/null
+++ b/app/assets/javascripts/comment_templates/routes.js
@@ -0,0 +1,15 @@
+import IndexComponent from './pages/index.vue';
+
+import EditComponent from './pages/edit.vue';
+
+export default [
+ {
+ path: '/',
+ component: IndexComponent,
+ },
+ {
+ name: 'edit',
+ path: '/:id',
+ component: EditComponent,
+ },
+];