Merge branch 'master' into sathieu/gitlab-ce-project_api

This commit is contained in:
Douglas Barbosa Alexandre 2019-07-09 14:45:46 -03:00
commit 2615265ef8
No known key found for this signature in database
GPG Key ID: 4DC4A918C347CAC9
834 changed files with 11633 additions and 7459 deletions

View File

@ -5,6 +5,7 @@ globals:
gl: false
gon: false
localStorage: false
IS_EE: false
plugins:
- import
- html

View File

@ -66,9 +66,7 @@ docs lint:
- mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
- cd /tmp/gitlab-docs
# Lint Markdown
# https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md
- bundle exec mdl content/$DOCS_GITLAB_REPO_SUFFIX/**/*.md --ignore-front-matter --rules \
MD004,MD032,MD034
- bundle exec mdl content/$DOCS_GITLAB_REPO_SUFFIX -c $CI_PROJECT_DIR/.mdlrc
# Build HTML from Markdown
- bundle exec nanoc
# Check the internal links

View File

@ -139,7 +139,7 @@ setup-test-env:
rspec unit pg:
<<: *rspec-metadata-pg
parallel: 25
parallel: 20
rspec integration pg:
<<: *rspec-metadata-pg
@ -152,7 +152,7 @@ rspec system pg:
rspec unit pg-10:
<<: *rspec-metadata-pg-10
<<: *only-schedules-master
parallel: 25
parallel: 20
rspec integration pg-10:
<<: *rspec-metadata-pg-10

View File

@ -77,7 +77,7 @@ schedule:review-build-cng:
.review-deploy-base: &review-deploy-base
<<: *review-base
allow_failure: true
retry: 2
retry: 1
stage: review
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"

4
.mdlrc Normal file
View File

@ -0,0 +1,4 @@
# See https://github.com/markdownlint/markdownlint/blob/master/docs/configuration.md
ignore_front_matter true
style File.expand_path('.mdlrc.style', __dir__)

7
.mdlrc.style Normal file
View File

@ -0,0 +1,7 @@
# See https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md
rule 'MD001'
# False positives, see https://github.com/markdownlint/markdownlint/issues/261
# rule 'MD004', style: :dash
rule 'MD032'
rule 'MD034'

View File

@ -466,12 +466,10 @@ Rails/LinkToBlank:
Rails/Presence:
Exclude:
- 'app/models/ci/pipeline.rb'
- 'app/models/clusters/platforms/kubernetes.rb'
- 'app/models/concerns/mentionable.rb'
- 'app/models/project_services/hipchat_service.rb'
- 'app/models/project_services/irker_service.rb'
- 'app/models/project_services/jira_service.rb'
- 'app/models/project_services/kubernetes_service.rb'
- 'app/models/project_services/packagist_service.rb'
- 'app/models/wiki_page.rb'
- 'lib/gitlab/github_import/importer/releases_importer.rb'
@ -514,7 +512,6 @@ Security/YAMLLoad:
- 'spec/config/mail_room_spec.rb'
- 'spec/initializers/secret_token_spec.rb'
- 'spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb'
- 'spec/models/project_services/kubernetes_service_spec.rb'
# Offense count: 34
# Configuration parameters: EnforcedStyle.

View File

@ -1 +1 @@
1.49.0
1.51.0

View File

@ -132,7 +132,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '0.0.9'
gem 'rouge', '~> 3.1'
gem 'rouge', '~> 3.5'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.3'
@ -309,7 +309,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false
# Prometheus
gem 'prometheus-client-mmap', '~> 0.9.6'
gem 'prometheus-client-mmap', '~> 0.9.8'
gem 'raindrops', '~> 0.18'
end
@ -368,6 +368,7 @@ group :development, :test do
gem 'haml_lint', '~> 0.31.0', require: false
gem 'simplecov', '~> 0.16.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
gem 'mdl', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false
@ -419,7 +420,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
gem 'net-ssh', '~> 5.0'
gem 'net-ssh', '~> 5.2'
gem 'sshkey', '~> 2.0'
# Required for ED25519 SSH host key support
@ -429,7 +430,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 1.32.0', require: 'gitaly'
gem 'gitaly-proto', '~> 1.36.0', require: 'gitaly'
gem 'grpc', '~> 1.19.0'

View File

