Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-10-24 06:07:07 +00:00
parent 24ed154fa8
commit 12287a65b7
21 changed files with 177 additions and 90 deletions

View file

@ -0,0 +1,5 @@
// capture anything starting with http:// or https://
// up until a disallowed character or whitespace
export const blobLinkRegex = /https?:\/\/[^"<>\\^`{|}\s]+/g;
export default { blobLinkRegex };

View file

@ -4,6 +4,10 @@ import Flash from '../../flash';
import { handleLocationHash } from '../../lib/utils/common_utils'; import { handleLocationHash } from '../../lib/utils/common_utils';
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { blobLinkRegex } from '~/blob/blob_utils';
const SIMPLE_VIEWER_NAME = 'simple';
const RICH_VIEWER_NAME = 'rich';
export default class BlobViewer { export default class BlobViewer {
constructor() { constructor() {
@ -21,7 +25,7 @@ export default class BlobViewer {
} }
static initRichViewer() { static initRichViewer() {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]'); const viewer = document.querySelector(`.blob-viewer[data-type="${RICH_VIEWER_NAME}"]`);
if (!viewer || !viewer.dataset.richType) return; if (!viewer || !viewer.dataset.richType) return;
const initViewer = promise => const initViewer = promise =>
@ -61,8 +65,12 @@ export default class BlobViewer {
this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn'); this.switcherBtns = document.querySelectorAll('.js-blob-viewer-switch-btn');
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn'); this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="simple"]'); this.simpleViewer = this.$fileHolder[0].querySelector(
this.richViewer = this.$fileHolder[0].querySelector('.blob-viewer[data-type="rich"]'); `.blob-viewer[data-type="${SIMPLE_VIEWER_NAME}"]`,
);
this.richViewer = this.$fileHolder[0].querySelector(
`.blob-viewer[data-type="${RICH_VIEWER_NAME}"]`,
);
this.initBindings(); this.initBindings();
@ -71,10 +79,10 @@ export default class BlobViewer {
switchToInitialViewer() { switchToInitialViewer() {
const initialViewer = this.$fileHolder[0].querySelector('.blob-viewer:not(.hidden)'); const initialViewer = this.$fileHolder[0].querySelector('.blob-viewer:not(.hidden)');
let initialViewerName = initialViewer.getAttribute('data-type'); let initialViewerName = initialViewer.dataset.type;
if (this.switcher && window.location.hash.indexOf('#L') === 0) { if (this.switcher && window.location.hash.indexOf('#L') === 0) {
initialViewerName = 'simple'; initialViewerName = SIMPLE_VIEWER_NAME;
} }
this.switchToViewer(initialViewerName); this.switchToViewer(initialViewerName);
@ -91,35 +99,41 @@ export default class BlobViewer {
this.copySourceBtn.addEventListener('click', () => { this.copySourceBtn.addEventListener('click', () => {
if (this.copySourceBtn.classList.contains('disabled')) return this.copySourceBtn.blur(); if (this.copySourceBtn.classList.contains('disabled')) return this.copySourceBtn.blur();
return this.switchToViewer('simple'); return this.switchToViewer(SIMPLE_VIEWER_NAME);
}); });
} }
} }
static linkifyURLs(viewer) {
if (viewer.dataset.linkified) return;
document.querySelectorAll('.js-blob-content .code .line').forEach(line => {
// eslint-disable-next-line no-param-reassign
line.innerHTML = line.innerHTML.replace(blobLinkRegex, '<a href="$&">$&</a>');
});
// eslint-disable-next-line no-param-reassign
viewer.dataset.linkified = true;
}
switchViewHandler(e) { switchViewHandler(e) {
const target = e.currentTarget; const target = e.currentTarget;
e.preventDefault(); e.preventDefault();
this.switchToViewer(target.getAttribute('data-viewer')); this.switchToViewer(target.dataset.viewer);
} }
toggleCopyButtonState() { toggleCopyButtonState() {
if (!this.copySourceBtn) return; if (!this.copySourceBtn) return;
if (this.simpleViewer.getAttribute('data-loaded')) { if (this.simpleViewer.dataset.loaded) {
this.copySourceBtn.setAttribute('title', __('Copy file contents')); this.copySourceBtn.dataset.title = __('Copy file contents');
this.copySourceBtn.classList.remove('disabled'); this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) { } else if (this.activeViewer === this.simpleViewer) {
this.copySourceBtn.setAttribute( this.copySourceBtn.dataset.title = __('Wait for the file to load to copy its contents');
'title',
__('Wait for the file to load to copy its contents'),
);
this.copySourceBtn.classList.add('disabled'); this.copySourceBtn.classList.add('disabled');
} else { } else {
this.copySourceBtn.setAttribute( this.copySourceBtn.dataset.title = __('Switch to the source to copy the file contents');
'title',
__('Switch to the source to copy the file contents'),
);
this.copySourceBtn.classList.add('disabled'); this.copySourceBtn.classList.add('disabled');
} }
@ -159,6 +173,8 @@ export default class BlobViewer {
this.$fileHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
handleLocationHash(); handleLocationHash();
if (name === SIMPLE_VIEWER_NAME) BlobViewer.linkifyURLs(viewer);
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}) })
.catch(() => new Flash(__('Error loading viewer'))); .catch(() => new Flash(__('Error loading viewer')));
@ -166,17 +182,17 @@ export default class BlobViewer {
static loadViewer(viewerParam) { static loadViewer(viewerParam) {
const viewer = viewerParam; const viewer = viewerParam;
const url = viewer.getAttribute('data-url'); const { url, loaded, loading } = viewer.dataset;
if (!url || viewer.getAttribute('data-loaded') || viewer.getAttribute('data-loading')) { if (!url || loaded || loading) {
return Promise.resolve(viewer); return Promise.resolve(viewer);
} }
viewer.setAttribute('data-loading', 'true'); viewer.dataset.loading = true;
return axios.get(url).then(({ data }) => { return axios.get(url).then(({ data }) => {
viewer.innerHTML = data.html; viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true'); viewer.dataset.loaded = true;
return viewer; return viewer;
}); });

View file

@ -4,7 +4,8 @@ import $ from 'jquery';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator'; import { blobLinkRegex } from '~/blob/blob_utils';
import TemplateSelectorMediator from '~/blob/file_template_mediator';
import getModeByFileExtension from '~/lib/utils/ace_utils'; import getModeByFileExtension from '~/lib/utils/ace_utils';
import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown'; import { addEditorMarkdownListeners } from '~/lib/utils/text_markdown';
@ -17,6 +18,7 @@ export default class EditBlob {
this.initModePanesAndLinks(); this.initModePanesAndLinks();
this.initSoftWrap(); this.initSoftWrap();
this.initFileSelectors(); this.initFileSelectors();
this.initBlobContentLinkClickability();
} }
configureAceEditor() { configureAceEditor() {
@ -89,6 +91,22 @@ export default class EditBlob {
return this.editor.focus(); return this.editor.focus();
} }
initBlobContentLinkClickability() {
this.editor.renderer.on('afterRender', () => {
document.querySelectorAll('.ace_text-layer .ace_line > *').forEach(token => {
if (token.dataset.linkified || !token.textContent.includes('http')) return;
// eslint-disable-next-line no-param-reassign
token.innerHTML = token.innerHTML.replace(
blobLinkRegex,
'<a target="_blank" href="$&">$&</a>',
);
// eslint-disable-next-line no-param-reassign
token.dataset.linkified = true;
});
});
}
initSoftWrap() { initSoftWrap() {
this.isSoftWrapped = false; this.isSoftWrapped = false;
this.$toggleButton = $('.soft-wrap-toggle'); this.$toggleButton = $('.soft-wrap-toggle');

View file

@ -258,6 +258,17 @@
} }
} }
} }
.file-editor {
.ace_underline {
text-decoration: none;
}
.ace_line a {
pointer-events: auto;
color: inherit;
}
}
} }
span.idiff { span.idiff {

View file

@ -29,3 +29,12 @@
color: $link; color: $link;
} }
} }
// Links to URLs, emails, or dependencies
.code .line a {
color: inherit;
&:hover {
text-decoration: underline;
}
}

