diff options
21 files changed, 187 insertions, 25 deletions
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index aaa9f8b759a..58d5b658b17 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -49,6 +49,8 @@ export default { return this.author.id ? this.author.id : ''; }, authorUrl() { + // TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings + // name: 'mailto:' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives return this.author.web_url || `mailto:${this.commit.author_email}`; }, authorAvatar() { @@ -80,7 +82,7 @@ export default { v-html="commit.title_html" ></a> - <span class="commit-row-message d-block d-sm-none"> · {{ commit.short_id }} </span> + <span class="commit-row-message d-block d-sm-none">· {{ commit.short_id }}</span> <button v-if="commit.description_html" diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue index 80aec84f574..1dcdb65d5c7 100644 --- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue +++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue @@ -1,6 +1,6 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; -import { n__, __ } from '~/locale'; +import { n__, __, sprintf } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; export default { @@ -54,11 +54,7 @@ export default { }, methods: { commitsText(version) { - return n__( - `${version.commits_count} commit,`, - `${version.commits_count} commits,`, - version.commits_count, - ); + return n__(`%d commit,`, `%d commits,`, version.commits_count); }, href(version) { if (this.isBase(version)) { @@ -76,7 +72,7 @@ export default { if (this.targetBranch && (this.isBase(version) || !version)) { return this.targetBranch.branchName; } - return `version ${version.version_index}`; + return sprintf(__(`version %{versionIndex}`), { versionIndex: version.version_index }); }, isActive(version) { if (!version) { @@ -125,9 +121,9 @@ export default { <div> <strong> {{ versionName(version) }} - <template v-if="isBase(version)"> - (base) - </template> + <template v-if="isBase(version)">{{ + s__('DiffsCompareBaseBranch|(base)') + }}</template> </strong> </div> <div> diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index f5876a73eff..63350fafefa 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -151,21 +151,22 @@ export default { <div v-if="forkMessageVisible" class="js-file-fork-suggestion-section file-fork-suggestion"> <span class="file-fork-suggestion-note"> - You're not allowed to <span class="js-file-fork-suggestion-section-action">edit</span> files - in this project directly. Please fork this project, make your changes there, and submit a - merge request. + {{ sprintf(__("You're not allowed to %{tag_start}edit%{tag_end} files in this project + directly. Please fork this project, make your changes there, and submit a merge request."), + { tag_start: '<span class="js-file-fork-suggestion-section-action">', tag_end: '</span>' }) + }} </span> <a :href="file.fork_path" class="js-fork-suggestion-button btn btn-grouped btn-inverted btn-success" - >Fork</a + >{{ __('Fork') }}</a > <button class="js-cancel-fork-suggestion-button btn btn-grouped" type="button" @click="hideForkMessage" > - Cancel + {{ __('Cancel') }} </button> </div> <gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" /> diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue index 7cf3d90d468..e28909b7be3 100644 --- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue +++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue @@ -74,7 +74,7 @@ export default { <button v-if="discussionsExpanded" type="button" - aria-label="Show comments" + :aria-label="__('Show comments')" class="diff-notes-collapse js-diff-comment-avatar js-diff-comment-button" @click="toggleDiscussions" > diff --git a/app/assets/javascripts/manual_ordering.js b/app/assets/javascripts/manual_ordering.js new file mode 100644 index 00000000000..e16ddbfef7e --- /dev/null +++ b/app/assets/javascripts/manual_ordering.js @@ -0,0 +1,58 @@ +import Sortable from 'sortablejs'; +import { s__ } from '~/locale'; +import createFlash from '~/flash'; +import { + getBoardSortableDefaultOptions, + sortableStart, +} from '~/boards/mixins/sortable_default_options'; +import axios from '~/lib/utils/axios_utils'; + +const updateIssue = (url, issueList, { move_before_id, move_after_id }) => + axios + .put(`${url}/reorder`, { + move_before_id, + move_after_id, + group_full_path: issueList.dataset.groupFullPath, + }) + .catch(() => { + createFlash(s__("ManualOrdering|Couldn't save the order of the issues")); + }); + +const initManualOrdering = () => { + const issueList = document.querySelector('.manual-ordering'); + + if (!issueList || !(gon.features && gon.features.manualSorting)) { + return; + } + + Sortable.create( + issueList, + getBoardSortableDefaultOptions({ + scroll: true, + dataIdAttr: 'data-id', + fallbackOnBody: false, + group: { + name: 'issues', + }, + draggable: 'li.issue', + onStart: () => { + sortableStart(); + }, + onUpdate: event => { + const el = event.item; + + const url = el.getAttribute('url'); + + const prev = el.previousElementSibling; + const next = el.nextElementSibling; + + const beforeId = prev && parseInt(prev.dataset.id, 10); + const afterId = next && parseInt(next.dataset.id, 10); + + updateIssue(url, issueList, { move_after_id: afterId, move_before_id: beforeId }); + }, + }), + ); +}; + +export default initManualOrdering; diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js index 9055738f86e..2ffeed8a584 100644 --- a/app/assets/javascripts/pages/dashboard/issues/index.js +++ b/app/assets/javascripts/pages/dashboard/issues/index.js @@ -2,6 +2,7 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { initFilteredSearch({ @@ -10,4 +11,5 @@ document.addEventListener('DOMContentLoaded', () => { }); projectSelect(); + initManualOrdering(); }); diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 35d4b034654..23fb5656008 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -2,6 +2,7 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); @@ -12,4 +13,5 @@ document.addEventListener('DOMContentLoaded', () => { filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); projectSelect(); + initManualOrdering(); }); diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index c34aff02111..c73ebb31eb3 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -7,6 +7,7 @@ import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); @@ -19,4 +20,5 @@ document.addEventListener('DOMContentLoaded', () => { new ShortcutsNavigation(); new UsersSelect(); + initManualOrdering(); }); diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 48289c8f381..8359a60ec9f 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -1,4 +1,18 @@ .issues-list { + &.manual-ordering { + background-color: $gray-light; + border-radius: $border-radius-default; + padding: $gl-padding-8; + + .issue { + background-color: $white-light; + margin-bottom: $gl-padding-8; + border-radius: $border-radius-default; + border: 1px solid $gray-100; + box-shadow: 0 1px 2px $issue-boards-card-shadow; + } + } + .issue { padding: 10px 0 10px $gl-padding; position: relative; diff --git a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb index 426f224d26b..f47ead2f0da 100644 --- a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb +++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb @@ -14,6 +14,10 @@ module RequiresWhitelistedMonitoringClient end def client_ip_whitelisted? + # Always allow developers to access http://localhost:3000/-/metrics for + # debugging purposes + return true if Rails.env.development? && request.local? + ip_whitelist.any? { |e| e.include?(Gitlab::RequestContext.client_ip) } end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index e936d771502..316da8f129d 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -7,6 +7,10 @@ class GroupsController < Groups::ApplicationController include PreviewMarkdown include RecordUserLastActivity + before_action do + push_frontend_feature_flag(:manual_sorting) + end + respond_to :html prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) } diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index b16f3dd9d82..f221f0363d3 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -10,6 +10,10 @@ class Projects::IssuesController < Projects::ApplicationController include SpammableActions include RecordUserLastActivity + before_action do + push_frontend_feature_flag(:manual_sorting) + end + def issue_except_actions %i[index calendar new create bulk_update import_csv] end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 59332c0b100..dfadcfc33b2 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -5,6 +5,7 @@ module IssuesHelper classes = ["issue"] classes << "closed" if issue.closed? classes << "today" if issue.today? + classes << "user-can-drag" if @sort == 'relative_position' classes.join(' ') end diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 6f7713124ac..7d539c9d749 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,6 +1,6 @@ - empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues') -%ul.content-list.issues-list.issuable-list +%ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position') } = render partial: "projects/issues/issue", collection: @issues - if @issues.blank? = render empty_state_path diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 987a5d4f13f..a21dcabb485 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,6 +1,6 @@ - if @issues.to_a.any? .card.card-small.card-without-border - %ul.content-list.issues-list.issuable-list + %ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position'), data: { group_full_path: @group&.full_path } } = render partial: 'projects/issues/issue', collection: @issues = paginate @issues, theme: "gitlab" - else diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml index 1dd97bc4ed1..403e001bfe8 100644 --- a/app/views/shared/issuable/_sort_dropdown.html.haml +++ b/app/views/shared/issuable/_sort_dropdown.html.haml @@ -1,6 +1,7 @@ - sort_value = @sort - sort_title = issuable_sort_option_title(sort_value) - viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues' +- manual_sorting = viewing_issues && controller.controller_name != 'dashboard' && Feature.enabled?(:manual_sorting) .dropdown.inline.prepend-left-10.issue-sort-dropdown .btn-group{ role: 'group' } @@ -17,6 +18,6 @@ = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title) = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title) - = sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues && Feature.enabled?(:manual_sorting) + = sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if manual_sorting = render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title) = issuable_sort_direction_button(sort_value) diff --git a/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml b/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml new file mode 100644 index 00000000000..acd944ea684 --- /dev/null +++ b/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml @@ -0,0 +1,5 @@ +--- +title: Always allow access to health endpoints from localhost in dev +merge_request: 29930 +author: +type: other diff --git a/changelogs/unreleased/fe-issue-reorder.yml b/changelogs/unreleased/fe-issue-reorder.yml new file mode 100644 index 00000000000..aca334b6149 --- /dev/null +++ b/changelogs/unreleased/fe-issue-reorder.yml @@ -0,0 +1,5 @@ +--- +title: Bring Manual Ordering on Issue List +merge_request: 29410 +author: +type: added diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1a9e8ae7288..5245bfffb03 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -41,6 +41,11 @@ msgid_plural "%d commits behind" msgstr[0] "" msgstr[1] "" +msgid "%d commit," +msgid_plural "%d commits," +msgstr[0] "" +msgstr[1] "" + msgid "%d commits" msgstr "" @@ -3529,6 +3534,9 @@ msgstr "" msgid "Diff limits" msgstr "" +msgid "DiffsCompareBaseBranch|(base)" +msgstr "" + msgid "Diffs|No file name available" msgstr "" @@ -6003,6 +6011,9 @@ msgstr "" msgid "Manual job" msgstr "" +msgid "ManualOrdering|Couldn't save the order of the issues" +msgstr "" + msgid "Map a FogBugz account ID to a GitLab user" msgstr "" @@ -9191,6 +9202,9 @@ msgstr "" msgid "Show command" msgstr "" +msgid "Show comments" +msgstr "" + msgid "Show comments only" msgstr "" @@ -12684,6 +12698,9 @@ msgstr "" msgid "verify ownership" msgstr "" +msgid "version %{versionIndex}" +msgstr "" + msgid "via %{closed_via}" msgstr "" diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb index f6f0468e76e..796de44a012 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/49 - context 'Create', :smoke, :quarantine do + context 'Create', :smoke do describe 'Snippet creation' do it 'User creates a snippet' do Runtime::Browser.visit(:gitlab, Page::Main::Login) @@ -13,7 +12,7 @@ module QA Resource::Snippet.fabricate_via_browser_ui! do |snippet| snippet.title = 'Snippet title' snippet.description = 'Snippet description' - snippet.visibility = 'Public' + snippet.visibility = 'Private' snippet.file_name = 'New snippet file name' snippet.file_content = 'Snippet file text' end @@ -21,8 +20,7 @@ module QA Page::Dashboard::Snippet::Show.perform do |snippet| expect(snippet).to have_snippet_title('Snippet title') expect(snippet).to have_snippet_description('Snippet description') - expect(snippet).to have_embed_type('Embed') - expect(snippet).to have_visibility_type('Public') + expect(snippet).to have_visibility_type('Private') expect(snippet).to have_file_name('New snippet file name') expect(snippet).to have_file_content('Snippet file text') end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 176f4a668ff..2e7525e0513 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe 'Group issues page' do include FilteredSearchHelpers + include DragTo let(:group) { create(:group) } let(:project) { create(:project, :public, group: group)} @@ -99,4 +100,49 @@ describe 'Group issues page' do end end end + + context 'manual ordering' do + let!(:issue1) { create(:issue, project: project, title: 'Issue #1') } + let!(:issue2) { create(:issue, project: project, title: 'Issue #2') } + let!(:issue3) { create(:issue, project: project, title: 'Issue #3') } + + it 'displays all issues' do + visit issues_group_path(group, sort: 'relative_position') + + page.within('.issues-list') do + expect(page).to have_selector('li.issue', count: 3) + end + end + + it 'has manual-ordering css applied' do + visit issues_group_path(group, sort: 'relative_position') + + expect(page).to have_selector('.manual-ordering') + end + + it 'each issue item has a user-can-drag css applied' do + visit issues_group_path(group, sort: 'relative_position') + + page.within('.manual-ordering') do + expect(page).to have_selector('.issue.user-can-drag', count: 3) + end + end + + it 'issues should be draggable and persist order', :js do + visit issues_group_path(group, sort: 'relative_position') + + drag_to(selector: '.manual-ordering', + scrollable: '#board-app', + list_from_index: 0, + from_index: 0, + to_index: 2, + list_to_index: 0) + + page.within('.manual-ordering') do + expect(find('.issue:nth-child(1) .title')).to have_content('Issue #2') + expect(find('.issue:nth-child(2) .title')).to have_content('Issue #1') + expect(find('.issue:nth-child(3) .title')).to have_content('Issue #3') + end + end + end end |