@ -303,7 +303,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gitaly-proto (1.32.0)
gitaly-proto (1.36.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-labkit (0.3.0)
@ -459,6 +459,7 @@ GEM
kgio (2.11.2)
knapsack (1.17.0)
rake
kramdown (1.17.0)
kubeclient (4.2.2)
http (~> 3.0)
recursive-open-struct (~> 1.0, >= 1.0.4)
@ -492,6 +493,10 @@ GEM
mail (2.7.1)
mini_mime (>= 0.1.1)
mail_room (0.9.1)
mdl (0.5.0)
kramdown (~> 1.12, >= 1.12.0)
mixlib-cli (~> 1.7, >= 1.7.0)
mixlib-config (~> 2.2, >= 2.2.1)
memoist (0.16.0)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
@ -505,6 +510,9 @@ GEM
mini_mime (1.0.1)
mini_portile2 (2.4.0)
minitest (5.11.3)
mixlib-cli (1.7.0)
mixlib-config (2.2.18)
tomlrb
msgpack (1.2.10)
multi_json (1.13.1)
multi_xml (0.6.0)
@ -515,7 +523,7 @@ GEM
mysql2 (0.4.10)
nakayoshi_fork (0.0.4)
net-ldap (0.16.0)
net-ssh (5.0.1)
net-ssh (5.2.0)
netrc (0.11.0)
nio4r (2.3.1)
nokogiri (1.10.3)
@ -652,7 +660,7 @@ GEM
parser
unparser
procto (0.0.3)
prometheus-client-mmap (0.9.6)
prometheus-client-mmap (0.9.8)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@ -770,7 +778,7 @@ GEM
retriable (3.1.2)
rinku (2.0.0)
rotp (2.1.2)
rouge (3.4.1)
rouge (3.5.1)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@ -943,6 +951,7 @@ GEM
parslet (~> 1.8.0)
toml-rb (1.0.0)
citrus (~> 3.0, > 3.0)
tomlrb (1.2.8)
truncato (0.7.11)
htmlentities (~> 4.3.1)
nokogiri (>= 1.7.0, <= 2.0)
@ -1092,7 +1101,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 1.32.0)
gitaly-proto (~> 1.36.0)
github-markup (~> 1.7.0)
gitlab-labkit (~> 0.3.0)
gitlab-markup (~> 1.7.0)
@ -1134,6 +1143,7 @@ DEPENDENCIES
lograge (~> 0.5)
loofah (~> 2.2)
mail_room (~> 0.9.1)
mdl (~> 0.5.0)
memory_profiler (~> 0.9)
method_source (~> 0.8)
mimemagic (~> 0.3.2)
@ -1142,7 +1152,7 @@ DEPENDENCIES
mysql2 (~> 0.4.10)
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ssh (~> 5.0)
net-ssh (~> 5.2)
nokogiri (~> 1.10.3)
oauth2 (~> 1.4)
octokit (~> 4.9)
@ -1173,7 +1183,7 @@ DEPENDENCIES
peek-redis (~> 1.2.0)
pg (~> 1.1)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.6)
prometheus-client-mmap (~> 0.9.8)
pry-byebug (~> 3.5.1)
pry-rails (~> 0.3.4)
puma (~> 3.12)
@ -1199,7 +1209,7 @@ DEPENDENCIES
redis-rails (~> 5.0.2)
request_store (~> 1.3)
responders (~> 2.0)
rouge (~> 3.1)
rouge (~> 3.5)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.7.0)

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -24,6 +24,7 @@ const Api = {
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
userCountsPath: '/api/:version/user_counts',
usersPath: '/api/:version/users.json',
userPath: '/api/:version/users/:id',
userStatusPath: '/api/:version/users/:id/status',
@ -312,6 +313,11 @@ const Api = {
});
},
userCounts() {
const url = Api.buildUrl(this.userCountsPath);
return axios.get(url);
},
userStatus(id, options) {
const url = Api.buildUrl(this.userStatusPath).replace(':id', encodeURIComponent(id));
return axios.get(url, {

View File

@ -61,7 +61,7 @@ export default {
<div class="board-blank-state p-3">
<p>
{{
__('BoardBlankState|Add the following default lists to your Issue Board with one click:')
s__('BoardBlankState|Add the following default lists to your Issue Board with one click:')
}}
</p>
<ul class="list-unstyled board-blank-state-list">
@ -76,7 +76,7 @@ export default {
</ul>
<p>
{{
__(
s__(
'BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board.',
)
}}
@ -86,10 +86,10 @@ export default {
type="button"
@click.stop="addDefaultLists"
>
{{ __('BoardBlankState|Add default lists') }}
{{ s__('BoardBlankState|Add default lists') }}
</button>
<button class="btn btn-default btn-block" type="button" @click.stop="clearBlankState">
{{ __("BoardBlankState|Nevermind, I'll use my own") }}
{{ s__("BoardBlankState|Nevermind, I'll use my own") }}
</button>
</div>
</template>

View File

@ -2,7 +2,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable
import FilteredSearchContainer from '../filtered_search/container';
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
import boardsStore from './stores/boards_store';
import { isEE } from '~/lib/utils/common_utils';
export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
@ -10,7 +9,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
page: 'boards',
isGroupDecendent: true,
stateFiltersSelector: '.issues-state-filters',
isGroup: isEE(),
isGroup: IS_EE,
filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys,
});

View File

@ -1,6 +1,7 @@
import $ from 'jquery';
import Vue from 'vue';
import mountMultipleBoardsSwitcher from 'ee_else_ce/boards/mount_multiple_boards_switcher';
import Flash from '~/flash';
import { __ } from '~/locale';
import './models/label';
@ -20,7 +21,7 @@ import modalMixin from './mixins/modal_mixins';
import './filters/due_date_filters';
import Board from './components/board';
import BoardSidebar from './components/board_sidebar';
import initNewListDropdown from './components/new_list_dropdown';
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
import {
@ -78,13 +79,14 @@ export default () => {
},
},
created() {
gl.boardService = new BoardService({
boardsStore.setEndpoints({
boardsEndpoint: this.boardsEndpoint,
recentBoardsEndpoint: this.recentBoardsEndpoint,
listsEndpoint: this.listsEndpoint,
bulkUpdatePath: this.bulkUpdatePath,
boardId: this.boardId,
});
gl.boardService = new BoardService();
boardsStore.rootPath = this.boardsEndpoint;
eventHub.$on('updateTokens', this.updateTokens);
@ -278,4 +280,6 @@ export default () => {
`,
});
}
mountMultipleBoardsSwitcher();
};

View File

@ -5,7 +5,7 @@
import Vue from 'vue';
import './label';
import { isEE, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import IssueProject from './project';
import boardsStore from '../stores/boards_store';
@ -91,13 +91,13 @@ class ListIssue {
addMilestone(milestone) {
const miletoneId = this.milestone ? this.milestone.id : null;
if (isEE && milestone.id !== miletoneId) {
if (IS_EE && milestone.id !== miletoneId) {
this.milestone = new ListMilestone(milestone);
}
}
removeMilestone(removeMilestone) {
if (isEE && removeMilestone && removeMilestone.id === this.milestone.id) {
if (IS_EE && removeMilestone && removeMilestone.id === this.milestone.id) {
this.milestone = {};
}
}

View File

@ -4,7 +4,7 @@
import { __ } from '~/locale';
import ListLabel from './label';
import ListAssignee from './assignee';
import { isEE, urlParamsToObject } from '~/lib/utils/common_utils';
import { urlParamsToObject } from '~/lib/utils/common_utils';
import boardsStore from '../stores/boards_store';
import ListMilestone from './milestone';
@ -58,7 +58,7 @@ class List {
} else if (obj.user) {
this.assignee = new ListAssignee(obj.user);
this.title = this.assignee.name;
} else if (isEE && obj.milestone) {
} else if (IS_EE && obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
this.title = this.milestone.title;
}
@ -85,7 +85,7 @@ class List {
entityType = 'label_id';
} else if (this.assignee) {
entityType = 'assignee_id';
} else if (isEE && this.milestone) {
} else if (IS_EE && this.milestone) {
entityType = 'milestone_id';
}
@ -205,7 +205,7 @@ class List {
issue.addAssignee(this.assignee);
}
if (isEE && this.milestone) {
if (IS_EE && this.milestone) {
if (listFrom && listFrom.type === 'milestone') {
issue.removeMilestone(listFrom.milestone);
}

View File

@ -1,11 +1,9 @@
import { isEE } from '~/lib/utils/common_utils';
export default class ListMilestone {
constructor(obj) {
this.id = obj.id;
this.title = obj.title;
if (isEE) {
if (IS_EE) {
this.path = obj.path;
this.state = obj.state;
this.webUrl = obj.web_url || obj.webUrl;

View File

@ -0,0 +1,2 @@
// this will be moved from EE to CE as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/53811
export default () => {};

View File

@ -1,106 +1,66 @@
import axios from '../../lib/utils/axios_utils';
import { mergeUrlParams } from '../../lib/utils/url_utility';
/* eslint-disable class-methods-use-this */
import boardsStore from '~/boards/stores/boards_store';
export default class BoardService {
constructor({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) {
this.boardsEndpoint = boardsEndpoint;
this.boardId = boardId;
this.listsEndpoint = listsEndpoint;
this.listsEndpointGenerate = `${listsEndpoint}/generate.json`;
this.bulkUpdatePath = bulkUpdatePath;
this.recentBoardsEndpoint = `${recentBoardsEndpoint}.json`;
}
generateBoardsPath(id) {
return `${this.boardsEndpoint}${id ? `/${id}` : ''}.json`;
return boardsStore.generateBoardsPath(id);
}
generateIssuesPath(id) {
return `${this.listsEndpoint}${id ? `/${id}` : ''}/issues`;
return boardsStore.generateIssuesPath(id);
}
static generateIssuePath(boardId, id) {
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
id ? `/${id}` : ''
}`;
return boardsStore.generateIssuePath(boardId, id);
}
all() {
return axios.get(this.listsEndpoint);
return boardsStore.all();
}
generateDefaultLists() {
return axios.post(this.listsEndpointGenerate, {});
return boardsStore.generateDefaultLists();
}
createList(entityId, entityType) {
const list = {
[entityType]: entityId,
};
return axios.post(this.listsEndpoint, {
list,
});
return boardsStore.createList(entityId, entityType);
}
updateList(id, position) {
return axios.put(`${this.listsEndpoint}/${id}`, {
list: {
position,
},
});
return boardsStore.updateList(id, position);
}
destroyList(id) {
return axios.delete(`${this.listsEndpoint}/${id}`);
return boardsStore.destroyList(id);
}
getIssuesForList(id, filter = {}) {
const data = { id };
Object.keys(filter).forEach(key => {
data[key] = filter[key];
});
return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
return boardsStore.getIssuesForList(id, filter);
}
moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) {
return axios.put(BoardService.generateIssuePath(this.boardId, id), {
from_list_id: fromListId,
to_list_id: toListId,
move_before_id: moveBeforeId,
move_after_id: moveAfterId,
});
return boardsStore.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId);
}
newIssue(id, issue) {
return axios.post(this.generateIssuesPath(id), {
issue,
});
return boardsStore.newIssue(id, issue);
}
getBacklog(data) {
return axios.get(
mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`),
);
return boardsStore.getBacklog(data);
}
bulkUpdate(issueIds, extraData = {}) {
const data = {
update: Object.assign(extraData, {
issuable_ids: issueIds.join(','),
}),
};
return axios.post(this.bulkUpdatePath, data);
return boardsStore.bulkUpdate(issueIds, extraData);
}
static getIssueInfo(endpoint) {
return axios.get(endpoint);
return boardsStore.getIssueInfo(endpoint);
}
static toggleIssueSubscription(endpoint) {
return axios.post(endpoint);
return boardsStore.toggleIssueSubscription(endpoint);
}
}

View File

@ -8,6 +8,8 @@ import Cookies from 'js-cookie';
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../eventhub';
const boardsStore = {
@ -28,6 +30,7 @@ const boardsStore = {
},
currentPage: '',
reload: false,
endpoints: {},
},
detail: {
issue: {},
@ -36,6 +39,19 @@ const boardsStore = {
issue: {},
list: {},
},
setEndpoints({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId, recentBoardsEndpoint }) {
const listsEndpointGenerate = `${listsEndpoint}/generate.json`;
this.state.endpoints = {
boardsEndpoint,
boardId,
listsEndpoint,
listsEndpointGenerate,
bulkUpdatePath,
recentBoardsEndpoint: `${recentBoardsEndpoint}.json`,
};
},
create() {
this.state.lists = [];
this.filter.path = getUrlParamsArray().join('&');
@ -229,6 +245,101 @@ const boardsStore = {
setTimeTrackingLimitToHours(limitToHours) {
this.timeTracking.limitToHours = parseBoolean(limitToHours);
},
generateBoardsPath(id) {
return `${this.state.endpoints.boardsEndpoint}${id ? `/${id}` : ''}.json`;
},
generateIssuesPath(id) {
return `${this.state.endpoints.listsEndpoint}${id ? `/${id}` : ''}/issues`;
},
generateIssuePath(boardId, id) {
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
id ? `/${id}` : ''
}`;
},
all() {
return axios.get(this.state.endpoints.listsEndpoint);
},
generateDefaultLists() {
return axios.post(this.state.endpoints.listsEndpointGenerate, {});
},
createList(entityId, entityType) {
const list = {
[entityType]: entityId,
};
return axios.post(this.state.endpoints.listsEndpoint, {
list,
});
},
updateList(id, position) {
return axios.put(`${this.state.endpoints.listsEndpoint}/${id}`, {
list: {
position,
},
});
},
destroyList(id) {
return axios.delete(`${this.state.endpoints.listsEndpoint}/${id}`);
},
getIssuesForList(id, filter = {}) {
const data = { id };
Object.keys(filter).forEach(key => {
data[key] = filter[key];
});
return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
},
moveIssue(id, fromListId = null, toListId = null, moveBeforeId = null, moveAfterId = null) {
return axios.put(this.generateIssuePath(this.state.endpoints.boardId, id), {
from_list_id: fromListId,
to_list_id: toListId,
move_before_id: moveBeforeId,
move_after_id: moveAfterId,
});
},
newIssue(id, issue) {
return axios.post(this.generateIssuesPath(id), {
issue,
});
},
getBacklog(data) {
return axios.get(
mergeUrlParams(
data,
`${gon.relative_url_root}/-/boards/${this.state.endpoints.boardId}/issues.json`,
),
);
},
bulkUpdate(issueIds, extraData = {}) {
const data = {
update: Object.assign(extraData, {
issuable_ids: issueIds.join(','),
}),
};
return axios.post(this.state.endpoints.bulkUpdatePath, data);
},
getIssueInfo(endpoint) {
return axios.get(endpoint);
},
toggleIssueSubscription(endpoint) {
return axios.post(endpoint);
},
};
BoardsStoreEE.initEESpecific(boardsStore);

View File

@ -36,7 +36,9 @@ export default endpoint => {
}, 100);
Object.entries(data).forEach(([branchName, val]) => {
const el = document.querySelector(`.js-branch-${branchName} .js-branch-divergence-graph`);
const el = document.querySelector(
`[data-name="${branchName}"] .js-branch-divergence-graph`,
);
if (!el) return;

View File

@ -4,3 +4,6 @@ import './jquery';
import './bootstrap';
import './vue';
import '../lib/utils/axios_utils';
import { openUserCountsBroadcast } from './nav/user_merge_requests';
openUserCountsBroadcast();

View File

@ -0,0 +1,67 @@
import Api from '~/api';
let channel;
function broadcastCount(newCount) {
if (!channel) {
return;
}
channel.postMessage(newCount);
}
function updateUserMergeRequestCounts(newCount) {
const mergeRequestsCountEl = document.querySelector('.merge-requests-count');
mergeRequestsCountEl.textContent = newCount.toLocaleString();
mergeRequestsCountEl.classList.toggle('hidden', Number(newCount) === 0);
}
/**
* Refresh user counts (and broadcast if open)
*/
export function refreshUserMergeRequestCounts() {
return Api.userCounts()
.then(({ data }) => {
const count = data.merge_requests;
updateUserMergeRequestCounts(count);
broadcastCount(count);
})
.catch(ex => {
console.error(ex); // eslint-disable-line no-console
});
}
/**
* Close the broadcast channel for user counts
*/
export function closeUserCountsBroadcast() {
if (!channel) {
return;
}
channel.close();
channel = null;
}
/**
* Open the broadcast channel for user counts, adds user id so we only update
*
* **Please note:**
* Not supported in all browsers, but not polyfilling for now
* to keep bundle size small and
* no special functionality lost except cross tab notifications
*/
export function openUserCountsBroadcast() {
closeUserCountsBroadcast();
if (window.BroadcastChannel) {
const currentUserId = typeof gon !== 'undefined' && gon && gon.current_user_id;
if (currentUserId) {
channel = new BroadcastChannel(`mr_count_channel_${currentUserId}`);
channel.onmessage = ev => {
updateUserMergeRequestCounts(ev.data);
};
}
}
}

View File

@ -0,0 +1,58 @@
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
GlDropdown,
GlDropdownItem,
Icon,
},
props: {
projects: {
type: Array,
required: true,
},
selectedProject: {
type: Object,
required: false,
default: () => ({}),
},
},
computed: {
dropdownText() {
if (Object.keys(this.selectedProject).length) {
return this.selectedProject.name;
}
return __('Select private project');
},
},
methods: {
selectProject(project) {
this.$emit('click', project);
},
},
};
</script>
<template>
<gl-dropdown toggle-class="d-flex align-items-center w-100" class="w-100">
<template slot="button-content">
<span class="str-truncated-100 mr-2">
<icon name="lock" />
{{ dropdownText }}
</span>
<icon name="chevron-down" class="ml-auto" />
</template>
<gl-dropdown-item v-for="project in projects" :key="project.id" @click="selectProject(project)">
<icon
name="mobile-issue-close"
:class="{ icon: project.id !== selectedProject.id }"
class="js-active-project-check"
/>
<span class="ml-1">{{ project.name }}</span>
</gl-dropdown-item>
</gl-dropdown>
</template>

View File

@ -0,0 +1,132 @@
<script>
import { GlLink } from '@gitlab/ui';
import { __, sprintf } from '../../locale';
import createFlash from '../../flash';
import Api from '../../api';
import state from '../state';
import Dropdown from './dropdown.vue';
export default {
components: {
GlLink,
Dropdown,
},
props: {
namespacePath: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
newForkPath: {
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
},
data() {
return {
projects: [],
};
},
computed: {
selectedProject() {
return state.selectedProject;
},
noForkText() {
return sprintf(
__(
"To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.",
),
{ link_start: `<a href="${this.newForkPath}" class="help-link">`, link_end: '</a>' },
false,
);
},
},
mounted() {
this.fetchProjects();
this.createBtn = document.querySelector('.js-create-target');
this.warningText = document.querySelector('.js-exposed-info-warning');
},
methods: {
selectProject(project) {
if (project) {
Object.assign(state, {
selectedProject: project,
});
if (project.namespaceFullPath !== this.namespacePath) {
this.showWarning();
}
} else if (this.createBtn) {
this.createBtn.setAttribute('disabled', 'disabled');
}
},
normalizeProjectData(data) {
return data.map(p => ({
id: p.id,
name: p.name_with_namespace,
pathWithNamespace: p.path_with_namespace,
namespaceFullpath: p.namespace.full_path,
}));
},
fetchProjects() {
Api.projectForks(this.projectPath, {
with_merge_requests_enabled: true,
min_access_level: 30,
visibility: 'private',
})
.then(({ data }) => {
this.projects = this.normalizeProjectData(data);
this.selectProject(this.projects[0]);
})
.catch(e => {
createFlash(__('Error fetching forked projects. Please try again.'));
throw e;
});
},
showWarning() {
if (this.warningText) {
this.warningText.classList.remove('hidden');
}
if (this.createBtn) {
this.createBtn.classList.add('btn-warning');
this.createBtn.classList.remove('btn-success');
}
},
},
};
</script>
<template>
<div class="form-group">
<label>{{ __('Project') }}</label>
<div>
<dropdown
v-if="projects.length"
:projects="projects"
:selected-project="selectedProject"
@click="selectProject"
/>
<p class="text-muted mt-1 mb-0">
<template v-if="projects.length">
{{
__(
"To protect this issue's confidentiality, a private fork of this project was selected.",
)
}}
</template>
<template v-else>
{{ __('No forks available to you.') }}<br />
<span v-html="noForkText"></span>
</template>
</p>
</div>
</div>
</template>

View File

@ -0,0 +1,30 @@
import Vue from 'vue';
import { parseBoolean } from '../lib/utils/common_utils';
import ProjectFormGroup from './components/project_form_group.vue';
import state from './state';
export function isConfidentialIssue() {
return parseBoolean(document.querySelector('.js-create-mr').dataset.isConfidential);
}
export function canCreateConfidentialMergeRequest() {
return isConfidentialIssue() && Object.keys(state.selectedProject).length > 0;
}
export function init() {
const el = document.getElementById('js-forked-project');
return new Vue({
el,
render(h) {
return h(ProjectFormGroup, {
props: {
namespacePath: el.dataset.namespacePath,
projectPath: el.dataset.projectPath,
newForkPath: el.dataset.newForkPath,
helpPagePath: el.dataset.helpPagePath,
},
});
},
});
}

View File

@ -0,0 +1,5 @@
import Vue from 'vue';
export default Vue.observable({
selectedProject: {},
});

View File

@ -5,6 +5,12 @@ import Flash from './flash';
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
import { __, sprintf } from './locale';
import {
init as initConfidentialMergeRequest,
isConfidentialIssue,
canCreateConfidentialMergeRequest,
} from './confidential_merge_request';
import confidentialMergeRequestState from './confidential_merge_request/state';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
@ -12,6 +18,17 @@ const InputSetter = Object.assign({}, ISetter);
const CREATE_MERGE_REQUEST = 'create-mr';
const CREATE_BRANCH = 'create-branch';
function createEndpoint(projectPath, endpoint) {
if (canCreateConfidentialMergeRequest()) {
return endpoint.replace(
projectPath,
confidentialMergeRequestState.selectedProject.pathWithNamespace,
);
}
return endpoint;
}
export default class CreateMergeRequestDropdown {
constructor(wrapperEl) {
this.wrapperEl = wrapperEl;
@ -42,6 +59,8 @@ export default class CreateMergeRequestDropdown {
this.refIsValid = true;
this.refsPath = this.wrapperEl.dataset.refsPath;
this.suggestedRef = this.refInput.value;
this.projectPath = this.wrapperEl.dataset.projectPath;
this.projectId = this.wrapperEl.dataset.projectId;
// These regexps are used to replace
// a backend generated new branch name and its source (ref)
@ -58,6 +77,14 @@ export default class CreateMergeRequestDropdown {
};
this.init();
if (isConfidentialIssue()) {
this.createMergeRequestButton.setAttribute(
'data-dropdown-trigger',
'#create-merge-request-dropdown',
);
initConfidentialMergeRequest();
}
}
available() {
@ -113,7 +140,9 @@ export default class CreateMergeRequestDropdown {
this.isCreatingBranch = true;
return axios
.post(this.createBranchPath)
.post(createEndpoint(this.projectPath, this.createBranchPath), {
confidential_issue_project_id: canCreateConfidentialMergeRequest() ? this.projectId : null,
})
.then(({ data }) => {
this.branchCreated = true;
window.location.href = data.url;
@ -125,7 +154,11 @@ export default class CreateMergeRequestDropdown {
this.isCreatingMergeRequest = true;
return axios
.post(this.createMrPath)
.post(this.createMrPath, {
target_project_id: canCreateConfidentialMergeRequest()
? confidentialMergeRequestState.selectedProject.id
: null,
})
.then(({ data }) => {
this.mergeRequestCreated = true;
window.location.href = data.url;
@ -149,6 +182,8 @@ export default class CreateMergeRequestDropdown {
}
enable() {
if (!canCreateConfidentialMergeRequest()) return;
this.createMergeRequestButton.classList.remove('disabled');
this.createMergeRequestButton.removeAttribute('disabled');
@ -205,7 +240,7 @@ export default class CreateMergeRequestDropdown {
if (!ref) return false;
return axios
.get(`${this.refsPath}${encodeURIComponent(ref)}`)
.get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`)
.then(({ data }) => {
const branches = data[Object.keys(data)[0]];
const tags = data[Object.keys(data)[1]];
@ -325,6 +360,12 @@ export default class CreateMergeRequestDropdown {
let xhr = null;
event.preventDefault();
if (isConfidentialIssue() && !event.target.classList.contains('js-create-target')) {
this.droplab.hooks.forEach(hook => hook.list.toggle());
return;
}
if (this.isBusy()) {
return;
}

View File

@ -24,6 +24,11 @@ export default {
required: false,
default: '',
},
hasDraft: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
className() {
@ -55,6 +60,7 @@ export default {
:help-page-path="helpPagePath"
/>
<diff-discussion-reply
v-if="!hasDraft"
:has-form="line.hasForm"
:render-reply-placeholder="Boolean(line.discussions.length)"
@showNewDiscussionForm="

View File

@ -57,6 +57,7 @@ export default {
:diff-file-hash="diffFile.file_hash"
:line="line"
:help-page-path="helpPagePath"
:has-draft="shouldRenderDraftRow(diffFile.file_hash, line) || false"
/>
<inline-draft-comment-row
v-if="shouldRenderDraftRow(diffFile.file_hash, line)"

View File

@ -28,6 +28,16 @@ export default {
required: false,
default: '',
},
hasDraftLeft: {
type: Boolean,
required: false,
default: false,
},
hasDraftRight: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
hasExpandedDiscussionOnLeft() {
@ -121,6 +131,7 @@ export default {
/>
</div>
<diff-discussion-reply
v-if="!hasDraftLeft"
:has-form="showLeftSideCommentForm"
:render-reply-placeholder="shouldRenderReplyPlaceholderOnLeft"
@showNewDiscussionForm="showNewDiscussionForm"
@ -145,6 +156,7 @@ export default {
/>
</div>
<diff-discussion-reply
v-if="!hasDraftRight"
:has-form="showRightSideCommentForm"
:render-reply-placeholder="shouldRenderReplyPlaceholderOnRight"
@showNewDiscussionForm="showNewDiscussionForm"

View File

@ -58,6 +58,8 @@ export default {
:diff-file-hash="diffFile.file_hash"
:line-index="index"
:help-page-path="helpPagePath"
:has-draft-left="hasParallelDraftLeft(diffFile.file_hash, line) || false"
:has-draft-right="hasParallelDraftRight(diffFile.file_hash, line) || false"
/>
<parallel-draft-comment-row
v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"

View File

@ -6,5 +6,7 @@ export default {
imageDiscussions() {
return this.diffFile.discussions;
},
hasParallelDraftLeft: () => () => false,
hasParallelDraftRight: () => () => false,
},
};

View File

@ -1 +1,2 @@
// Noop function which has a EE counter-part
export default () => {};

View File

@ -144,7 +144,9 @@ export default {
'triggerFilesChange',
]),
initEditor() {
if (this.shouldHideEditor) return;
if (this.shouldHideEditor && (this.file.content || this.file.raw)) {
return;
}
this.editor.clearEditor();

View File

@ -77,6 +77,7 @@ export const decorateFiles = ({
const fileFolder = parent && insertParent(parent);
if (name) {
const previewMode = viewerInformationForPath(name);
parentPath = fileFolder && fileFolder.path;
file = decorateData({
@ -92,9 +93,9 @@ export const decorateFiles = ({
changed: tempFile,
content,
base64,
binary,
binary: (previewMode && previewMode.binary) || binary,
rawPath,
previewMode: viewerInformationForPath(name),
previewMode,
parentPath,
});

View File

@ -1,6 +1,7 @@
import * as types from '../mutation_types';
import { sortTree } from '../utils';
import { diffModes } from '../../constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
export default {
[types.SET_FILE_ACTIVE](state, { path, active }) {
@ -35,19 +36,18 @@ export default {
}
},
[types.SET_FILE_DATA](state, { data, file }) {
Object.assign(state.entries[file.path], {
id: data.id,
blamePath: data.blame_path,
commitsPath: data.commits_path,
permalink: data.permalink,
rawPath: data.raw_path,
binary: data.binary,
renderError: data.render_error,
raw: (state.entries[file.path] && state.entries[file.path].raw) || null,
baseRaw: null,
html: data.html,
size: data.size,
lastCommitSha: data.last_commit_sha,
const stateEntry = state.entries[file.path];
const stagedFile = state.stagedFiles.find(f => f.path === file.path);
const openFile = state.openFiles.find(f => f.path === file.path);
const changedFile = state.changedFiles.find(f => f.path === file.path);
[stateEntry, stagedFile, openFile, changedFile].forEach(f => {
if (f) {
Object.assign(f, convertObjectPropsToCamelCase(data, { dropKeys: ['raw', 'baseRaw'] }), {
raw: (stateEntry && stateEntry.raw) || null,
baseRaw: null,
});
}
});
},
[types.SET_FILE_RAW_DATA](state, { file, raw }) {

View File

@ -1,4 +1,4 @@
/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback, no-unused-vars */
/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback */
import $ from 'jquery';
import _ from 'underscore';
@ -7,7 +7,7 @@ import Flash from './flash';
import { __ } from './locale';
export default {
init({ container, form, issues, prefixId } = {}) {
init({ form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_';
this.form = form || this.getElement('.bulk-update');
this.$labelDropdown = this.form.find('.js-label-select');

View File

@ -2,26 +2,13 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { s__, __ } from './locale';
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import issuableInitBulkUpdateSidebar from './issuable_init_bulk_update_sidebar';
export default class IssuableIndex {
constructor(pagePrefix) {
this.initBulkUpdate(pagePrefix);
issuableInitBulkUpdateSidebar.init(pagePrefix);
IssuableIndex.resetIncomingEmailToken();
}
initBulkUpdate(pagePrefix) {
const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
if (userCanBulkUpdate && !alreadyInitialized) {
IssuableBulkUpdateActions.init({
prefixId: pagePrefix,
});
this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
}
}
static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset');

View File

@ -0,0 +1,19 @@
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import issuableBulkUpdateActions from './issuable_bulk_update_actions';
export default {
bulkUpdateSidebar: null,
init(prefixId) {
const bulkUpdateEl = document.querySelector('.issues-bulk-update');
const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
if (bulkUpdateEl && !alreadyInitialized) {
issuableBulkUpdateActions.init({ prefixId });
this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
}
return this.bulkUpdateSidebar;
},
};

View File

@ -11,7 +11,7 @@ import CreateLabelDropdown from './create_label';
import flash from './flash';
import ModalStore from './boards/stores/modal_store';
import boardsStore from './boards/stores/boards_store';
import { isEE, isScopedLabel } from '~/lib/utils/common_utils';
import { isScopedLabel } from '~/lib/utils/common_utils';
export default class LabelsSelect {
constructor(els, options = {}) {
@ -140,7 +140,7 @@ export default class LabelsSelect {
labelCount = data.labels.length;
// EE Specific
if (isEE) {
if (IS_EE) {
/**
* For Scoped labels, the last label selected with the
* same key will be applied to the current issueable.

View File

@ -726,14 +726,6 @@ export const NavigationType = {
TYPE_RESERVED: 255,
};
/**
* Returns the value of `gon.ee`
* Used to check if it's the EE codebase or the CE one.
*
* @returns Boolean
*/
export const isEE = () => window.gon && window.gon.ee;
/**
* Checks if the given Label has a special syntax `::` in
* it's title.

View File

@ -33,6 +33,8 @@ import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
import { __ } from './locale';
import 'ee_else_ce/main_ee';
// expose jQuery as global (TODO: remove these)
window.jQuery = jQuery;
window.$ = jQuery;
@ -119,11 +121,15 @@ function deferredInitialisation() {
.catch(() => {});
}
const glTooltipDelay = localStorage.getItem('gl-tooltip-delay');
const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0;
// Initialize tooltips
$body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]',
trigger: 'hover',
boundary: 'viewport',
delay,
});
// Initialize popovers

View File

@ -0,0 +1 @@
// This is an empty file to satisfy ee_else_ce import for the EE main entry point

View File

@ -4,7 +4,6 @@ import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import '~/vue_shared/mixins/is_ee';
import { getParameterValues } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import MonitorAreaChart from './charts/area.vue';
@ -124,6 +123,11 @@ export default {
required: false,
default: '',
},
smallEmptyState: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -155,6 +159,12 @@ export default {
selectedDashboardText() {
return this.currentDashboard || (this.allDashboards[0] && this.allDashboards[0].display_name);
},
addingMetricsAvailable() {
return IS_EE && this.canAddMetrics && !this.showEmptyState;
},
alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint;
},
},
created() {
this.setEndpoints({
@ -308,7 +318,7 @@ export default {
</div>
</div>
<div class="d-flex">
<div v-if="isEE && canAddMetrics && !showEmptyState">
<div v-if="addingMetricsAvailable">
<gl-button
v-gl-modal-directive="$options.addMetric.modalId"
class="js-add-metric-button text-success border-success"
@ -367,7 +377,7 @@ export default {
group-id="monitor-area-chart"
>
<alert-widget
v-if="isEE && prometheusAlertsAvailable && alertsEndpoint && graphData"
v-if="alertWidgetAvailable && graphData"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.queries"
:alerts-to-manage="getGraphAlerts(graphData.queries)"
@ -386,6 +396,7 @@ export default {
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-no-data-svg-path="emptyNoDataSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
:compact="smallEmptyState"
/>
</div>
</template>

View File

@ -1,7 +1,11 @@
<script>
import { __ } from '~/locale';
import { GlEmptyState } from '@gitlab/ui';
export default {
components: {
GlEmptyState,
},
props: {
documentationPath: {
type: String,
@ -37,6 +41,11 @@ export default {
type: String,
required: true,
},
compact: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -58,6 +67,8 @@ export default {
If this takes a long time, ensure that data is available.`),
buttonText: __('View documentation'),
buttonPath: this.documentationPath,
secondaryButtonText: '',
secondaryButtonPath: '',
},
noData: {
svgUrl: this.emptyNoDataSvgPath,
@ -66,13 +77,19 @@ export default {
no data to display.`),
buttonText: __('Configure Prometheus'),
buttonPath: this.settingsPath,
secondaryButtonText: '',
secondaryButtonPath: '',
},
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: __('Unable to connect to Prometheus server'),
description: 'Ensure connectivity is available from the GitLab server to the ',
description: __(
'Ensure connectivity is available from the GitLab server to the Prometheus server',
),
buttonText: __('View documentation'),
buttonPath: this.documentationPath,
secondaryButtonText: __('Configure Prometheus'),
secondaryButtonPath: this.settingsPath,
},
},
};
@ -81,45 +98,19 @@ export default {
currentState() {
return this.states[this.selectedState];
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
},
},
};
</script>
<template>
<div class="row empty-state js-empty-state">
<div class="col-12">
<div class="state-svg svg-content">
<img :src="currentState.svgUrl" />
</div>
</div>
<div class="col-12">
<div class="text-content">
<h4 class="state-title text-center">{{ currentState.title }}</h4>
<p class="state-description">
{{ currentState.description }}
<a v-if="showButtonDescription" :href="settingsPath">{{ __('Prometheus server') }}</a>
</p>
<div class="text-center">
<a
v-if="currentState.buttonPath"
:href="currentState.buttonPath"
class="btn btn-success"
>{{ currentState.buttonText }}</a
>
<a
v-if="currentState.secondaryButtonPath"
:href="currentState.secondaryButtonPath"
class="btn"
>{{ currentState.secondaryButtonText }}</a
>
</div>
</div>
</div>
</div>
<gl-empty-state
:title="currentState.title"
:description="currentState.description"
:primary-button-text="currentState.buttonText"
:primary-button-link="currentState.buttonPath"
:secondary-button-text="currentState.secondaryButtonText"
:secondary-button-link="currentState.secondaryButtonPath"
:svg-path="currentState.svgUrl"
:compact="compact"
/>
</template>

View File

@ -36,15 +36,26 @@ function removeTimeSeriesNoData(queries) {
// { metricId: 2, ...query2Attrs }] },
// { title: 'new title', y_label: 'MB', queries: [{ metricId: 3, ...query3Attrs }]}
// ]
function groupQueriesByChartInfo(metrics) {
export function groupQueriesByChartInfo(metrics) {
const metricsByChart = metrics.reduce((accumulator, metric) => {
const { queries, ...chart } = metric;
const metricId = chart.id ? chart.id.toString() : null;
const chartKey = `${chart.title}|${chart.y_label}`;
accumulator[chartKey] = accumulator[chartKey] || { ...chart, queries: [] };
queries.forEach(queryAttrs => accumulator[chartKey].queries.push({ metricId, ...queryAttrs }));
queries.forEach(queryAttrs => {
let metricId;
if (chart.id) {
metricId = chart.id.toString();
} else if (queryAttrs.metric_id) {
metricId = queryAttrs.metric_id.toString();
} else {
metricId = null;
}
accumulator[chartKey].queries.push({ metricId, ...queryAttrs });
});
return accumulator;
}, {});

View File

@ -13,6 +13,7 @@ import {
splitCamelCase,
slugifyWithUnderscore,
} from '../../lib/utils/text_utility';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
@ -65,14 +66,12 @@ export default {
return this.getUserData.id;
},
commentButtonTitle() {
return this.noteType === constants.COMMENT ? 'Comment' : 'Start thread';
return this.noteType === constants.COMMENT ? __('Comment') : __('Start thread');
},
startDiscussionDescription() {
let text = 'Discuss a specific suggestion or question';
if (this.getNoteableData.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE) {
text += ' that needs to be resolved';
}
return `${text}.`;
return this.getNoteableData.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE
? __('Discuss a specific suggestion or question that needs to be resolved.')
: __('Discuss a specific suggestion or question.');
},
isOpen() {
return this.openState === constants.OPENED || this.openState === constants.REOPENED;
@ -127,8 +126,8 @@ export default {
},
issuableTypeTitle() {
return this.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE
? 'merge request'
: 'issue';
? __('merge request')
: __('issue');
},
trackingLabel() {
return slugifyWithUnderscore(`${this.commentButtonTitle} button`);
@ -203,7 +202,7 @@ export default {
this.discard();
} else {
Flash(
'Something went wrong while adding your comment. Please try again.',
__('Something went wrong while adding your comment. Please try again.'),
'alert',
this.$refs.commentForm,
);
@ -219,8 +218,9 @@ export default {
.catch(() => {
this.enableButton();
this.discard(false);
const msg = `Your comment could not be submitted!
Please check your network connection and try again.`;
const msg = __(
'Your comment could not be submitted! Please check your network connection and try again.',
);
Flash(msg, 'alert', this.$el);
this.note = noteData.data.note.note; // Restore textarea content.
this.removePlaceholderNotes();
@ -235,7 +235,10 @@ Please check your network connection and try again.`;
toggleIssueState() {
if (this.isOpen) {
this.closeIssue()
.then(() => this.enableButton())
.then(() => {
this.enableButton();
refreshUserMergeRequestCounts();
})
.catch(() => {
this.enableButton();
this.toggleStateButtonLoading(false);
@ -248,7 +251,10 @@ Please check your network connection and try again.`;
});
} else {
this.reopenIssue()
.then(() => this.enableButton())
.then(() => {
this.enableButton();
refreshUserMergeRequestCounts();
})
.catch(({ data }) => {
this.enableButton();
this.toggleStateButtonLoading(false);
@ -298,7 +304,7 @@ Please check your network connection and try again.`;
const noteableType = capitalizeFirstCharacter(convertToCamelCase(this.noteableType));
this.autosave = new Autosave($(this.$refs.textarea), [
'Note',
__('Note'),
noteableType,
this.getNoteableData.id,
]);
@ -359,8 +365,8 @@ Please check your network connection and try again.`;
class="note-textarea js-vue-comment-form js-note-text
js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()"
@keydown.ctrl.enter="handleSave()"
@ -381,7 +387,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
data-track-event="click_button"
@click.prevent="handleSave()"
>
{{ __(commentButtonTitle) }}
{{ commentButtonTitle }}
</button>
<button
:disabled="isSubmitButtonDisabled"
@ -390,7 +396,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-success note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown"
data-display="static"
data-toggle="dropdown"
aria-label="Open comment type dropdown"
:aria-label="__('Open comment type dropdown')"
>
<i aria-hidden="true" class="fa fa-caret-down toggle-icon"> </i>
</button>
@ -404,8 +410,14 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
<strong>Comment</strong>
<p>Add a general comment to this {{ noteableDisplayName }}.</p>
<strong>{{ __('Comment') }}</strong>
<p>
{{
sprintf(__('Add a general comment to this %{noteableDisplayName}.'), {
noteableDisplayName,
})
}}
</p>
</div>
</button>
</li>
@ -418,7 +430,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
<strong>Start thread</strong>
<strong>{{ __('Start thread') }}</strong>
<p>{{ startDiscussionDescription }}</p>
</div>
</button>

View File

@ -100,7 +100,7 @@ export default {
class="btn-link btn-link-retry btn-no-padding js-toggle-lazy-diff-retry-button"
@click="fetchDiff"
>
Try again
{{ __('Try again') }}
</button>
</td>
<td v-else class="line_content js-success-lazy-load">

View File

@ -8,12 +8,14 @@ import SystemNote from '~/vue_shared/components/notes/system_note.vue';
import NoteableNote from './noteable_note.vue';
import ToggleRepliesWidget from './toggle_replies_widget.vue';
import NoteEditedText from './note_edited_text.vue';
import DiscussionNotesRepliesWrapper from './discussion_notes_replies_wrapper.vue';
export default {
name: 'DiscussionNotes',
components: {
ToggleRepliesWidget,
NoteEditedText,
DiscussionNotesRepliesWrapper,
},
props: {
discussion: {
@ -119,9 +121,7 @@ export default {
/>
<slot slot="avatar-badge" name="avatar-badge"></slot>
</component>
<div
:class="discussion.diff_discussion ? 'discussion-collapsible bordered-box clearfix' : ''"
>
<discussion-notes-replies-wrapper :is-diff-discussion="discussion.diff_discussion">
<toggle-replies-widget
v-if="hasReplies"
:collapsed="!isExpanded"
@ -141,7 +141,7 @@ export default {
/>
</template>
<slot :show-replies="isExpanded || !hasReplies" name="footer"></slot>
</div>
</discussion-notes-replies-wrapper>
</template>
<template v-else>
<component

View File

@ -0,0 +1,27 @@
<script>
/**
* Wrapper for discussion notes replies section.
*
* This is a functional component using the render method because in some cases
* the wrapper is not needed and we want to simply render along the children.
*/
export default {
functional: true,
props: {
isDiffDiscussion: {
type: Boolean,
required: false,
default: false,
},
},
render(h, { props, children }) {
if (props.isDiffDiscussion) {
return h('li', { class: 'discussion-collapsible bordered-box clearfix' }, [
h('ul', { class: 'notes' }, children),
]);
}
return children;
},
};
</script>

View File

@ -4,6 +4,7 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import Flash from '../../flash';
import { glEmojiTag } from '../../emoji';
import { __, sprintf } from '~/locale';
export default {
components: {
@ -108,23 +109,26 @@ export default {
// Add myself to the beginning of the list so title will start with You.
if (hasReactionByCurrentUser) {
namesToShow.unshift('You');
namesToShow.unshift(__('You'));
}
let title = '';
// We have 10+ awarded user, join them with comma and add `and x more`.
if (remainingAwardList.length) {
title = `${namesToShow.join(', ')}, and ${remainingAwardList.length} more.`;
title = sprintf(__(`%{listToShow}, and %{awardsListLength} more.`), {
listToShow: namesToShow.join(', '),
awardsListLength: remainingAwardList.length,
});
} else if (namesToShow.length > 1) {
// Join all names with comma but not the last one, it will be added with and text.
title = namesToShow.slice(0, namesToShow.length - 1).join(', ');
// If we have more than 2 users we need an extra comma before and text.
title += namesToShow.length > 2 ? ',' : '';
title += ` and ${namesToShow.slice(-1)}`; // Append and text
title += sprintf(__(` and %{sliced}`), { sliced: namesToShow.slice(-1) }); // Append and text
} else {
// We have only 2 users so join them with and.
title = namesToShow.join(' and ');
title = namesToShow.join(__(' and '));
}
return title;
@ -155,7 +159,7 @@ export default {
awardName: parsedName,
};
this.toggleAwardRequest(data).catch(() => Flash('Something went wrong on our end.'));
this.toggleAwardRequest(data).catch(() => Flash(__('Something went wrong on our end.')));
},
},
};
@ -184,7 +188,7 @@ export default {
:class="{ 'js-user-authored': isAuthoredByMe }"
class="award-control btn js-add-award"
title="Add reaction"
aria-label="Add reaction"
:aria-label="__('Add reaction')"
data-boundary="viewport"
type="button"
>

View File

@ -1,14 +1,14 @@
<script>
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { mapGetters, mapActions } from 'vuex';
import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
import { __ } from '~/locale';
import { __, sprintf } from '~/locale';
import { getDraft, updateDraft } from '~/lib/utils/autosave';
import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
export default {
name: 'NoteForm',
@ -174,6 +174,18 @@ export default {
(this.line && this.line.can_receive_suggestion)
);
},
changedCommentText() {
return sprintf(
__(
'This comment has changed since you started editing, please review the %{startTag}updated comment%{endTag} to ensure information is not lost.',
),
{
startTag: `<a href="${this.noteHash}" target="_blank" rel="noopener noreferrer">`,
endTag: '</a>',
},
false,
);
},
},
watch: {
noteBody() {
@ -228,11 +240,11 @@ export default {
<template>
<div ref="editNoteForm" class="note-edit-form current-note-edit-form js-discussion-note-form">
<div v-if="conflictWhileEditing" class="js-conflict-edit-warning alert alert-danger">
This comment has changed since you started editing, please review the
<a :href="noteHash" target="_blank" rel="noopener noreferrer">updated comment</a> to ensure
information is not lost.
</div>
<div
v-if="conflictWhileEditing"
class="js-conflict-edit-warning alert alert-danger"
v-html="changedCommentText"
></div>
<div class="flash-container timeline-content"></div>
<form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form">
<issue-warning
@ -264,8 +276,8 @@ export default {
name="note[note]"
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
dir="auto"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.meta.enter="handleKeySubmit()"
@keydown.ctrl.enter="handleKeySubmit()"
@keydown.exact.up="editMyLastNote()"
@ -339,7 +351,7 @@ export default {
type="button"
@click="cancelHandler()"
>
Cancel
{{ __('Cancel') }}
</button>
</template>
</div>

View File

@ -103,7 +103,7 @@ export default {
</template>
<i
class="fa fa-spinner fa-spin editing-spinner"
aria-label="Comment is being updated"
:aria-label="__('Comment is being updated')"
aria-hidden="true"
></i>
</span>

View File

@ -1,5 +1,6 @@
<script>
import { mapGetters } from 'vuex';
import { __, sprintf } from '~/locale';
export default {
computed: {
@ -10,12 +11,24 @@ export default {
signInLink() {
return this.getNotesDataByProp('newSessionPath');
},
signedOutText() {
return sprintf(
__(
'Please %{startTagRegister}register%{endRegisterTag} or %{startTagSignIn}sign in%{endSignInTag} to reply',
),
{
startTagRegister: `<a href="${this.registerLink}">`,
startTagSignIn: `<a href="${this.signInLink}">`,
endRegisterTag: '</a>',
endSignInTag: '</a>',
},
false,
);
},
},
};
</script>
<template>
<div class="disabled-comment text-center">
Please <a :href="registerLink">register</a> or <a :href="signInLink">sign in</a> to reply
</div>
<div class="disabled-comment text-center" v-html="signedOutText"></div>
</template>

View File

@ -144,15 +144,6 @@ export default {
return {};
},
componentClassName() {
if (this.shouldRenderDiffs) {
if (!this.lastUpdatedAt && !this.discussion.resolved) {
return 'unresolved';
}
}
return '';
},
isExpanded() {
return this.discussion.expanded || this.alwaysExpanded;
},
@ -283,8 +274,9 @@ export default {
this.removePlaceholderNotes();
this.isReplying = true;
this.$nextTick(() => {
const msg = `Your comment could not be submitted!
Please check your network connection and try again.`;
const msg = __(
'Your comment could not be submitted! Please check your network connection and try again.',
);
Flash(msg, 'alert', this.$el);
this.$refs.noteForm.note = noteText;
callback(err);
@ -312,11 +304,11 @@ Please check your network connection and try again.`;
</script>
<template>
<timeline-entry-item class="note note-discussion" :class="componentClassName">
<timeline-entry-item class="note note-discussion">
<div class="timeline-content">
<div :data-discussion-id="discussion.id" class="discussion js-discussion-container">
<div v-if="shouldRenderDiffs" class="discussion-header note-wrapper">
<div v-once class="timeline-icon">
<div v-once class="timeline-icon align-self-start flex-shrink-0">
<user-avatar-link
v-if="author"
:link-href="author.path"
@ -325,7 +317,7 @@ Please check your network connection and try again.`;
:img-size="40"
/>
</div>
<div class="timeline-content">
<div class="timeline-content w-100">
<note-header
:author="author"
:created-at="firstNote.created_at"

View File

@ -5,7 +5,7 @@ import { escape } from 'underscore';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import draftMixin from 'ee_else_ce/notes/mixins/draft';
import { s__, sprintf } from '../../locale';
import { __, s__, sprintf } from '../../locale';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
@ -128,9 +128,13 @@ export default {
this.$emit('handleEdit');
},
deleteHandler() {
const typeOfComment = this.note.isDraft ? 'pending comment' : 'comment';
// eslint-disable-next-line no-alert
if (window.confirm(`Are you sure you want to delete this ${typeOfComment}?`)) {
const typeOfComment = this.note.isDraft ? __('pending comment') : __('comment');
if (
// eslint-disable-next-line no-alert
window.confirm(
sprintf(__('Are you sure you want to delete this %{typeOfComment}?'), { typeOfComment }),
)
) {
this.isDeleting = true;
this.$emit('handleDeleteNote', this.note);
@ -141,7 +145,7 @@ export default {
this.isDeleting = false;
})
.catch(() => {
Flash('Something went wrong while deleting your note. Please try again.');
Flash(__('Something went wrong while deleting your note. Please try again.'));
this.isDeleting = false;
});
}
@ -185,7 +189,7 @@ export default {
this.isRequesting = false;
this.isEditing = true;
this.$nextTick(() => {
const msg = 'Something went wrong while editing your comment. Please try again.';
const msg = __('Something went wrong while editing your comment. Please try again.');
Flash(msg, 'alert', this.$el);
this.recoverNoteContent(noteText);
callback();
@ -195,7 +199,7 @@ export default {
formCancelHandler(shouldConfirm, isDirty) {
if (shouldConfirm && isDirty) {
// eslint-disable-next-line no-alert
if (!window.confirm('Are you sure you want to cancel editing this comment?')) return;
if (!window.confirm(__('Are you sure you want to cancel editing this comment?'))) return;
}
this.$refs.noteBody.resetAutoSave();
if (this.oldContent) {

View File

@ -1,4 +1,5 @@
<script>
import { __ } from '~/locale';
import { mapGetters, mapActions } from 'vuex';
import { getLocationHash } from '../../lib/utils/url_utility';
import Flash from '../../flash';
@ -170,7 +171,7 @@ export default {
.catch(() => {
this.setLoadingState(false);
this.setNotesFetchedState(true);
Flash('Something went wrong while fetching comments. Please try again.');
Flash(__('Something went wrong while fetching comments. Please try again.'));
});
},
initPolling() {

View File

@ -1,5 +1,4 @@
import Vue from 'vue';
import { isEE } from '~/lib/utils/common_utils';
import initNoteStats from 'ee_else_ce/event_tracking/notes';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
@ -41,9 +40,7 @@ document.addEventListener('DOMContentLoaded', () => {
};
},
mounted() {
if (isEE) {
initNoteStats();
}
initNoteStats();
},
render(createElement) {
return createElement('notes-app', {

View File

@ -1,5 +1,4 @@
import Vue from 'vue';
import Api from '~/api';
import VueResource from 'vue-resource';
import * as constants from '../constants';
@ -45,7 +44,4 @@ export default {
toggleIssueState(endpoint, data) {
return Vue.http.put(endpoint, data);
},
applySuggestion(id) {
return Api.applySuggestion(id);
},
};

View File

@ -14,6 +14,7 @@ import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement, isInMRPage } from '../../lib/utils/common_utils';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
import { __ } from '~/locale';
import Api from '~/api';
let eTagPoll;
@ -449,8 +450,7 @@ export const submitSuggestion = (
{ commit, dispatch },
{ discussionId, noteId, suggestionId, flashContainer },
) =>
service
.applySuggestion(suggestionId)
Api.applySuggestion(suggestionId)
.then(() => commit(types.APPLY_SUGGESTION, { discussionId, noteId, suggestionId }))
.then(() => dispatch('resolveDiscussion', { discussionId }).catch(() => {}))
.catch(err => {

View File

@ -82,7 +82,7 @@ export default class Todos {
})
.catch(() => {
this.updateRowState(target, true);
return flash(__('Error updating todo status.'));
return flash(__('Error updating status of to-do item.'));
});
}
@ -124,7 +124,7 @@ export default class Todos {
this.updateAllState(target, data);
this.updateBadges(data);
})
.catch(() => flash(__('Error updating status for all todos.')));
.catch(() => flash(__('Error updating status for all to-do items.')));
}
updateAllState(target, data) {

View File

@ -1,11 +1,15 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
import { FILTERED_SEARCH } from '~/pages/constants';
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import initManualOrdering from '~/manual_ordering';
const ISSUE_BULK_UPDATE_PREFIX = 'issue_';
document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
issuableInitBulkUpdateSidebar.init(ISSUE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,

View File

@ -17,7 +17,5 @@ export default () => {
new MilestoneSelect();
new IssuableTemplateSelectors();
if (gon.features.graphql) {
initSuggestions();
}
initSuggestions();
};

View File

@ -76,7 +76,7 @@ export default {
variables: {
projectPath: this.projectPath,
ref: this.ref,
path: this.path,
path: this.path || '/',
nextPageCursor: this.nextPageCursor,
pageSize: PAGE_SIZE,
},

View File

@ -1,4 +1,6 @@
<script>
import { n__ } from '~/locale';
export default {
name: 'AssigneeTitle',
props: {
@ -24,7 +26,7 @@ export default {
computed: {
assigneeTitle() {
const assignees = this.numberOfAssignees;
return assignees > 1 ? `${assignees} Assignees` : 'Assignee';
return n__('Assignee', `%d Assignees`, assignees);
},
},
};
@ -32,18 +34,18 @@ export default {
<template>
<div class="title hide-collapsed">
{{ assigneeTitle }}
<i v-if="loading" aria-hidden="true" class="fa fa-spinner fa-spin block-loading"> </i>
<i v-if="loading" aria-hidden="true" class="fa fa-spinner fa-spin block-loading"></i>
<a v-if="editable" class="js-sidebar-dropdown-toggle edit-link float-right" href="#">
{{ __('Edit') }}
</a>
<a
v-if="showToggle"
aria-label="Toggle sidebar"
:aria-label="__('Toggle sidebar')"
class="gutter-toggle float-right js-sidebar-toggle"
href="#"
role="button"
>
<i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"> </i>
<i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i>
</a>
</div>
</template>

View File

@ -1,5 +1,5 @@
<script>
import { __ } from '~/locale';
import { __, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
@ -62,7 +62,8 @@ export default {
return this.numberOfHiddenAssignees > 0;
},
hiddenAssigneesLabel() {
return `+ ${this.numberOfHiddenAssignees} more`;
const { numberOfHiddenAssignees } = this;
return sprintf(__('+ %{numberOfHiddenAssignees} more'), { numberOfHiddenAssignees });
},
collapsedTooltipTitle() {
const maxRender = Math.min(this.defaultRenderCount, this.users.length);
@ -103,12 +104,15 @@ export default {
// Everyone can merge
return null;
} else if (cannotMergeCount === assigneesCount && assigneesCount > 1) {
return 'No one can merge';
return __('No one can merge');
} else if (assigneesCount === 1) {
return 'Cannot merge';
return __('Cannot merge');
}
return `${canMergeCount}/${assigneesCount} can merge`;
return sprintf(__('%{canMergeCount}/%{assigneesCount} can merge'), {
canMergeCount,
assigneesCount,
});
},
},
methods: {
@ -128,7 +132,7 @@ export default {
return `${this.rootPath}${user.username}`;
},
assigneeAlt(user) {
return `${user.name}'s avatar`;
return sprintf(__("%{userName}'s avatar"), { userName: user.name });
},
assigneeUsername(user) {
return `@${user.username}`;
@ -153,7 +157,7 @@ export default {
data-placement="left"
data-boundary="viewport"
>
<i v-if="hasNoUsers" aria-label="None" class="fa fa-user"> </i>
<i v-if="hasNoUsers" :aria-label="__('None')" class="fa fa-user"> </i>
<button
v-for="(user, index) in users"
v-if="shouldRenderCollapsedAssignee(index)"
@ -185,9 +189,12 @@ export default {
</span>
<template v-if="hasNoUsers">
<span class="assign-yourself no-value qa-assign-yourself">
None
{{ __('None') }}
<template v-if="editable">
- <button type="button" class="btn-link" @click="assignSelf">assign yourself</button>
-
<button type="button" class="btn-link" @click="assignSelf">
{{ __('assign yourself') }}
</button>
</template>
</span>
</template>
@ -232,9 +239,7 @@ export default {
<template v-if="showLess">
{{ hiddenAssigneesLabel }}
</template>
<template v-else>
- show less
</template>
<template v-else>{{ __('- show less') }}</template>
</button>
</div>
</template>

View File

@ -2,8 +2,10 @@
import Flash from '~/flash';
import eventHub from '~/sidebar/event_hub';
import Store from '~/sidebar/stores/sidebar_store';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import AssigneeTitle from './assignee_title.vue';
import Assignees from './assignees.vue';
import { __ } from '~/locale';
export default {
name: 'SidebarAssignees',
@ -72,9 +74,12 @@ export default {
this.mediator
.saveAssignees(this.field)
.then(setLoadingFalse.bind(this))
.then(() => {
refreshUserMergeRequestCounts();
})
.catch(() => {
setLoadingFalse();
return new Flash('Error occurred when saving assignees');
return new Flash(__('Error occurred when saving assignees'));
});
},
},

View File

@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
import eventHub from '../../event_hub';
import { __ } from '~/locale';
export default {
props: {
@ -15,7 +16,7 @@ export default {
},
computed: {
toggleButtonText() {
return this.isConfidential ? 'Turn Off' : 'Turn On';
return this.isConfidential ? __('Turn Off') : __('Turn On');
},
updateConfidentialBool() {
return !this.isConfidential;

View File

@ -79,7 +79,7 @@ export default {
} else if (this.showSpentOnlyState) {
return `${this.timeSpent} / --`;
} else if (this.showNoTimeTrackingState) {
return 'None';
return __('None');
}
return '';

View File

@ -2,6 +2,7 @@
import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import tooltip from '../../../vue_shared/directives/tooltip';
import { GlProgressBar } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
export default {
name: 'TimeTrackingComparisonPane',
@ -43,8 +44,14 @@ export default {
return stringifyTime(this.parsedTimeRemaining);
},
timeRemainingTooltip() {
const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
return `${prefix} ${this.timeRemainingHumanReadable}`;
const { timeRemainingHumanReadable, timeRemainingMinutes } = this;
return timeRemainingMinutes < 0
? sprintf(s__('TimeTracking|Over by %{timeRemainingHumanReadable}'), {
timeRemainingHumanReadable,
})
: sprintf(s__('TimeTracking|Time remaining: %{timeRemainingHumanReadable}'), {
timeRemainingHumanReadable,
});
},
/* Diff values for comparison meter */
timeRemainingMinutes() {
@ -74,12 +81,12 @@ export default {
<gl-progress-bar :value="timeRemainingPercent" :variant="progressBarVariant" />
<div class="compare-display-container">
<div class="compare-display float-left">
<span class="compare-label"> {{ s__('TimeTracking|Spent') }} </span>
<span class="compare-value spent"> {{ timeSpentHumanReadable }} </span>
<span class="compare-label">{{ s__('TimeTracking|Spent') }}</span>
<span class="compare-value spent">{{ timeSpentHumanReadable }}</span>
</div>
<div class="compare-display estimated float-right">
<span class="compare-label"> {{ s__('TimeTrackingEstimated|Est') }} </span>
<span class="compare-value"> {{ timeEstimateHumanReadable }} </span>
<span class="compare-label">{{ s__('TimeTrackingEstimated|Est') }}</span>
<span class="compare-value">{{ timeEstimateHumanReadable }}</span>
</div>
</div>
</div>

View File

@ -1,4 +1,6 @@
<script>
import { sprintf, s__ } from '~/locale';
export default {
name: 'TimeTrackingSpentOnlyPane',
props: {
@ -7,11 +9,22 @@ export default {
required: true,
},
},
computed: {
timeSpent() {
return sprintf(
s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'),
{
startTag: '<span class="bold">',
endTag: '</span>',
timeSpentHumanReadable: this.timeSpentHumanReadable,
},
false,
);
},
},
};
</script>
<template>
<div class="time-tracking-spend-only-pane">
<span class="bold">Spent:</span> {{ timeSpentHumanReadable }}
</div>
<div class="time-tracking-spend-only-pane" v-html="timeSpent"></div>
</template>

View File

@ -5,8 +5,8 @@ import { GlLoadingIcon } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
const MARK_TEXT = __('Mark todo as done');
const TODO_TEXT = __('Add todo');
const MARK_TEXT = __('Mark as done');
const TODO_TEXT = __('Add a To Do');
export default {
directives: {

View File

@ -125,7 +125,9 @@ export default {
this.isStopping = false;
})
.catch(() => {
createFlash('Something went wrong while stopping this environment. Please try again.');
createFlash(
__('Something went wrong while stopping this environment. Please try again.'),
);
this.isStopping = false;
});
}

View File

@ -139,7 +139,7 @@ export default {
type="button"
class="btn dropdown-toggle qa-dropdown-toggle"
data-toggle="dropdown"
aria-label="Download as"
:aria-label="__('Download as')"
aria-haspopup="true"
aria-expanded="false"
>

View File

@ -6,6 +6,7 @@ import statusIcon from '../mr_widget_status_icon.vue';
import MrWidgetAuthor from '../../components/mr_widget_author.vue';
import eventHub from '../../event_hub';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
import { __ } from '~/locale';
export default {
name: 'MRWidgetAutoMergeEnabled',
@ -55,7 +56,7 @@ export default {
})
.catch(() => {
this.isCancellingAutoMerge = false;
Flash('Something went wrong. Please try again.');
Flash(__('Something went wrong. Please try again.'));
});
},
removeSourceBranch() {
@ -76,7 +77,7 @@ export default {
})
.catch(() => {
this.isRemovingSourceBranch = false;
Flash('Something went wrong. Please try again.');
Flash(__('Something went wrong. Please try again.'));
});
},
},
@ -107,15 +108,15 @@ export default {
<section class="mr-info-list">
<p>
{{ s__('mrWidget|The changes will be merged into') }}
<a :href="mr.targetBranchPath" class="label-branch"> {{ mr.targetBranch }} </a>
<a :href="mr.targetBranchPath" class="label-branch">{{ mr.targetBranch }}</a>
</p>
<p v-if="mr.shouldRemoveSourceBranch">
{{ s__('mrWidget|The source branch will be deleted') }}
</p>
<p v-else class="d-flex align-items-start">
<span class="append-right-10">
{{ s__('mrWidget|The source branch will not be deleted') }}
</span>
<span class="append-right-10">{{
s__('mrWidget|The source branch will not be deleted')
}}</span>
<a
v-if="canRemoveSourceBranch"
:disabled="isRemovingSourceBranch"

View File

@ -4,6 +4,7 @@ import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub';
import statusIcon from '../mr_widget_status_icon.vue';
import Flash from '../../../flash';
import { __, sprintf } from '~/locale';
export default {
name: 'MRWidgetRebase',
@ -40,6 +41,17 @@ export default {
showDisabledButton() {
return ['failed', 'loading'].includes(this.status);
},
fastForwardMergeText() {
return sprintf(
__(
`Fast-forward merge is not possible. Rebase the source branch onto %{startTag}${this.mr.targetBranch}%{endTag} to allow this merge request to be merged.`,
),
{
startTag: '<span class="label-branch">',
endTag: '</span>',
},
);
},
},
methods: {
rebase() {
@ -54,7 +66,7 @@ export default {
.catch(error => {
this.rebasingError = error.merge_error;
this.isMakingRequest = false;
Flash('Something went wrong. Please try again.');
Flash(__('Something went wrong. Please try again.'));
});
},
checkRebaseStatus(continuePolling, stopPolling) {
@ -69,7 +81,7 @@ export default {
if (res.merge_error && res.merge_error.length) {
this.rebasingError = res.merge_error;
Flash('Something went wrong. Please try again.');
Flash(__('Something went wrong. Please try again.'));
}
eventHub.$emit('MRWidgetRebaseSuccess');
@ -78,7 +90,7 @@ export default {
})
.catch(() => {
this.isMakingRequest = false;
Flash('Something went wrong. Please try again.');
Flash(__('Something went wrong. Please try again.'));
stopPolling();
});
},
@ -91,19 +103,14 @@ export default {
<div class="rebase-state-find-class-convention media media-body space-children">
<template v-if="mr.rebaseInProgress || isMakingRequest">
<span class="bold"> Rebase in progress </span>
<span class="bold">{{ __('Rebase in progress') }}</span>
</template>
<template v-if="!mr.rebaseInProgress && !mr.canPushToSourceBranch">
<span class="bold">
Fast-forward merge is not possible. Rebase the source branch onto
<span class="label-branch">{{ mr.targetBranch }}</span> to allow this merge request to be
merged.
</span>
<span class="bold" v-html="fastForwardMergeText"></span>
</template>
<template v-if="!mr.rebaseInProgress && mr.canPushToSourceBranch && !isMakingRequest">
<div
class="accept-merge-holder clearfix
js-toggle-container accept-action media space-children"
class="accept-merge-holder clearfix js-toggle-container accept-action media space-children"
>
<button
:disabled="isMakingRequest"
@ -111,14 +118,14 @@ js-toggle-container accept-action media space-children"
class="btn btn-sm btn-reopen btn-success qa-mr-rebase-button"
@click="rebase"
>
<gl-loading-icon v-if="isMakingRequest" />
Rebase
<gl-loading-icon v-if="isMakingRequest" />{{ __('Rebase') }}
</button>
<span v-if="!rebasingError" class="bold">
Fast-forward merge is not possible. Rebase the source branch onto the target branch or
merge target branch into source branch to allow this merge request to be merged.
</span>
<span v-else class="bold danger"> {{ rebasingError }} </span>
<span v-if="!rebasingError" class="bold">{{
__(
'Fast-forward merge is not possible. Rebase the source branch onto the target branch or merge target branch into source branch to allow this merge request to be merged.',
)
}}</span>
<span v-else class="bold danger">{{ rebasingError }}</span>
</div>
</template>
</div>

View File

@ -22,19 +22,29 @@ export default {
<span v-html="emptyStateSVG"></span>
</div>
<div class="text col-md-7 order-md-first col-12">
<span>
Merge requests are a place to propose changes you have made to a project and discuss those
changes with others.
</span>
<p>Interested parties can even contribute by pushing commits if they want to.</p>
<span>{{
s__(
'mrWidgetNothingToMerge|Merge requests are a place to propose changes you have made to a project and discuss those changes with others.',
)
}}</span>
<p>
Currently there are no changes in this merge request's source branch. Please push new
commits or use a different branch.
{{
s__(
'mrWidgetNothingToMerge|Interested parties can even contribute by pushing commits if they want to.',
)
}}
</p>
<p>
{{
s__(
"mrWidgetNothingToMerge|Currently there are no changes in this merge request's source branch. Please push new commits or use a different branch.",
)
}}
</p>
<div>
<a v-if="mr.newBlobPath" :href="mr.newBlobPath" class="btn btn-inverted btn-success">
Create file
</a>
<a v-if="mr.newBlobPath" :href="mr.newBlobPath" class="btn btn-inverted btn-success">{{
__('Create file')
}}</a>
</div>
</div>
</div>

View File

@ -6,6 +6,7 @@ import simplePoll from '~/lib/utils/simple_poll';
import { __ } from '~/locale';
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
import MergeRequest from '../../../merge_request';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
@ -174,6 +175,8 @@ export default {
MergeRequest.decreaseCounter();
stopPolling();
refreshUserMergeRequestCounts();
// If user checked remove source branch and we didn't remove the branch yet
// we should start another polling for source branch remove process
if (this.removeSourceBranch && data.source_branch_exists) {
@ -248,7 +251,7 @@ export default {
type="button"
class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
data-toggle="dropdown"
aria-label="Select merge moment"
:aria-label="__('Select merge moment')"
>
<i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
</button>

View File

@ -46,14 +46,20 @@ export default {
<status-icon :show-disabled-button="Boolean(mr.removeWIPPath)" status="warning" />
<div class="media-body space-children">
<span class="bold">
This is a Work in Progress
{{ __('This is a Work in Progress') }}
<i
v-tooltip
class="fa fa-question-circle"
title="When this merge request is ready,
remove the WIP: prefix from the title to allow it to be merged"
aria-label="When this merge request is ready,
remove the WIP: prefix from the title to allow it to be merged"
:title="
s__(
'mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged',
)
"
:aria-label="
s__(
'mrWidget|When this merge request is ready, remove the WIP: prefix from the title to allow it to be merged',
)
"
>
</i>
</span>
@ -64,8 +70,8 @@ export default {
class="btn btn-default btn-sm js-remove-wip"
@click="removeWIP"
>
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"> </i> Resolve WIP
status
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
{{ s__('mrWidget|Resolve WIP status') }}
</button>
</div>
</div>

View File

@ -263,8 +263,11 @@ export default {
if (!data.pipeline) return;
const { label } = data.pipeline.details.status;
const title = `Pipeline ${label}`;
const message = `Pipeline ${label} for "${data.title}"`;
const title = sprintf(__('Pipeline %{label}'), { label });
const message = sprintf(__('Pipeline %{label} for "%{dataTitle}"'), {
dataTitle: data.title,
label,
});
notify.notifyMe(title, message, this.mr.gitlabLogo);
},

View File

@ -1,7 +1,6 @@
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { pluralize } from '~/lib/utils/text_utility';
import { __, sprintf } from '~/locale';
import { getCommitIconMap } from '~/ide/utils';
@ -69,7 +68,7 @@ export default {
});
} else if (this.file.changed && this.file.staged) {
return sprintf(__('Unstaged and staged %{type}'), {
type: pluralize(type),
type,
});
}

View File

@ -1,6 +1,7 @@
<script>
import _ from 'underscore';
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from './user_avatar/user_avatar_link.vue';
import Icon from '../../vue_shared/components/icon.vue';
@ -129,7 +130,9 @@ export default {
* @returns {String}
*/
userImageAltDescription() {
return this.author && this.author.username ? `${this.author.username}'s avatar` : null;
return this.author && this.author.username
? sprintf(__("%{username}'s avatar"), { username: this.author.username })
: null;
},
},
};
@ -180,7 +183,7 @@ export default {
{{ title }}
</gl-link>
</tooltip-on-truncate>
<span v-else> Can't find HEAD commit for this branch </span>
<span v-else>{{ __("Can't find HEAD commit for this branch") }}</span>
</div>
</div>
</template>

View File

@ -3,6 +3,7 @@ import { __ } from '~/locale';
const viewers = {
image: {
id: 'image',
binary: true,
},
markdown: {
id: 'markdown',

View File

@ -91,7 +91,9 @@ export default {
|
</template>
<template v-if="hasDimensions">
<strong>W</strong>: {{ width }} | <strong>H</strong>: {{ height }}
<strong>{{ s__('ImageViewerDimensions|W') }}</strong
>: {{ width }} | <strong>{{ s__('ImageViewerDimensions|H') }}</strong
>: {{ height }}
</template>
</p>
</div>

View File

@ -40,7 +40,7 @@ export default {
this.fetchMarkdownPreview();
},
destroyed() {
if (this.isLoading) axiosSource.cancel('Cancelling Preview');
if (this.isLoading) axiosSource.cancel(__('Cancelling Preview'));
},
methods: {
fetchMarkdownPreview() {

View File

@ -1,5 +1,7 @@
<script>
/* eslint-disable vue/require-default-prop */
import { __ } from '~/locale';
export default {
name: 'DeprecatedModal', // use GlModal instead
@ -39,7 +41,7 @@ export default {
closeButtonLabel: {
type: String,
required: false,
default: 'Cancel',
default: __('Cancel'),
},
primaryButtonLabel: {
type: String,
@ -94,7 +96,7 @@ export default {
type="button"
class="close float-right"
data-dismiss="modal"
aria-label="Close"
:aria-label="__('Close')"
@click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>

View File

@ -69,7 +69,7 @@ export default {
data-display="static"
data-toggle="dropdown"
>
<icon name="arrow-down" aria-label="toggle dropdown" />
<icon name="arrow-down" :aria-label="__('toggle dropdown')" />
</button>
<ul :class="dropdownClass" class="dropdown-menu dropdown-open-top">
<template v-for="(action, index) in actions">

View File

@ -115,7 +115,7 @@ export default {
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
aria-label="Expand dropdown"
:aria-label="__('Expand dropdown')"
>
<icon name="angle-down" :size="12" />
</button>
@ -125,7 +125,7 @@ export default {
ref="searchInput"
v-model="filter"
type="search"
placeholder="Filter"
:placeholder="__('Filter')"
class="js-filtered-dropdown-input dropdown-input-field"
/>
<icon class="dropdown-input-search" name="search" />

View File

@ -1,5 +1,6 @@
<script>
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import CiIconBadge from './ci_badge_link.vue';
import TimeagoTooltip from './time_ago_tooltip.vue';
import UserAvatarImage from './user_avatar/user_avatar_image.vue';
@ -65,7 +66,7 @@ export default {
computed: {
userAvatarAltText() {
return `${this.user.name}'s avatar`;
return sprintf(__(`%{username}'s avatar`), { username: this.user.name });
},
},
@ -87,16 +88,12 @@ export default {
<strong> {{ itemName }} #{{ itemId }} </strong>
<template v-if="shouldRenderTriggeredLabel">
triggered
</template>
<template v-else>
created
</template>
<template v-if="shouldRenderTriggeredLabel">{{ __('triggered') }}</template>
<template v-else>{{ __('created') }}</template>
<timeago-tooltip :time="time" />
by
{{ __('by') }}
<template v-if="user">
<gl-link

View File

@ -1,7 +1,7 @@
<script>
import { GlLink } from '@gitlab/ui';
import _ from 'underscore';
import { sprintf } from '~/locale';
import { __, sprintf } from '~/locale';
import icon from '../../../vue_shared/components/icon.vue';
function buildDocsLinkStart(path) {
@ -47,7 +47,9 @@ export default {
},
confidentialAndLockedDiscussionText() {
return sprintf(
'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
__(
'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
),
{
confidentialLinkStart: buildDocsLinkStart(this.confidentialIssueDocsPath),
lockedLinkStart: buildDocsLinkStart(this.lockedIssueDocsPath),
@ -66,7 +68,7 @@ export default {
<span v-if="isLockedAndConfidential">
<span v-html="confidentialAndLockedDiscussionText"></span>
{{
__(`People without permission will never get a notification and won't be able to comment.`)
__("People without permission will never get a notification and won't be able to comment.")
}}
</span>

View File

@ -160,8 +160,8 @@ export default {
:disabled="removeDisabled"
type="button"
class="btn btn-default btn-svg btn-item-remove js-issue-item-remove-button qa-remove-issue-button mr-xl-0 align-self-xl-center"
title="Remove"
aria-label="Remove"
:title="__('Remove')"
:aria-label="__('Remove')"
@click="onRemoveRequest"
>
<icon :size="16" class="btn-item-remove-icon" name="close" />

View File

@ -1,7 +1,7 @@
<script>
import $ from 'jquery';
import _ from 'underscore';
import { __ } from '~/locale';
import { __, sprintf } from '~/locale';
import { stripHtml } from '~/lib/utils/text_utility';
import Flash from '../../../flash';
import GLForm from '../../../gl_form';
@ -118,6 +118,18 @@ export default {
lineType() {
return this.line ? this.line.type : '';
},
addMultipleToDiscussionWarning() {
return sprintf(
__(
'%{icon}You are about to add %{usersTag} people to the discussion. Proceed with caution.',
),
{
icon: '<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>',
usersTag: `<strong><span class="js-referenced-users-count">${this.referencedUsers.length}</span></strong>`,
},
false,
);
},
},
mounted() {
/*
@ -172,7 +184,7 @@ export default {
renderMarkdown(data = {}) {
this.markdownPreviewLoading = false;
this.markdownPreview = data.body || 'Nothing to preview.';
this.markdownPreview = data.body || __('Nothing to preview.');
if (data.references) {
this.referencedCommands = data.references.commands;
@ -207,7 +219,11 @@ export default {
<div v-show="!previewMarkdown" class="md-write-holder">
<div class="zen-backdrop">
<slot name="textarea"></slot>
<a class="zen-control zen-control-leave js-zen-leave" href="#" aria-label="Enter zen mode">
<a
class="zen-control zen-control-leave js-zen-leave"
href="#"
:aria-label="__('Enter zen mode')"
>
<icon :size="32" name="screen-normal" />
</a>
<markdown-toolbar
@ -246,13 +262,7 @@ export default {
<template v-if="previewMarkdown && !markdownPreviewLoading">
<div v-if="referencedCommands" class="referenced-commands" v-html="referencedCommands"></div>
<div v-if="shouldShowReferencedUsers" class="referenced-users">
<span>
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i> You are about to add
<strong>
<span class="js-referenced-users-count">{{ referencedUsers.length }}</span>
</strong>
people to the discussion. Proceed with caution.
</span>
<span v-html="addMultipleToDiscussionWarning"></span>
</div>
</template>
</div>

View File

@ -1,5 +1,6 @@
<script>
import Vue from 'vue';
import { __ } from '~/locale';
import SuggestionDiff from './suggestion_diff.vue';
import Flash from '~/flash';
@ -56,7 +57,7 @@ export default {
const suggestionElements = container.querySelectorAll('.js-render-suggestion');
if (this.lineType === 'old') {
Flash('Unable to apply suggestions to a deleted line.', 'alert', this.$el);
Flash(__('Unable to apply suggestions to a deleted line.'), 'alert', this.$el);
}
suggestionElements.forEach((suggestionEl, i) => {

View File

@ -33,13 +33,18 @@ export default {
<div class="comment-toolbar clearfix">
<div class="toolbar-text">
<template v-if="!hasQuickActionsDocsPath && markdownDocsPath">
<gl-link :href="markdownDocsPath" target="_blank" tabindex="-1"
>Markdown is supported</gl-link
>
<gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">{{
__('Markdown is supported')
}}</gl-link>
</template>
<template v-if="hasQuickActionsDocsPath && markdownDocsPath">
<gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">Markdown</gl-link> and
<gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">quick actions</gl-link>
<gl-link :href="markdownDocsPath" target="_blank" tabindex="-1">{{
__('Markdown')
}}</gl-link>
and
<gl-link :href="quickActionsDocsPath" target="_blank" tabindex="-1">{{
__('quick actions')
}}</gl-link>
are supported
</template>
</div>
@ -57,15 +62,17 @@ export default {
<i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i>
</span>
<span class="uploading-error-message"></span>
<button class="retry-uploading-link" type="button">Try again</button> or
<button class="attach-new-file markdown-selector" type="button">attach a new file</button>
<button class="retry-uploading-link" type="button">{{ __('Try again') }}</button> or
<button class="attach-new-file markdown-selector" type="button">
{{ __('attach a new file') }}
</button>
</span>
<button class="markdown-selector button-attach-file btn-link" tabindex="-1" type="button">
<i class="fa fa-file-image-o toolbar-button-icon" aria-hidden="true"></i
><span class="text-attach-file">Attach a file</span>
><span class="text-attach-file">{{ __('Attach a file') }}</span>
</button>
<button class="btn btn-default btn-sm hide button-cancel-uploading-files" type="button">
Cancel
{{ __('Cancel') }}
</button>
</span>
</div>

View File

@ -1,4 +1,5 @@
<script>
import { __, sprintf } from '~/locale';
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
@ -20,7 +21,7 @@ export default {
computed: {
getFormattedMedian() {
const deployedSince = getTimeago().format(this.deploymentTime * 1000);
return `Deployed ${deployedSince}`;
return sprintf(__('Deployed %{deployedSince}'), { deployedSince });
},
},
mounted() {

View File

@ -103,7 +103,7 @@ export default {
<div v-if="hasMoreCommits" class="flex-list">
<div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded">
<icon :name="toggleIcon" :size="8" class="append-right-5" />
<span>Toggle commit list</span>
<span>{{ __('Toggle commit list') }}</span>
</div>
</div>
</div>

View File

@ -14,7 +14,7 @@
/>
*/
import { __ } from '~/locale';
import defaultAvatarUrl from 'images/no_avatar.png';
import { placeholderImage } from '../../../lazy_loader';
@ -39,7 +39,7 @@ export default {
imgAlt: {
type: String,
required: false,
default: 'project avatar',
default: __('project avatar'),
},
size: {
type: Number,

View File

@ -85,7 +85,7 @@ export default {
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
<span v-if="showFromText">From</span> <span>{{ dateText('min') }}</span>
<span v-if="showFromText">{{ __('From') }}</span> <span>{{ dateText('min') }}</span>
</span>
</collapsed-calendar-icon>
<div v-if="hasMinAndMaxDates" class="text-center sidebar-collapsed-divider">-</div>
@ -96,7 +96,7 @@ export default {
@click="toggleSidebar"
>
<span class="sidebar-collapsed-value">
<span v-if="!minDate">Until</span> <span>{{ dateText('max') }}</span>
<span v-if="!minDate">{{ __('Until') }}</span> <span>{{ dateText('max') }}</span>
</span>
</collapsed-calendar-icon>
</div>

Some files were not shown because too many files have changed in this diff Show More