diff options
Diffstat (limited to 'app/assets/javascripts/issues')
13 files changed, 280 insertions, 23 deletions
diff --git a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue index b9d876ef72f..8edc9a08c9e 100644 --- a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue +++ b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue @@ -30,20 +30,35 @@ import { __ } from '~/locale'; import { TOKEN_TITLE_ASSIGNEE, TOKEN_TITLE_AUTHOR, + TOKEN_TITLE_LABEL, + TOKEN_TITLE_MILESTONE, + TOKEN_TITLE_MY_REACTION, TOKEN_TYPE_ASSIGNEE, TOKEN_TYPE_AUTHOR, + TOKEN_TYPE_LABEL, + TOKEN_TYPE_MILESTONE, + TOKEN_TYPE_MY_REACTION, } from '~/vue_shared/components/filtered_search_bar/constants'; import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue'; import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants'; +import { AutocompleteCache } from '../utils'; const UserToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/user_token.vue'); +const EmojiToken = () => + import('~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue'); +const LabelToken = () => + import('~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'); +const MilestoneToken = () => + import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'); export default { i18n: { calendarButtonText: __('Subscribe to calendar'), closed: __('CLOSED'), closedMoved: __('CLOSED (MOVED)'), - emptyStateTitle: __('Please select at least one filter to see results'), + emptyStateWithFilterTitle: __('Sorry, your filter produced no results'), + emptyStateWithFilterDescription: __('To widen your search, change or remove filters above'), + emptyStateWithoutFilterTitle: __('Please select at least one filter to see results'), errorFetchingIssues: __('An error occurred while loading issues'), rssButtonText: __('Subscribe to RSS feed'), searchInputPlaceholder: __('Search or filter results...'), @@ -60,8 +75,12 @@ export default { GlTooltip: GlTooltipDirective, }, inject: [ + 'autocompleteAwardEmojisPath', 'calendarPath', - 'emptyStateSvgPath', + 'dashboardLabelsPath', + 'dashboardMilestonesPath', + 'emptyStateWithFilterSvgPath', + 'emptyStateWithoutFilterSvgPath', 'hasBlockedIssuesFeature', 'hasIssuableHealthStatusFeature', 'hasIssueWeightsFeature', @@ -117,6 +136,9 @@ export default { this.issuesError = this.$options.i18n.errorFetchingIssues; Sentry.captureException(error); }, + skip() { + return !this.hasSearch; + }, debounce: 200, }, }, @@ -124,6 +146,25 @@ export default { apiFilterParams() { return convertToApiParams(this.filterTokens); }, + emptyStateDescription() { + return this.hasSearch ? this.$options.i18n.emptyStateWithFilterDescription : undefined; + }, + emptyStateSvgPath() { + return this.hasSearch + ? this.emptyStateWithFilterSvgPath + : this.emptyStateWithoutFilterSvgPath; + }, + emptyStateTitle() { + return this.hasSearch + ? this.$options.i18n.emptyStateWithFilterTitle + : this.$options.i18n.emptyStateWithoutFilterTitle; + }, + hasSearch() { + return Boolean(this.searchQuery || Object.keys(this.urlFilterParams).length); + }, + renderedIssues() { + return this.hasSearch ? this.issues : []; + }, searchQuery() { return convertToSearchQuery(this.filterTokens); }, @@ -159,12 +200,46 @@ export default { preloadedUsers, recentSuggestionsStorageKey: 'dashboard-issues-recent-tokens-author', }, + { + type: TOKEN_TYPE_LABEL, + title: TOKEN_TITLE_LABEL, + icon: 'labels', + token: LabelToken, + fetchLabels: this.fetchLabels, + recentSuggestionsStorageKey: 'dashboard-issues-recent-tokens-label', + }, + { + type: TOKEN_TYPE_MILESTONE, + title: TOKEN_TITLE_MILESTONE, + icon: 'clock', + token: MilestoneToken, + fetchMilestones: this.fetchMilestones, + recentSuggestionsStorageKey: 'dashboard-issues-recent-tokens-milestone', + shouldSkipSort: true, + }, ]; + if (this.isSignedIn) { + tokens.push({ + type: TOKEN_TYPE_MY_REACTION, + title: TOKEN_TITLE_MY_REACTION, + icon: 'thumb-up', + token: EmojiToken, + unique: true, + fetchEmojis: this.fetchEmojis, + recentSuggestionsStorageKey: 'dashboard-issues-recent-tokens-my_reaction', + }); + } + + tokens.sort((a, b) => a.title.localeCompare(b.title)); + return tokens; }, showPaginationControls() { - return this.issues.length > 0 && (this.pageInfo.hasNextPage || this.pageInfo.hasPreviousPage); + return ( + this.renderedIssues.length > 0 && + (this.pageInfo.hasNextPage || this.pageInfo.hasPreviousPage) + ); }, sortOptions() { return getSortOptions({ @@ -185,7 +260,34 @@ export default { }; }, }, + created() { + this.autocompleteCache = new AutocompleteCache(); + }, methods: { + fetchEmojis(search) { + return this.autocompleteCache.fetch({ + url: this.autocompleteAwardEmojisPath, + cacheName: 'emojis', + searchProperty: 'name', + search, + }); + }, + fetchLabels(search) { + return this.autocompleteCache.fetch({ + url: this.dashboardLabelsPath, + cacheName: 'labels', + searchProperty: 'title', + search, + }); + }, + fetchMilestones(search) { + return this.autocompleteCache.fetch({ + url: this.dashboardMilestonesPath, + cacheName: 'milestones', + searchProperty: 'title', + search, + }); + }, fetchUsers(search) { return axios.get('/-/autocomplete/users.json', { params: { active: true, search } }); }, @@ -266,7 +368,7 @@ export default { :has-scoped-labels-feature="hasScopedLabelsFeature" :initial-filter-value="filterTokens" :initial-sort-by="sortKey" - :issuables="issues" + :issuables="renderedIssues" :issuables-loading="$apollo.queries.issues.loading" namespace="dashboard" recent-searches-storage-key="issues" @@ -307,7 +409,11 @@ export default { </template> <template #empty-state> - <gl-empty-state :svg-path="emptyStateSvgPath" :title="$options.i18n.emptyStateTitle" /> + <gl-empty-state + :description="emptyStateDescription" + :svg-path="emptyStateSvgPath" + :title="emptyStateTitle" + /> </template> </issuable-list> </template> diff --git a/app/assets/javascripts/issues/dashboard/index.js b/app/assets/javascripts/issues/dashboard/index.js index e3e5cc614cb..005ab5ce3b0 100644 --- a/app/assets/javascripts/issues/dashboard/index.js +++ b/app/assets/javascripts/issues/dashboard/index.js @@ -14,8 +14,12 @@ export function mountIssuesDashboardApp() { Vue.use(VueApollo); const { + autocompleteAwardEmojisPath, calendarPath, - emptyStateSvgPath, + dashboardLabelsPath, + dashboardMilestonesPath, + emptyStateWithFilterSvgPath, + emptyStateWithoutFilterSvgPath, hasBlockedIssuesFeature, hasIssuableHealthStatusFeature, hasIssueWeightsFeature, @@ -33,8 +37,12 @@ export function mountIssuesDashboardApp() { defaultClient: createDefaultClient(), }), provide: { + autocompleteAwardEmojisPath, calendarPath, - emptyStateSvgPath, + dashboardLabelsPath, + dashboardMilestonesPath, + emptyStateWithFilterSvgPath, + emptyStateWithoutFilterSvgPath, hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature), hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature), hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), diff --git a/app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql b/app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql index 8ffcb456755..43b8804108c 100644 --- a/app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql +++ b/app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql @@ -7,8 +7,14 @@ query getDashboardIssues( $search: String $sort: IssueSort $state: IssuableState + $assigneeId: String $assigneeUsernames: [String!] $authorUsername: String + $labelName: [String] + $milestoneTitle: [String] + $milestoneWildcardId: MilestoneWildcardId + $myReactionEmoji: String + $not: NegatedIssueFilterInput $afterCursor: String $beforeCursor: String $firstPageSize: Int @@ -18,8 +24,14 @@ query getDashboardIssues( search: $search sort: $sort state: $state + assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + labelName: $labelName + milestoneTitle: $milestoneTitle + milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji + not: $not after: $afterCursor before: $beforeCursor first: $firstPageSize diff --git a/app/assets/javascripts/issues/dashboard/utils.js b/app/assets/javascripts/issues/dashboard/utils.js new file mode 100644 index 00000000000..6fa95b38649 --- /dev/null +++ b/app/assets/javascripts/issues/dashboard/utils.js @@ -0,0 +1,23 @@ +import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import { MAX_LIST_SIZE } from '~/issues/list/constants'; +import axios from '~/lib/utils/axios_utils'; + +export class AutocompleteCache { + constructor() { + this.cache = {}; + } + + fetch({ url, cacheName, searchProperty, search }) { + if (this.cache[cacheName]) { + const data = search + ? fuzzaldrinPlus.filter(this.cache[cacheName], search, { key: searchProperty }) + : this.cache[cacheName].slice(0, MAX_LIST_SIZE); + return Promise.resolve(data); + } + + return axios.get(url).then(({ data }) => { + this.cache[cacheName] = data; + return data.slice(0, MAX_LIST_SIZE); + }); + } +} diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index 12a83f06453..e4000184f41 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -352,6 +352,7 @@ export default { title: TOKEN_TITLE_LABEL, icon: 'labels', token: LabelToken, + operators: this.hasOrFeature ? OPERATORS_IS_NOT_OR : OPERATORS_IS_NOT, fetchLabels: this.fetchLabels, recentSuggestionsStorageKey: `${this.fullPath}-issues-recent-tokens-label`, }, diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js index 49a953cad43..87184799d5f 100644 --- a/app/assets/javascripts/issues/list/constants.js +++ b/app/assets/javascripts/issues/list/constants.js @@ -159,7 +159,7 @@ export const TYPE_TOKEN_OBJECTIVE_OPTION = { }; export const TYPE_TOKEN_KEY_RESULT_OPTION = { - icon: 'issue-type-key-result', + icon: 'issue-type-keyresult', title: 'key_result', value: 'key_result', }; @@ -247,6 +247,7 @@ export const filters = { [API_PARAM]: { [NORMAL_FILTER]: 'labelName', [SPECIAL_FILTER]: 'labelName', + [ALTERNATIVE_FILTER]: 'labelNames', }, [URL_PARAM]: { [OPERATOR_IS]: { @@ -257,6 +258,9 @@ export const filters = { [OPERATOR_NOT]: { [NORMAL_FILTER]: 'not[label_name][]', }, + [OPERATOR_OR]: { + [ALTERNATIVE_FILTER]: 'or[label_name][]', + }, }, }, [TOKEN_TYPE_TYPE]: { @@ -360,14 +364,17 @@ export const filters = { }, [TOKEN_TYPE_HEALTH]: { [API_PARAM]: { - [NORMAL_FILTER]: 'healthStatus', - [SPECIAL_FILTER]: 'healthStatus', + [NORMAL_FILTER]: 'healthStatusFilter', + [SPECIAL_FILTER]: 'healthStatusFilter', }, [URL_PARAM]: { [OPERATOR_IS]: { [NORMAL_FILTER]: 'health_status', [SPECIAL_FILTER]: 'health_status', }, + [OPERATOR_NOT]: { + [NORMAL_FILTER]: 'not[health_status]', + }, }, }, [TOKEN_TYPE_CONTACT]: { diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js index b566e08731c..bbd081843ca 100644 --- a/app/assets/javascripts/issues/list/utils.js +++ b/app/assets/javascripts/issues/list/utils.js @@ -13,6 +13,8 @@ import { TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE, TOKEN_TYPE_TYPE, + TOKEN_TYPE_HEALTH, + TOKEN_TYPE_LABEL, } from '~/vue_shared/components/filtered_search_bar/constants'; import { ALTERNATIVE_FILTER, @@ -252,8 +254,9 @@ const isSpecialFilter = (type, data) => { const getFilterType = ({ type, value: { data, operator } }) => { const isUnionedAuthor = type === TOKEN_TYPE_AUTHOR && operator === OPERATOR_OR; + const isUnionedLabel = type === TOKEN_TYPE_LABEL && operator === OPERATOR_OR; - if (isUnionedAuthor) { + if (isUnionedAuthor || isUnionedLabel) { return ALTERNATIVE_FILTER; } if (isSpecialFilter(type, data)) { @@ -267,8 +270,13 @@ const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_R const isWildcardValue = (tokenType, value) => wildcardTokens.includes(tokenType) && specialFilterValues.includes(value); +const isHealthStatusSpecialFilter = (tokenType, value) => + tokenType === TOKEN_TYPE_HEALTH && specialFilterValues.includes(value); + const requiresUpperCaseValue = (tokenType, value) => - tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value); + tokenType === TOKEN_TYPE_TYPE || + isWildcardValue(tokenType, value) || + isHealthStatusSpecialFilter(tokenType, value); const formatData = (token) => { if (requiresUpperCaseValue(token.type, token.value.data)) { diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue index 983e2e6530e..56e360c75e3 100644 --- a/app/assets/javascripts/issues/show/components/header_actions.vue +++ b/app/assets/javascripts/issues/show/components/header_actions.vue @@ -19,6 +19,7 @@ import { visitUrl } from '~/lib/utils/url_utility'; import { s__, __, sprintf } from '~/locale'; import eventHub from '~/notes/event_hub'; import Tracking from '~/tracking'; +import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; import promoteToEpicMutation from '../queries/promote_to_epic.mutation.graphql'; import updateIssueMutation from '../queries/update_issue.mutation.graphql'; import DeleteIssueModal from './delete_issue_modal.vue'; @@ -50,6 +51,7 @@ export default { GlDropdownItem, GlLink, GlModal, + AbuseCategorySelector, }, directives: { GlModal: GlModalDirective, @@ -93,13 +95,15 @@ export default { projectPath: { default: '', }, - reportAbusePath: { - default: '', - }, submitAsSpamPath: { default: '', }, }, + data() { + return { + isReportAbuseDrawerOpen: false, + }; + }, computed: { ...mapState(['isToggleStateButtonLoading']), ...mapGetters(['openState', 'getBlockedByIssues']), @@ -163,6 +167,9 @@ export default { this.invokeUpdateIssueMutation(); }, + toggleReportAbuseDrawer(isOpen) { + this.isReportAbuseDrawerOpen = isOpen; + }, invokeUpdateIssueMutation() { this.toggleStateButtonLoading(true); @@ -255,7 +262,7 @@ export default { <gl-dropdown-item v-if="canPromoteToEpic" @click="promoteToEpic"> {{ __('Promote to epic') }} </gl-dropdown-item> - <gl-dropdown-item v-if="!isIssueAuthor" :href="reportAbusePath"> + <gl-dropdown-item v-if="!isIssueAuthor" @click="toggleReportAbuseDrawer(true)"> {{ $options.i18n.reportAbuse }} </gl-dropdown-item> <gl-dropdown-item @@ -314,7 +321,7 @@ export default { > {{ __('Promote to epic') }} </gl-dropdown-item> - <gl-dropdown-item v-if="!isIssueAuthor" :href="reportAbusePath"> + <gl-dropdown-item v-if="!isIssueAuthor" @click="toggleReportAbuseDrawer(true)"> {{ $options.i18n.reportAbuse }} </gl-dropdown-item> <gl-dropdown-item @@ -360,5 +367,10 @@ export default { :modal-id="$options.deleteModalId" :title="deleteButtonText" /> + + <abuse-category-selector + :show-drawer="isReportAbuseDrawerOpen" + @close-drawer="toggleReportAbuseDrawer(false)" + /> </div> </template> diff --git a/app/assets/javascripts/issues/show/components/incidents/constants.js b/app/assets/javascripts/issues/show/components/incidents/constants.js index 22db19610c1..2fdae538902 100644 --- a/app/assets/javascripts/issues/show/components/incidents/constants.js +++ b/app/assets/javascripts/issues/show/components/incidents/constants.js @@ -12,6 +12,9 @@ export const timelineFormI18n = Object.freeze({ 'Incident|Something went wrong while creating the incident timeline event.', ), areaPlaceholder: s__('Incident|Timeline text...'), + areaDefaultMessage: s__('Incident|Incident'), + selectTags: __('Select tags'), + tagsLabel: __('Event tag (optional)'), save: __('Save'), cancel: __('Cancel'), delete: __('Delete'), @@ -42,4 +45,14 @@ export const timelineItemI18n = Object.freeze({ timeUTC: __('%{time} UTC'), }); +export const timelineEventTagsI18n = Object.freeze({ + startTime: __('Start time'), + endTime: __('End time'), +}); + export const MAX_TEXT_LENGTH = 280; + +export const TIMELINE_EVENT_TAGS = Object.values(timelineEventTagsI18n).map((item) => ({ + text: item, + value: item, +})); diff --git a/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue b/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue index 6bb72e82778..81111d42b39 100644 --- a/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue +++ b/app/assets/javascripts/issues/show/components/incidents/create_timeline_event.vue @@ -74,6 +74,7 @@ export default { incidentId: convertToGraphQLId(TYPE_ISSUE, this.issuableId), note: eventDetails.note, occurredAt: eventDetails.occurredAt, + timelineEventTagNames: eventDetails.timelineEventTags, }, }, update: this.updateCache, diff --git a/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue b/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue index 8cdd62ca9ef..4ef9b9c5a99 100644 --- a/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue +++ b/app/assets/javascripts/issues/show/components/incidents/edit_timeline_event.vue @@ -40,7 +40,7 @@ export default { :is-event-processed="editTimelineEventActive" :previous-occurred-at="event.occurredAt" :previous-note="event.note" - show-delete + is-editing @save-event="saveEvent" @cancel="$emit('hide-edit')" @delete="$emit('delete')" diff --git a/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue b/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue index f1a3aebc990..6648e20865d 100644 --- a/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue +++ b/app/assets/javascripts/issues/show/components/incidents/timeline_events_form.vue @@ -1,7 +1,9 @@ <script> -import { GlDatepicker, GlFormInput, GlFormGroup, GlButton } from '@gitlab/ui'; +import { GlDatepicker, GlFormInput, GlFormGroup, GlButton, GlListbox } from '@gitlab/ui'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; -import { MAX_TEXT_LENGTH, timelineFormI18n } from './constants'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { __, sprintf } from '~/locale'; +import { MAX_TEXT_LENGTH, TIMELINE_EVENT_TAGS, timelineFormI18n } from './constants'; import { getUtcShiftedDate } from './utils'; export default { @@ -23,7 +25,9 @@ export default { GlFormInput, GlFormGroup, GlButton, + GlListbox, }, + mixins: [glFeatureFlagsMixin()], i18n: timelineFormI18n, MAX_TEXT_LENGTH, props: { @@ -32,7 +36,7 @@ export default { required: false, default: false, }, - showDelete: { + isEditing: { type: Boolean, required: false, default: false, @@ -51,6 +55,16 @@ export default { required: false, default: '', }, + previousTags: { + type: Array, + required: false, + default: () => [], + }, + tags: { + type: Array, + required: false, + default: () => TIMELINE_EVENT_TAGS, + }, }, data() { // if occurredAt is null, returns "now" in UTC @@ -58,10 +72,12 @@ export default { return { timelineText: this.previousNote, + timelineTextIsDirty: this.isEditing, placeholderDate, hourPickerInput: placeholderDate.getHours(), minutePickerInput: placeholderDate.getMinutes(), datePickerInput: placeholderDate, + selectedTags: [...this.previousTags], }; }, computed: { @@ -85,6 +101,20 @@ export default { timelineTextCount() { return this.timelineText.length; }, + dropdownText() { + if (!this.selectedTags.length) { + return timelineFormI18n.selectTags; + } + + const dropdownText = + this.selectedTags.length === 1 + ? this.selectedTags[0] + : sprintf(__('%{numberOfSelectedTags} tags'), { + numberOfSelectedTags: this.selectedTags.length, + }); + + return dropdownText; + }, }, mounted() { this.focusDate(); @@ -96,14 +126,35 @@ export default { this.hourPickerInput = newPlaceholderDate.getHours(); this.minutePickerInput = newPlaceholderDate.getMinutes(); this.timelineText = ''; + this.selectedTags = []; }, focusDate() { this.$refs.datepicker.$el.querySelector('input')?.focus(); }, + setTimelineTextDirty() { + this.timelineTextIsDirty = true; + }, + onTagsChange(tagValue) { + this.selectedTags = [...tagValue]; + + if (!this.timelineTextIsDirty) { + this.timelineText = this.generateTimelineTextFromTags(this.selectedTags); + } + }, + generateTimelineTextFromTags(tags) { + if (!tags.length) { + return ''; + } + + const tagsMessage = tags.map((tag) => tag.toLocaleLowerCase()).join(', '); + + return `${timelineFormI18n.areaDefaultMessage} ${tagsMessage}`; + }, handleSave(addAnotherEvent) { const event = { note: this.timelineText, occurredAt: this.occurredAtString, + timelineEventTags: this.selectedTags, }; this.$emit('save-event', event, addAnotherEvent); }, @@ -146,6 +197,16 @@ export default { <p class="gl-ml-3 gl-align-self-end gl-line-height-32">{{ __('UTC') }}</p> </div> </div> + <gl-form-group v-if="glFeatures.incidentEventTags" :label="$options.i18n.tagsLabel"> + <gl-listbox + :selected="selectedTags" + :toggle-text="dropdownText" + :items="tags" + :is-check-centered="true" + :multiple="true" + @select="onTagsChange" + /> + </gl-form-group> <div class="common-note-form"> <gl-form-group class="gl-mb-3" :label="$options.i18n.areaLabel"> <markdown-field @@ -169,6 +230,7 @@ export default { aria-describedby="timeline-form-hint" :placeholder="$options.i18n.areaPlaceholder" :maxlength="$options.MAX_TEXT_LENGTH" + @input="setTimelineTextDirty" > </textarea> <div id="timeline-form-hint" class="gl-sr-only">{{ $options.i18n.hint }}</div> @@ -214,7 +276,7 @@ export default { {{ $options.i18n.cancel }} </gl-button> <gl-button - v-if="showDelete" + v-if="isEditing" class="gl-ml-auto btn-danger" :disabled="isEventProcessed" @click="$emit('delete')" diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index 3cb5007ab0d..21d877c5fe6 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -83,7 +83,7 @@ export function initIssueApp(issueData, store) { return undefined; } - const { fullPath } = el.dataset; + const { fullPath, registerPath, signInPath } = el.dataset; scrollToTargetOnResize(); @@ -99,6 +99,8 @@ export function initIssueApp(issueData, store) { provide: { canCreateIncident, fullPath, + registerPath, + signInPath, hasIssueWeightsFeature, }, computed: { @@ -150,6 +152,8 @@ export function initHeaderActions(store, type = '') { projectPath: el.dataset.projectPath, projectId: el.dataset.projectId, reportAbusePath: el.dataset.reportAbusePath, + reportedUserId: el.dataset.reportedUserId, + reportedFromUrl: el.dataset.reportedFromUrl, submitAsSpamPath: el.dataset.submitAsSpamPath, }, render: (createElement) => createElement(HeaderActions), |