summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue13
-rw-r--r--app/assets/javascripts/project_find_file.js48
-rw-r--r--app/views/layouts/_flash.html.haml2
-rw-r--r--changelogs/unreleased/20081-add-mb-2-class-to-global-alerts.yml5
-rw-r--r--changelogs/unreleased/28985-links-to-notes-in-collapsed-discussions-dont-work.yml5
-rw-r--r--doc/administration/geo/replication/troubleshooting.md13
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql9
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json22
-rw-r--r--doc/ci/img/junit_test_report_ui.pngbin84300 -> 0 bytes
-rw-r--r--doc/ci/img/pipelines_junit_test_report_ui_v12_5.pngbin0 -> 15957 bytes
-rw-r--r--doc/ci/junit_test_reports.md2
-rw-r--r--doc/development/api_graphql_styleguide.md26
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/frontend/notes/components/note_app_spec.js24
-rw-r--r--spec/javascripts/search_autocomplete_spec.js68
15 files changed, 163 insertions, 83 deletions
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index c6c97489e5e..9d1de4ef8a0 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -122,6 +122,8 @@ export default {
this.toggleAward({ awardName, noteId });
});
}
+
+ window.addEventListener('hashchange', this.handleHashChanged);
},
updated() {
this.$nextTick(() => {
@@ -131,6 +133,7 @@ export default {
},
beforeDestroy() {
this.stopPolling();
+ window.removeEventListener('hashchange', this.handleHashChanged);
},
methods: {
...mapActions([
@@ -138,7 +141,6 @@ export default {
'fetchDiscussions',
'poll',
'toggleAward',
- 'scrollToNoteIfNeeded',
'setNotesData',
'setNoteableData',
'setUserData',
@@ -151,6 +153,13 @@ export default {
'convertToDiscussion',
'stopPolling',
]),
+ handleHashChanged() {
+ const noteId = this.checkLocationHash();
+
+ if (noteId) {
+ this.setTargetNoteHash(getLocationHash());
+ }
+ },
fetchNotes() {
if (this.isFetching) return null;
@@ -194,6 +203,8 @@ export default {
this.expandDiscussion({ discussionId: discussion.id });
}
}
+
+ return noteId;
},
startReplying(discussionId) {
return this.convertToDiscussion(discussionId)
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 58f088444d0..031c54d2336 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, no-var, consistent-return, one-var, no-cond-assign, no-return-assign */
+/* eslint-disable func-names, consistent-return, no-return-assign */
import $ from 'jquery';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
@@ -9,9 +9,12 @@ import sanitize from 'sanitize-html';
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
const highlighter = function(element, text, matches) {
- var j, lastIndex, len, matchIndex, matchedChars, unmatched;
- lastIndex = 0;
- matchedChars = [];
+ let j = 0;
+ let len = 0;
+ let lastIndex = 0;
+ let matchedChars = [];
+ let matchIndex = matches[j];
+ let unmatched = text.substring(lastIndex, matchIndex);
for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j];
unmatched = text.substring(lastIndex, matchIndex);
@@ -55,10 +58,10 @@ export default class ProjectFindFile {
'keyup',
(function(_this) {
return function(event) {
- var oldValue, ref, target, value;
- target = $(event.target);
- value = target.val();
- oldValue = (ref = target.data('oldValue')) != null ? ref : '';
+ const target = $(event.target);
+ const value = target.val();
+ const ref = target.data('oldValue');
+ const oldValue = ref != null ? ref : '';
if (value !== oldValue) {
target.data('oldValue', value);
_this.findFile();
@@ -74,9 +77,8 @@ export default class ProjectFindFile {
}
findFile() {
- var result, searchText;
- searchText = sanitize(this.inputElement.val());
- result =
+ const searchText = sanitize(this.inputElement.val());
+ const result =
searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
return this.renderList(result, searchText);
// find file
@@ -101,20 +103,21 @@ export default class ProjectFindFile {
// render result
renderList(filePaths, searchText) {
- var blobItemUrl, filePath, html, i, len, matches, results;
+ let i = 0;
+ let len = 0;
+ let matches = [];
+ const results = [];
this.element.find('.tree-table > tbody').empty();
- results = [];
-
for (i = 0, len = filePaths.length; i < len; i += 1) {
- filePath = filePaths[i];
+ const filePath = filePaths[i];
if (i === 20) {
break;
}
if (searchText) {
matches = fuzzaldrinPlus.match(filePath, searchText);
}
- blobItemUrl = `${this.options.blobUrlTemplate}/${encodeURIComponent(filePath)}`;
- html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
+ const blobItemUrl = `${this.options.blobUrlTemplate}/${encodeURIComponent(filePath)}`;
+ const html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find('.tree-table > tbody').append(html));
}
@@ -125,8 +128,7 @@ export default class ProjectFindFile {
// make tbody row html
static makeHtml(filePath, matches, blobItemUrl) {
- var $tr;
- $tr = $(
+ const $tr = $(
"<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>",
);
if (matches) {
@@ -141,9 +143,9 @@ export default class ProjectFindFile {
}
selectRow(type) {
- var next, rows, selectedRow;
- rows = this.element.find('.files-slider tr.tree-item');
- selectedRow = this.element.find('.files-slider tr.tree-item.selected');
+ const rows = this.element.find('.files-slider tr.tree-item');
+ let selectedRow = this.element.find('.files-slider tr.tree-item.selected');
+ let next = selectedRow.prev();
if (rows && rows.length > 0) {
if (selectedRow && selectedRow.length > 0) {
if (type === 'UP') {
@@ -175,7 +177,7 @@ export default class ProjectFindFile {
}
goToBlob() {
- var $link = this.element.find('.tree-item.selected .tree-item-file-name a');
+ const $link = this.element.find('.tree-item.selected .tree-item-file-name a');
if ($link.length) {
$link.get(0).click();
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index a0815f3a565..a0b030fa3b2 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -3,7 +3,7 @@
- flash.each do |key, value|
-# Don't show a flash message if the message is nil
- if value
- %div{ class: "flash-#{key}" }
+ %div{ class: "flash-#{key} mb-2" }
%span= value
%div{ class: "close-icon-wrapper js-close-icon" }
= sprite_icon('close', size: 16, css_class: 'close-icon')
diff --git a/changelogs/unreleased/20081-add-mb-2-class-to-global-alerts.yml b/changelogs/unreleased/20081-add-mb-2-class-to-global-alerts.yml
new file mode 100644
index 00000000000..2aea916402e
--- /dev/null
+++ b/changelogs/unreleased/20081-add-mb-2-class-to-global-alerts.yml
@@ -0,0 +1,5 @@
+---
+title: Add mb-2 class to global alerts
+merge_request: 20081
+author: 2knal
+type: other
diff --git a/changelogs/unreleased/28985-links-to-notes-in-collapsed-discussions-dont-work.yml b/changelogs/unreleased/28985-links-to-notes-in-collapsed-discussions-dont-work.yml
new file mode 100644
index 00000000000..46d1e94829b
--- /dev/null
+++ b/changelogs/unreleased/28985-links-to-notes-in-collapsed-discussions-dont-work.yml
@@ -0,0 +1,5 @@
+---
+title: Fix expanding collapsed threads when reference link clicked
+merge_request: 20148
+author:
+type: fixed
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index 5c40a4441b6..d2fe02abbab 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -366,7 +366,7 @@ to start again from scratch, there are a few steps that can help you:
gitlab-ctl tail sidekiq
```
-1. Rename repository storage folders and create new ones
+1. Rename repository storage folders and create new ones. If you are not concerned about possible orphaned directories and files, then you can simply skip this step.
```sh
mv /var/opt/gitlab/git-data/repositories /var/opt/gitlab/git-data/repositories.old
@@ -413,7 +413,9 @@ to start again from scratch, there are a few steps that can help you:
1. Reset the Tracking Database
```sh
- gitlab-rake geo:db:reset
+ gitlab-rake geo:db:drop
+ gitlab-ctl reconfigure
+ gitlab-rake geo:db:setup
```
1. Restart previously stopped services
@@ -653,13 +655,6 @@ Geo cannot reuse an existing tracking database.
It is safest to use a fresh secondary, or reset the whole secondary by following
[Resetting Geo secondary node replication](#resetting-geo-secondary-node-replication).
-If you are not concerned about possible orphaned directories and files, then you
-can simply reset the existing tracking database with:
-
-```sh
-sudo gitlab-rake geo:db:reset
-```
-
### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node
This error refers to a problem with the database replica on a **secondary** node,
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index ecb7f04318a..f79122538a8 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -539,7 +539,7 @@ type DesignCollection {
"""
Filters designs to only those that existed at the version. If argument is
- omitted or nil then all designs will reflect the latest version.
+ omitted or nil then all designs will reflect the latest version
"""
atVersion: ID
@@ -549,12 +549,17 @@ type DesignCollection {
before: String
"""
+ Filters designs by their filename
+ """
+ filenames: [String!]
+
+ """
Returns the first _n_ elements from the list.
"""
first: Int
"""
- The list of IDs of designs.
+ Filters designs by their ID
"""
ids: [ID!]
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index b8d788fb6ec..eee98255367 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -7979,7 +7979,7 @@
"args": [
{
"name": "ids",
- "description": "The list of IDs of designs.",
+ "description": "Filters designs by their ID",
"type": {
"kind": "LIST",
"name": null,
@@ -7996,8 +7996,26 @@
"defaultValue": null
},
{
+ "name": "filenames",
+ "description": "Filters designs by their filename",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
"name": "atVersion",
- "description": "Filters designs to only those that existed at the version. If argument is omitted or nil then all designs will reflect the latest version.",
+ "description": "Filters designs to only those that existed at the version. If argument is omitted or nil then all designs will reflect the latest version",
"type": {
"kind": "SCALAR",
"name": "ID",
diff --git a/doc/ci/img/junit_test_report_ui.png b/doc/ci/img/junit_test_report_ui.png
deleted file mode 100644
index 380c6bbb89c..00000000000
--- a/doc/ci/img/junit_test_report_ui.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/img/pipelines_junit_test_report_ui_v12_5.png b/doc/ci/img/pipelines_junit_test_report_ui_v12_5.png
new file mode 100644
index 00000000000..5b1e3254f8b
--- /dev/null
+++ b/doc/ci/img/pipelines_junit_test_report_ui_v12_5.png
Binary files differ
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index 3a94e9725e7..c03d1798ae1 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -187,7 +187,7 @@ If JUnit XML files are generated and uploaded as part of a pipeline, these repor
can be viewed inside the pipelines details page. The **Tests** tab on this page will
display a list of test suites and cases reported from the XML file.
-![Test Reports Widget](img/junit_test_report_ui.png)
+![Test Reports Widget](img/pipelines_junit_test_report_ui_v12_5.png)
You can view all the known test suites and click on each of these to see further
details, including the cases that makeup the suite. Cases are ordered by status,
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 7161caf19ed..b0d94511c6e 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -548,6 +548,32 @@ found, we should raise a
`Gitlab::Graphql::Errors::ResourceNotAvailable` error. Which will be
correctly rendered to the clients.
+## Gitlab's custom scalars
+
+### `Types::TimeType`
+
+[`Types::TimeType`](https://gitlab.com/gitlab-org/gitlab/blob/master/app%2Fgraphql%2Ftypes%2Ftime_type.rb)
+must be used as the type for all fields and arguments that deal with Ruby
+`Time` and `DateTime` objects.
+
+The type is
+[a custom scalar](https://github.com/rmosolgo/graphql-ruby/blob/master/guides/type_definitions/scalars.md#custom-scalars)
+that:
+
+- Converts Ruby's `Time` and `DateTime` objects into standardized
+ ISO-8601 formatted strings, when used as the type for our GraphQL fields.
+- Converts ISO-8601 formatted time strings into Ruby `Time` objects,
+ when used as the type for our GraphQL arguments.
+
+This allows our GraphQL API to have a standardized way that it presents time
+and handles time inputs.
+
+Example:
+
+```ruby
+field :created_at, Types::TimeType, null: false, description: 'Timestamp of when the issue was created'
+```
+
## Testing
_full stack_ tests for a graphql query or mutation live in
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9be13d4e214..81709437eef 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5800,6 +5800,9 @@ msgstr ""
msgid "DesignManagement|Upload and view the latest designs for this issue. Consistent and easy to find, so everyone is up to date."
msgstr ""
+msgid "DesignManagement|We could not delete %{design}. Please try again."
+msgstr ""
+
msgid "DesignManagement|We could not delete design(s). Please try again."
msgstr ""
@@ -20127,6 +20130,9 @@ msgstr ""
msgid "a deleted user"
msgstr ""
+msgid "a design"
+msgstr ""
+
msgid "added %{created_at_timeago}"
msgstr ""
@@ -20521,6 +20527,9 @@ msgstr ""
msgid "design"
msgstr ""
+msgid "designs"
+msgstr ""
+
msgid "detached"
msgstr ""
diff --git a/spec/frontend/notes/components/note_app_spec.js b/spec/frontend/notes/components/note_app_spec.js
index b0271d30872..3716b349210 100644
--- a/spec/frontend/notes/components/note_app_spec.js
+++ b/spec/frontend/notes/components/note_app_spec.js
@@ -10,6 +10,7 @@ import '~/behaviors/markdown/render_gfm';
import { setTestTimeout } from 'helpers/timeout';
// TODO: use generated fixture (https://gitlab.com/gitlab-org/gitlab-foss/issues/62491)
import * as mockData from '../../notes/mock_data';
+import * as urlUtility from '~/lib/utils/url_utility';
setTestTimeout(1000);
@@ -54,7 +55,9 @@ describe('note_app', () => {
components: {
NotesApp,
},
- template: '<div class="js-vue-notes-event"><notes-app v-bind="$attrs" /></div>',
+ template: `<div class="js-vue-notes-event">
+ <notes-app ref="notesApp" v-bind="$attrs" />
+ </div>`,
},
{
attachToDocument: true,
@@ -313,4 +316,23 @@ describe('note_app', () => {
});
});
});
+
+ describe('mounted', () => {
+ beforeEach(() => {
+ axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
+ wrapper = mountComponent();
+ return waitForDiscussionsRequest();
+ });
+
+ it('should listen hashchange event', () => {
+ const notesApp = wrapper.find(NotesApp);
+ const hash = 'some dummy hash';
+ jest.spyOn(urlUtility, 'getLocationHash').mockReturnValueOnce(hash);
+ const setTargetNoteHash = jest.spyOn(notesApp.vm, 'setTargetNoteHash');
+
+ window.dispatchEvent(new Event('hashchange'), hash);
+
+ expect(setTargetNoteHash).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 9702cb56d99..1798f9962e2 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-var, one-var, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, vars-on-top */
+/* eslint-disable no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign */
import $ from 'jquery';
import '~/gl_dropdown';
@@ -6,41 +6,27 @@ import initSearchAutocomplete from '~/search_autocomplete';
import '~/lib/utils/common_utils';
describe('Search autocomplete dropdown', () => {
- var assertLinks,
- dashboardIssuesPath,
- dashboardMRsPath,
- groupIssuesPath,
- groupMRsPath,
- groupName,
- mockDashboardOptions,
- mockGroupOptions,
- mockProjectOptions,
- projectIssuesPath,
- projectMRsPath,
- projectName,
- userId,
- widget;
- var userName = 'root';
+ let widget = null;
- widget = null;
+ const userName = 'root';
- userId = 1;
+ const userId = 1;
- dashboardIssuesPath = '/dashboard/issues';
+ const dashboardIssuesPath = '/dashboard/issues';
- dashboardMRsPath = '/dashboard/merge_requests';
+ const dashboardMRsPath = '/dashboard/merge_requests';
- projectIssuesPath = '/gitlab-org/gitlab-foss/issues';
+ const projectIssuesPath = '/gitlab-org/gitlab-foss/issues';
- projectMRsPath = '/gitlab-org/gitlab-foss/merge_requests';
+ const projectMRsPath = '/gitlab-org/gitlab-foss/merge_requests';
- groupIssuesPath = '/groups/gitlab-org/issues';
+ const groupIssuesPath = '/groups/gitlab-org/issues';
- groupMRsPath = '/groups/gitlab-org/merge_requests';
+ const groupMRsPath = '/groups/gitlab-org/merge_requests';
- projectName = 'GitLab Community Edition';
+ const projectName = 'GitLab Community Edition';
- groupName = 'Gitlab Org';
+ const groupName = 'Gitlab Org';
const removeBodyAttributes = function() {
const $body = $('body');
@@ -76,7 +62,7 @@ describe('Search autocomplete dropdown', () => {
};
// Mock `gl` object in window for dashboard specific page. App code will need it.
- mockDashboardOptions = function() {
+ const mockDashboardOptions = function() {
window.gl || (window.gl = {});
return (window.gl.dashboardOptions = {
issuesPath: dashboardIssuesPath,
@@ -85,7 +71,7 @@ describe('Search autocomplete dropdown', () => {
};
// Mock `gl` object in window for project specific page. App code will need it.
- mockProjectOptions = function() {
+ const mockProjectOptions = function() {
window.gl || (window.gl = {});
return (window.gl.projectOptions = {
'gitlab-ce': {
@@ -96,7 +82,7 @@ describe('Search autocomplete dropdown', () => {
});
};
- mockGroupOptions = function() {
+ const mockGroupOptions = function() {
window.gl || (window.gl = {});
return (window.gl.groupOptions = {
'gitlab-org': {
@@ -107,7 +93,7 @@ describe('Search autocomplete dropdown', () => {
});
};
- assertLinks = function(list, issuesPath, mrsPath) {
+ const assertLinks = function(list, issuesPath, mrsPath) {
if (issuesPath) {
const issuesAssignedToMeLink = `a[href="${issuesPath}/?assignee_username=${userName}"]`;
const issuesIHaveCreatedLink = `a[href="${issuesPath}/?author_username=${userName}"]`;
@@ -144,29 +130,26 @@ describe('Search autocomplete dropdown', () => {
});
it('should show Dashboard specific dropdown menu', function() {
- var list;
addBodyAttributes();
mockDashboardOptions();
widget.searchInput.triggerHandler('focus');
- list = widget.wrap.find('.dropdown-menu').find('ul');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
});
it('should show Group specific dropdown menu', function() {
- var list;
addBodyAttributes('group');
mockGroupOptions();
widget.searchInput.triggerHandler('focus');
- list = widget.wrap.find('.dropdown-menu').find('ul');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, groupIssuesPath, groupMRsPath);
});
it('should show Project specific dropdown menu', function() {
- var list;
addBodyAttributes('project');
mockProjectOptions();
widget.searchInput.triggerHandler('focus');
- list = widget.wrap.find('.dropdown-menu').find('ul');
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, projectIssuesPath, projectMRsPath);
});
@@ -180,26 +163,25 @@ describe('Search autocomplete dropdown', () => {
});
it('should not show category related menu if there is text in the input', function() {
- var link, list;
addBodyAttributes('project');
mockProjectOptions();
widget.searchInput.val('help');
widget.searchInput.triggerHandler('focus');
- list = widget.wrap.find('.dropdown-menu').find('ul');
- link = `a[href='${projectIssuesPath}/?assignee_username=${userName}']`;
+ const list = widget.wrap.find('.dropdown-menu').find('ul');
+ const link = `a[href='${projectIssuesPath}/?assignee_username=${userName}']`;
expect(list.find(link).length).toBe(0);
});
it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
- var ENTER = 13;
- var DOWN = 40;
+ const ENTER = 13;
+ const DOWN = 40;
addBodyAttributes();
mockDashboardOptions(true);
- var submitSpy = spyOnEvent('form', 'submit');
+ const submitSpy = spyOnEvent('form', 'submit');
widget.searchInput.triggerHandler('focus');
widget.wrap.trigger($.Event('keydown', { which: DOWN }));
- var enterKeyEvent = $.Event('keydown', { which: ENTER });
+ const enterKeyEvent = $.Event('keydown', { which: ENTER });
widget.searchInput.trigger(enterKeyEvent);
// This does not currently catch failing behavior. For security reasons,
// browsers will not trigger default behavior (form submit, in this