Commit 5333cb6c authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 44baf08d
......@@ -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)
......
/* 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();
......
......@@ -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')
---
title: Add mb-2 class to global alerts
merge_request: 20081
author: 2knal
type: other
---
title: Fix expanding collapsed threads when reference link clicked
merge_request: 20148
author:
type: fixed
......@@ -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,
......
......@@ -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
......@@ -548,13 +548,18 @@ 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!]
......
......@@ -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,
......@@ -7995,9 +7995,27 @@
},
"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",
......
......@@ -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,
......
......@@ -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
......
......@@ -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 ""
......
......@@ -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();
});
});
});
/* 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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment