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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-14 09:09:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-14 09:09:22 +0300
commit067b3d04573d1473dbc6c81ef775d70c6636ff3f (patch)
treee05bfa986e49c1527fa93f03b2592f0c1a3da735 /app
parentef9eff8e7e1b38f48a354f90ecaeeb67da35b08c (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js2
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js97
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue13
-rw-r--r--app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql3
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/graphql/cache_update.js20
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql5
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/report.vue90
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/web_ide_link.vue2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss15
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml4
-rw-r--r--app/views/projects/merge_requests/show.html.haml4
-rw-r--r--app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml2
14 files changed, 192 insertions, 67 deletions
diff --git a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js
index f38e4514393..2c462cdde91 100644
--- a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js
+++ b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js
@@ -63,7 +63,7 @@ function maybeMerge(a, b) {
function createSourceMapAttributes(hastNode, source) {
const { position } = hastNode;
- return position.end
+ return position && position.end
? {
sourceMapKey: `${position.start.offset}:${position.end.offset}`,
sourceMarkdown: source.substring(position.start.offset, position.end.offset),
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index 055a32420b2..88f5192af77 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -12,22 +12,6 @@ const ignoreAttrs = {
const tableMap = new WeakMap();
-// Source taken from
-// prosemirror-markdown/src/to_markdown.js
-export function isPlainURL(link, parent, index, side) {
- if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) return false;
- const content = parent.child(index + (side < 0 ? -1 : 0));
- if (
- !content.isText ||
- content.text !== link.attrs.href ||
- content.marks[content.marks.length - 1] !== link
- )
- return false;
- if (index === (side < 0 ? 1 : parent.childCount - 1)) return true;
- const next = parent.child(index + (side < 0 ? -2 : 1));
- return !link.isInSet(next.marks);
-}
-
function containsOnlyText(node) {
if (node.childCount === 1) {
const child = node.child(0);
@@ -498,10 +482,79 @@ const linkType = (sourceMarkdown) => {
return LINK_HTML;
};
+const removeUrlProtocol = (url) => url.replace(/^\w+:\/?\/?/, '');
+
+const normalizeUrl = (url) => decodeURIComponent(removeUrlProtocol(url));
+
+/**
+ * Validates that the provided URL is well-formed
+ *
+ * @param {String} url
+ * @returns Returns true when the browser’s URL constructor
+ * can successfully parse the URL string
+ */
+const isValidUrl = (url) => {
+ try {
+ return new URL(url) && true;
+ } catch {
+ return false;
+ }
+};
+
+const findChildWithMark = (mark, parent) => {
+ let child;
+ let offset;
+ let index;
+
+ parent.forEach((_child, _offset, _index) => {
+ if (mark.isInSet(_child.marks)) {
+ child = _child;
+ offset = _offset;
+ index = _index;
+ }
+ });
+
+ return child ? { child, offset, index } : null;
+};
+
+/**
+ * This function detects whether a link should be serialized
+ * as an autolink.
+ *
+ * See https://github.github.com/gfm/#autolinks-extension-
+ * to understand the parsing rules of autolinks.
+ * */
+const isAutoLink = (linkMark, parent) => {
+ const { title, href } = linkMark.attrs;
+
+ if (title || !/^\w+:/.test(href)) {
+ return false;
+ }
+
+ const { child } = findChildWithMark(linkMark, parent);
+
+ if (
+ !child ||
+ !child.isText ||
+ !isValidUrl(href) ||
+ normalizeUrl(child.text) !== normalizeUrl(href)
+ ) {
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Returns true if the user used brackets to the define
+ * the autolink in the original markdown source
+ */
+const isBracketAutoLink = (sourceMarkdown) => /^<.+?>$/.test(sourceMarkdown);
+
export const link = {
- open(state, mark, parent, index) {
- if (isPlainURL(mark, parent, index, 1)) {
- return '<';
+ open(state, mark, parent) {
+ if (isAutoLink(mark, parent)) {
+ return isBracketAutoLink(mark.attrs.sourceMarkdown) ? '<' : '';
}
const { canonicalSrc, href, title, sourceMarkdown } = mark.attrs;
@@ -518,9 +571,9 @@ export const link = {
return openTag('a', attrs);
},
- close(state, mark, parent, index) {
- if (isPlainURL(mark, parent, index, -1)) {
- return '>';
+ close(state, mark, parent) {
+ if (isAutoLink(mark, parent)) {
+ return isBracketAutoLink(mark.attrs.sourceMarkdown) ? '>' : '';
}
const { canonicalSrc, href, title, sourceMarkdown } = mark.attrs;
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index a75262ee303..07316f9433a 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -19,8 +19,6 @@ import { scrollToElement } from '~/lib/utils/common_utils';
import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import FileIcon from '~/vue_shared/components/file_icon.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { DIFF_FILE_AUTOMATIC_COLLAPSE } from '../constants';
import { DIFF_FILE_HEADER } from '../i18n';
@@ -33,7 +31,6 @@ export default {
components: {
ClipboardButton,
GlIcon,
- FileIcon,
DiffStats,
GlBadge,
GlButton,
@@ -48,7 +45,7 @@ export default {
GlTooltip: GlTooltipDirective,
SafeHtml: GlSafeHtmlDirective,
},
- mixins: [glFeatureFlagsMixin(), IdState({ idProp: (vm) => vm.diffFile.file_hash })],
+ mixins: [IdState({ idProp: (vm) => vm.diffFile.file_hash })],
i18n: {
...DIFF_FILE_HEADER,
compareButtonLabel: __('Compare submodule commit revisions'),
@@ -301,14 +298,6 @@ export default {
:href="titleLink"
@click="handleFileNameClick"
>
- <file-icon
- v-if="!glFeatures.removeDiffHeaderIcons"
- :file-name="filePath"
- :size="16"
- aria-hidden="true"
- css-classes="gl-mr-2"
- :submodule="diffFile.submodule"
- />
<span v-if="isFileRenamed">
<strong
v-gl-tooltip
diff --git a/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
index 824997f8e33..fb771d7ec8a 100644
--- a/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
+++ b/app/assets/javascripts/graphql_shared/fragments/issuable_timelogs.fragment.graphql
@@ -11,4 +11,7 @@ fragment TimelogFragment on Timelog {
body
}
summary
+ userPermissions {
+ adminTimelog
+ }
}
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/graphql/cache_update.js b/app/assets/javascripts/sidebar/components/time_tracking/graphql/cache_update.js
new file mode 100644
index 00000000000..70177d84b1b
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/time_tracking/graphql/cache_update.js
@@ -0,0 +1,20 @@
+import produce from 'immer';
+
+export function removeTimelogFromStore(store, deletedTimelogId, query, variables) {
+ const sourceData = store.readQuery({
+ query,
+ variables,
+ });
+
+ const data = produce(sourceData, (draftData) => {
+ draftData.issuable.timelogs.nodes = draftData.issuable.timelogs.nodes.filter(
+ ({ id }) => id !== deletedTimelogId,
+ );
+ });
+
+ store.writeQuery({
+ query,
+ variables,
+ data,
+ });
+}
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql b/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql
new file mode 100644
index 00000000000..17bbad1acb1
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql
@@ -0,0 +1,5 @@
+mutation deleteTimelog($input: TimelogDeleteInput!) {
+ timelogDelete(input: $input) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/report.vue b/app/assets/javascripts/sidebar/components/time_tracking/report.vue
index b4c4c31bd7a..79ef5a32474 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/report.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/report.vue
@@ -1,11 +1,13 @@
<script>
-import { GlLoadingIcon, GlTableLite } from '@gitlab/ui';
+import { GlLoadingIcon, GlTableLite, GlButton, GlTooltipDirective } from '@gitlab/ui';
import createFlash from '~/flash';
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { formatDate, parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
-import { __ } from '~/locale';
+import { __, s__ } from '~/locale';
import { timelogQueries } from '~/sidebar/constants';
+import deleteTimelogMutation from './graphql/mutations/delete_timelog.mutation.graphql';
+import { removeTimelogFromStore } from './graphql/cache_update';
const TIME_DATE_FORMAT = 'mmmm d, yyyy, HH:MM ("UTC:" o)';
@@ -13,6 +15,10 @@ export default {
components: {
GlLoadingIcon,
GlTableLite,
+ GlButton,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
},
inject: ['issuableType'],
props: {
@@ -27,7 +33,7 @@ export default {
},
},
data() {
- return { report: [], isLoading: true };
+ return { report: [], isLoading: true, removingIds: [] };
},
apollo: {
report: {
@@ -35,9 +41,7 @@ export default {
return timelogQueries[this.issuableType].query;
},
variables() {
- return {
- id: convertToGraphQLId(this.getGraphQLEntityType(), this.issuableId),
- };
+ return this.getQueryVariables();
},
update(data) {
this.isLoading = false;
@@ -48,10 +52,23 @@ export default {
},
},
},
+ computed: {
+ deleteButtonTooltip() {
+ return s__('TimeTracking|Delete time spent');
+ },
+ },
methods: {
+ isDeletingTimelog(timelogId) {
+ return this.removingIds.includes(timelogId);
+ },
isIssue() {
return this.issuableType === 'issue';
},
+ getQueryVariables() {
+ return {
+ id: convertToGraphQLId(this.getGraphQLEntityType(), this.issuableId),
+ };
+ },
getGraphQLEntityType() {
return this.isIssue() ? TYPE_ISSUE : TYPE_MERGE_REQUEST;
},
@@ -76,12 +93,44 @@ export default {
stringifyTime(parseSeconds(seconds, { limitToHours: this.limitToHours }))
);
},
+ deleteTimelog(timelogId) {
+ this.removingIds.push(timelogId);
+ this.$apollo
+ .mutate({
+ mutation: deleteTimelogMutation,
+ variables: { input: { id: timelogId } },
+ update: (store) => {
+ removeTimelogFromStore(
+ store,
+ timelogId,
+ timelogQueries[this.issuableType].query,
+ this.getQueryVariables(),
+ );
+ },
+ })
+ .then(({ data }) => {
+ if (data.timelogDelete?.errors?.length) {
+ throw new Error(data.timelogDelete.errors[0]);
+ }
+ })
+ .catch((error) => {
+ createFlash({
+ message: s__('TimeTracking|An error occurred while removing the timelog.'),
+ captureError: true,
+ error,
+ });
+ })
+ .finally(() => {
+ this.removingIds.splice(this.removingIds.indexOf(timelogId), 1);
+ });
+ },
},
fields: [
- { key: 'spentAt', label: __('Spent At'), sortable: true, tdClass: 'gl-w-quarter' },
+ { key: 'spentAt', label: __('Spent at'), sortable: true, tdClass: 'gl-w-quarter' },
{ key: 'user', label: __('User'), sortable: true },
- { key: 'timeSpent', label: __('Time Spent'), sortable: true, tdClass: 'gl-w-15' },
- { key: 'summary', label: __('Summary / Note'), sortable: true },
+ { key: 'timeSpent', label: __('Time spent'), sortable: true, tdClass: 'gl-w-15' },
+ { key: 'summary', label: __('Summary / note'), sortable: true },
+ { key: 'actions', label: '', tdClass: 'gl-w-10' },
],
};
</script>
@@ -110,7 +159,28 @@ export default {
<template #cell(summary)="{ item: { summary, note } }">
<div>{{ getSummary(summary, note) }}</div>
</template>
- <template #foot(note)>&nbsp;</template>
+ <template #foot(summary)>&nbsp;</template>
+
+ <template
+ #cell(actions)="{
+ item: {
+ id,
+ userPermissions: { adminTimelog },
+ },
+ }"
+ >
+ <div v-if="adminTimelog">
+ <gl-button
+ v-gl-tooltip="{ title: deleteButtonTooltip }"
+ category="secondary"
+ icon="remove"
+ data-testid="deleteButton"
+ :loading="isDeletingTimelog(id)"
+ @click="deleteTimelog(id)"
+ />
+ </div>
+ </template>
+ <template #foot(actions)>&nbsp;</template>
</gl-table-lite>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index 057bb9f0100..e39d9f9fb49 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -252,6 +252,7 @@ export default {
size="lg"
:title="__('Time tracking report')"
:hide-footer="true"
+ @hide="refresh"
>
<time-tracking-report :limit-to-hours="limitToHours" :issuable-id="issuableId" />
</gl-modal>
diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
index 15f84e48179..cac0d5a45c9 100644
--- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue
+++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
@@ -307,7 +307,7 @@ export default {
<actions-button
:actions="actions"
:selected-key="selection"
- :variant="isBlob ? 'info' : 'default'"
+ :variant="isBlob ? 'confirm' : 'default'"
:category="isBlob ? 'primary' : 'secondary'"
@select="select"
/>
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index c1416e20aea..b63fd941a9b 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -200,11 +200,6 @@ $tabs-holder-z-index: 250;
}
}
-.assign-to-me-link {
- padding-left: 12px;
- white-space: nowrap;
-}
-
.table-holder {
.ci-table {
th {
@@ -252,13 +247,6 @@ $tabs-holder-z-index: 250;
}
}
-.merge-request-tabs {
- display: flex;
- flex-wrap: nowrap;
- margin-bottom: 0;
- padding: 0;
-}
-
.limit-container-width {
.merge-request-tabs-container {
max-width: $limited-layout-width;
@@ -274,9 +262,6 @@ $tabs-holder-z-index: 250;
}
.merge-request-tabs-container {
- display: flex;
- justify-content: space-between;
-
@include media-breakpoint-down(xs) {
.discussion-filter-container {
margin-bottom: $gl-padding-4;
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 5b4d79fab88..6dea73298a5 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -45,7 +45,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:realtime_labels, project)
push_frontend_feature_flag(:refactor_security_extension, @project)
push_frontend_feature_flag(:mr_attention_requests, current_user)
- push_frontend_feature_flag(:remove_diff_header_icons, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
push_frontend_feature_flag(:paginated_mr_discussions, project)
end
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 3c39c04d3a3..ef3174efcc7 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -18,11 +18,11 @@
= custom_icon ('illustration_no_commits')
- else
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
- .merge-request-tabs-container
+ .merge-request-tabs-container.gl-display-flex.gl-justify-content-space-between
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
- %ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.js-tabs-affix
+ %ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%li.commits-tab.new-tab
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
Commits
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 0ac5410eec9..e0adf34c6a8 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -21,8 +21,8 @@
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
= render "projects/merge_requests/mr_box"
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
- .merge-request-tabs-container{ class: "#{'is-merge-request' if Feature.enabled?(:moved_mr_sidebar, @project) && !fluid_layout}" }
- %ul.merge-request-tabs.nav-tabs.nav.nav-links{ class: "#{'gl-w-full gl-lg-w-auto!' if Feature.enabled?(:moved_mr_sidebar, @project)}" }
+ .merge-request-tabs-container.gl-display-flex.gl-justify-content-space-between{ class: "#{'is-merge-request' if Feature.enabled?(:moved_mr_sidebar, @project) && !fluid_layout}" }
+ %ul.merge-request-tabs.nav-tabs.nav.nav-links.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0{ class: "#{'gl-w-full gl-lg-w-auto!' if Feature.enabled?(:moved_mr_sidebar, @project)}" }
= render "projects/merge_requests/tabs/tab", class: "notes-tab", qa_selector: "notes_tab" do
= tab_link_for @merge_request, :show, force_link: @commit.present? do
= _("Overview")
diff --git a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
index 507c5a89649..f9c3c11eed8 100644
--- a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
+++ b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
@@ -8,4 +8,4 @@
= hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", 0, id: nil, data: { meta: '' }
= dropdown_tag(users_dropdown_label(issuable.assignees), options: assignees_dropdown_options(issuable.to_ability_name))
- = link_to _('Assign to me'), '#', class: "assign-to-me-link qa-assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"
+ = link_to _('Assign to me'), '#', class: "assign-to-me-link gl-white-space-nowrap gl-pl-4 qa-assign-to-me-link #{'hide' if issuable.assignees.include?(current_user)}"