View file

@ -193,11 +193,6 @@ $dark-il: #de935f;
color: $dark-highlight-color !important; color: $dark-highlight-color !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $dark-na;
}
.hll { background-color: $dark-hll-bg; } .hll { background-color: $dark-hll-bg; }
.c { color: $dark-c; } /* Comment */ .c { color: $dark-c; } /* Comment */
.err { color: $dark-err; } /* Error */ .err { color: $dark-err; } /* Error */

View file

@ -193,11 +193,6 @@ $monokai-gi: #a6e22e;
color: $black !important; color: $black !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $monokai-k;
}
.hll { background-color: $monokai-hll; } .hll { background-color: $monokai-hll; }
.c { color: $monokai-c; } /* Comment */ .c { color: $monokai-c; } /* Comment */
.err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */ .err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */

View file

@ -143,12 +143,6 @@
background-color: $white-normal; background-color: $white-normal;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $gl-text-color;
text-decoration: underline;
}
.hll { background-color: $white-light; } .hll { background-color: $white-light; }
.gd { .gd {

View file

@ -196,11 +196,6 @@ $solarized-dark-il: #2aa198;
background-color: $solarized-dark-highlight !important; background-color: $solarized-dark-highlight !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $solarized-dark-kd;
}
/* Solarized Dark /* Solarized Dark
For use with Jekyll and Pygments For use with Jekyll and Pygments

View file

@ -204,11 +204,6 @@ $solarized-light-il: #2aa198;
background-color: $solarized-light-highlight !important; background-color: $solarized-light-highlight !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $solarized-light-kd;
}
/* Solarized Light /* Solarized Light
For use with Jekyll and Pygments For use with Jekyll and Pygments

View file

@ -209,11 +209,6 @@ span.highlight_word {
background-color: $white-highlight !important; background-color: $white-highlight !important;
} }
// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; } .hll { background-color: $white-hll-bg; }
.c { color: $white-c; .c { color: $white-c;

View file

@ -10,7 +10,7 @@
%a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i } %a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i }
= link_icon = link_icon
= i = i
.blob-content{ data: { blob_id: blob.id } } .blob-content.js-blob-content{ data: { blob_id: blob.id } }
%pre.code.highlight %pre.code.highlight
%code %code
= blob.present.highlight = blob.present.highlight

View file

@ -0,0 +1,5 @@
---
title: Make URLs in blob viewer and blob editor into clickable links
merge_request: 18305
author:
type: added

View file

@ -187,14 +187,18 @@ keys must be manually replicated to the **secondary** node.
1. Visit the **primary** node's **Admin Area > Geo** 1. Visit the **primary** node's **Admin Area > Geo**
(`/admin/geo/nodes`) in your browser. (`/admin/geo/nodes`) in your browser.
1. Click the **New node** button. 1. Click the **New node** button.
1. Add the **secondary** node. Use the **exact** name you inputed for `gitlab_rails['geo_node_name']` as the Name and the full URL as the URL. **Do NOT** check the
**This is a primary node** checkbox.
![Add secondary node](img/adding_a_secondary_node.png) ![Add secondary node](img/adding_a_secondary_node.png)
1. Fill in **Name** with the `gitlab_rails['geo_node_name']` in
`/etc/gitlab/gitlab.rb`. These values must always match *exactly*, character
for character.
1. Fill in **URL** with the `external_url` in `/etc/gitlab/gitlab.rb`. These
values must always match, but it doesn't matter if one ends with a `/` and
the other doesn't.
1. **Do NOT** check the **This is a primary node** checkbox.
1. Optionally, choose which groups or storage shards should be replicated by the 1. Optionally, choose which groups or storage shards should be replicated by the
**secondary** node. Leave blank to replicate all. Read more in **secondary** node. Leave blank to replicate all. Read more in
[selective synchronization](#selective-synchronization). [selective synchronization](#selective-synchronization).
1. Click the **Add node** button. 1. Click the **Add node** button to add the **secondary** node.
1. SSH into your GitLab **secondary** server and restart the services: 1. SSH into your GitLab **secondary** server and restart the services:
```sh ```sh

View file

@ -115,11 +115,19 @@ Any **secondary** nodes should point only to read-only instances.
#### Can Geo detect the current node correctly? #### Can Geo detect the current node correctly?
Geo uses the defined node from the **Admin Area > Geo** screen, and tries to match Geo finds the current machine's name in `/etc/gitlab/gitlab.rb` by first looking
it with the value defined in the `/etc/gitlab/gitlab.rb` configuration file. for `gitlab_rails['geo_node_name']`. If it is not defined, then it defaults to
The relevant line looks like: `external_url "http://gitlab.example.com"`. the external URL defined in e.g. `external_url "http://gitlab.example.com"`. To
get a machine's name, run:
To check if the node on the current machine is correctly detected type: ```sh
sudo gitlab-rails runner "puts GeoNode.current_node_name"
```
This name is used to look up the node with the same **Name** in
**Admin Area > Geo**.
To check if current machine is correctly finding its node:
```sh ```sh
sudo gitlab-rails runner "puts Gitlab::Geo.current_node.inspect" sudo gitlab-rails runner "puts Gitlab::Geo.current_node.inspect"
@ -511,6 +519,20 @@ to [cleanup orphan artifact files](../../../raketasks/cleanup.md#remove-orphan-a
On a Geo **secondary** node, this command will also clean up all Geo On a Geo **secondary** node, this command will also clean up all Geo
registry record related to the orphan files on disk. registry record related to the orphan files on disk.
## Fixing sign in errors
### Message: The redirect URI included is not valid
If you are able to log in to the **primary** node, but you receive this error
when attempting to log into a **secondary**, you should check that the Geo
node's URL matches its external URL.
1. On the primary, visit **Admin Area > Geo**.
1. Find the affected **secondary** and click **Edit**.
1. Ensure the **URL** field matches the value found in `/etc/gitlab/gitlab.rb`
in `external_url "https://gitlab.example.com"` on the frontend server(s) of
the **secondary** node.
## Fixing common errors ## Fixing common errors
This section documents common errors reported in the Admin UI and how to fix them. This section documents common errors reported in the Admin UI and how to fix them.

View file

@ -87,10 +87,9 @@ not be found, or a user does not have access rights to create pipeline there,
the `staging` job is going to be marked as _failed_. the `staging` job is going to be marked as _failed_.
CAUTION: **Caution:** CAUTION: **Caution:**
`staging` will succeed as soon as a downstream pipeline gets created. In the example, `staging` will be marked as succeeded as soon as a downstream pipeline
GitLab does not support status attribution yet, however adding first-class gets created. If you want to display the downstream pipeline's status instead, see
`trigger` configuration syntax is ground work for implementing [Mirroring status from triggered pipeline](#mirroring-status-from-triggered-pipeline).
[status attribution](https://gitlab.com/gitlab-org/gitlab-foss/issues/39640).
NOTE: **Note:** NOTE: **Note:**
Bridge jobs do not support every configuration entry that a user can use Bridge jobs do not support every configuration entry that a user can use

View file

@ -42,6 +42,10 @@ Passing a `logger:` keyword argument to `Gitlab::Profiler.profile` will send
ActiveRecord and ActionController log output to that logger. Further options are ActiveRecord and ActionController log output to that logger. Further options are
documented with the method source. documented with the method source.
```ruby
Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first, logger: Logger.new(STDOUT))
```
There is also a RubyProf printer available: There is also a RubyProf printer available:
`Gitlab::Profiler::TotalTimeFlatPrinter`. This acts like `Gitlab::Profiler::TotalTimeFlatPrinter`. This acts like
`RubyProf::FlatPrinter`, but its `min_percent` option works on the method's `RubyProf::FlatPrinter`, but its `min_percent` option works on the method's

View file

@ -108,6 +108,10 @@ module QA
find_element(:more_assignees_link) find_element(:more_assignees_link)
end end
def noteable_note_item
find_element(:noteable_note_item)
end
def select_all_activities_filter def select_all_activities_filter
select_filter_with_text('Show all activity') select_filter_with_text('Show all activity')
end end

View file

@ -21,8 +21,9 @@ module QA
end end
context 'when using attachments in comments', :object_storage do context 'when using attachments in comments', :object_storage do
let(:gif_file_name) { 'banana_sample.gif' }
let(:file_to_attach) do let(:file_to_attach) do
File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif')) File.absolute_path(File.join('spec', 'fixtures', gif_file_name))
end end
before do before do
@ -37,15 +38,7 @@ module QA
Page::Project::Issue::Show.perform do |show| Page::Project::Issue::Show.perform do |show|
show.comment('See attached banana for scale', attachment: file_to_attach) show.comment('See attached banana for scale', attachment: file_to_attach)
show.refresh expect(show.noteable_note_item.find("img[src$='#{gif_file_name}']")).to be_visible
image_url = find('a[href$="banana_sample.gif"]')[:href]
found = show.wait(reload: false) do
show.asset_exists?(image_url)
end
expect(found).to be_truthy
end end
end end
end end

View file

@ -60,6 +60,13 @@ describe 'Editing file blob', :js do
expect(page).to have_content 'NextFeature' expect(page).to have_content 'NextFeature'
end end
it 'renders a URL in the content of file as a link' do
project.repository.create_file(user, 'file.yml', '# go to https://gitlab.com', message: 'testing', branch_name: branch)
visit project_edit_blob_path(project, tree_join(branch, 'file.yml'))
expect(page).to have_selector('.ace_content .ace_line a')
end
context 'from blob file path' do context 'from blob file path' do
before do before do
visit project_blob_path(project, tree_join(branch, file_path)) visit project_blob_path(project, tree_join(branch, file_path))

View file

@ -11,6 +11,13 @@ describe('Blob viewer', () => {
preloadFixtures('snippets/show.html'); preloadFixtures('snippets/show.html');
const asyncClick = () =>
new Promise(resolve => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(resolve);
});
beforeEach(() => { beforeEach(() => {
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
@ -66,19 +73,12 @@ describe('Blob viewer', () => {
}); });
it('doesnt reload file if already loaded', done => { it('doesnt reload file if already loaded', done => {
const asyncClick = () =>
new Promise(resolve => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(resolve);
});
asyncClick() asyncClick()
.then(() => asyncClick()) .then(() => asyncClick())
.then(() => { .then(() => {
expect( expect(document.querySelector('.blob-viewer[data-type="simple"]').dataset.loaded).toBe(
document.querySelector('.blob-viewer[data-type="simple"]').getAttribute('data-loaded'), 'true',
).toBe('true'); );
done(); done();
}) })
@ -100,9 +100,7 @@ describe('Blob viewer', () => {
}); });
it('has tooltip when disabled', () => { it('has tooltip when disabled', () => {
expect(copyButton.getAttribute('data-original-title')).toBe( expect(copyButton.dataset.title).toBe('Switch to the source to copy the file contents');
'Switch to the source to copy the file contents',
);
}); });
it('is blurred when clicked and disabled', () => { it('is blurred when clicked and disabled', () => {
@ -136,7 +134,7 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click(); document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(() => { setTimeout(() => {
expect(copyButton.getAttribute('data-original-title')).toBe('Copy file contents'); expect(copyButton.dataset.title).toBe('Copy file contents');
done(); done();
}); });
@ -177,4 +175,27 @@ describe('Blob viewer', () => {
expect(axios.get.calls.count()).toBe(1); expect(axios.get.calls.count()).toBe(1);
}); });
}); });
describe('a URL inside the blob content', () => {
beforeEach(() => {
mock.onGet('http://test.host/snippets/1.json?viewer=simple').reply(200, {
html:
'<div class="js-blob-content"><pre class="code"><code><span class="line" lang="yaml"><span class="c1">To install gitlab-shell you also need a Go compiler version 1.8 or newer. https://golang.org/dl/</span></span></code></pre></div>',
});
});
it('is rendered as a link in simple view', done => {
asyncClick()
.then(() => {
expect(document.querySelector('.blob-viewer[data-type="simple"]').innerHTML).toContain(
'<a href="https://golang.org/dl/">https://golang.org/dl/</a>',
);
done();
})
.catch(() => {
fail();
done();
});
});
});
}); });