Commit d27a5b20 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-04-26

parents 4a56c554 c73b5d31
...@@ -25,12 +25,12 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ ...@@ -25,12 +25,12 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
- [Workflow labels](#workflow-labels) - [Workflow labels](#workflow-labels)
- [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc) - [Type labels (~"feature proposal", ~bug, ~customer, etc.)](#type-labels-feature-proposal-bug-customer-etc)
- [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc) - [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
- [Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.)](#team-labels-cicd-discussion-edge-platform-etc) - [Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)](#team-labels-cicd-discussion-quality-platform-etc)
- [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release) - [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release)
- [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc) - [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc)
- [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc) - [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc)
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests) - [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
- [Implement design & UI elements](#implement-design-ui-elements) - [Implement design & UI elements](#implement-design--ui-elements)
- [Issue tracker](#issue-tracker) - [Issue tracker](#issue-tracker)
- [Issue triaging](#issue-triaging) - [Issue triaging](#issue-triaging)
- [Feature proposals](#feature-proposals) - [Feature proposals](#feature-proposals)
...@@ -115,7 +115,7 @@ is a great place to start. Issues with a lower weight (1 or 2) are deemed ...@@ -115,7 +115,7 @@ is a great place to start. Issues with a lower weight (1 or 2) are deemed
suitable for beginners. These issues will be of reasonable size and challenge, suitable for beginners. These issues will be of reasonable size and challenge,
for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
please consider we favor please consider we favor
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution! [asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
## Workflow labels ## Workflow labels
...@@ -128,7 +128,7 @@ Most issues will have labels for at least one of the following: ...@@ -128,7 +128,7 @@ Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc. - Type: ~"feature proposal", ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc. - Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~"CI/CD", ~Discussion, ~Edge, ~Platform, etc. - Team: ~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.
- Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release" - Milestone: ~Deliverable, ~Stretch, ~"Next Patch Release"
- Priority: ~P1, ~P2, ~P3, ~P4 - Priority: ~P1, ~P2, ~P3, ~P4
- Severity: ~S1, ~S2, ~S3, ~S4 - Severity: ~S1, ~S2, ~S3, ~S4
...@@ -172,13 +172,13 @@ Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api, ...@@ -172,13 +172,13 @@ Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api,
Subject labels are always all-lowercase. Subject labels are always all-lowercase.
### Team labels (~"CI/CD", ~Discussion, ~Edge, ~Platform, etc.) ### Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)
Team labels specify what team is responsible for this issue. Team labels specify what team is responsible for this issue.
Assigning a team label makes sure issues get the attention of the appropriate Assigning a team label makes sure issues get the attention of the appropriate
people. people.
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge, The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Quality,
~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX". ~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX".
The descriptions on the [labels page][labels-page] explain what falls under the The descriptions on the [labels page][labels-page] explain what falls under the
...@@ -200,12 +200,12 @@ release. There are three levels of Milestone labels: ...@@ -200,12 +200,12 @@ release. There are three levels of Milestone labels:
- ~Stretch: Issues that are a stretch goal for delivering in the current - ~Stretch: Issues that are a stretch goal for delivering in the current
milestone. If these issues are not done in the current release, they will milestone. If these issues are not done in the current release, they will
strongly be considered for the next release. strongly be considered for the next release.
- ~"Next Patch Release": Issues to put in the next patch release. Work on these - ~"Next Patch Release": Issues to put in the next patch release. Work on these
first, and add the "Pick Into X" label to the merge request, along with the first, and add the "Pick Into X" label to the merge request, along with the
appropriate milestone. appropriate milestone.
Each issue scheduled for the current milestone should be labeled ~Deliverable Each issue scheduled for the current milestone should be labeled ~Deliverable
or ~"Stretch". Any open issue for a previous milestone should be labeled or ~"Stretch". Any open issue for a previous milestone should be labeled
~"Next Patch Release", or otherwise rescheduled to a different milestone. ~"Next Patch Release", or otherwise rescheduled to a different milestone.
### Bug Priority labels (~P1, ~P2, ~P3 & etc.) ### Bug Priority labels (~P1, ~P2, ~P3 & etc.)
...@@ -222,16 +222,16 @@ This label documents the planned timeline & urgency which is used to measure aga ...@@ -222,16 +222,16 @@ This label documents the planned timeline & urgency which is used to measure aga
#### Specific Priority guidance #### Specific Priority guidance
| Label | Availability / Performance | | Label | Availability / Performance |
|-------|--------------------------------------------------------------| |-------|--------------------------------------------------------------|
| ~P1 | | | ~P1 | |
| ~P2 | The issue is (almost) guaranteed to occur in the near future | | ~P2 | The issue is (almost) guaranteed to occur in the near future |
| ~P3 | The issue is likely to occur in the near future | | ~P3 | The issue is likely to occur in the near future |
| ~P4 | The issue _may_ occur but it's not likely | | ~P4 | The issue _may_ occur but it's not likely |
### Bug Severity labels (~S1, ~S2, ~S3 & etc.) ### Bug Severity labels (~S1, ~S2, ~S3 & etc.)
Severity labels help us clearly communicate the impact of a ~bug on users. Severity labels help us clearly communicate the impact of a ~bug on users.
| Label | Meaning | Impact of the defect | Example | | Label | Meaning | Impact of the defect | Example |
|-------|-------------------|-------------------------------------------------------|---------| |-------|-------------------|-------------------------------------------------------|---------|
...@@ -242,9 +242,9 @@ Severity labels help us clearly communicate the impact of a ~bug on users. ...@@ -242,9 +242,9 @@ Severity labels help us clearly communicate the impact of a ~bug on users.
#### Specific Severity guidance #### Specific Severity guidance
| Label | Security Impact | | Label | Security Impact |
|-------|-------------------------------------------------------------------| |-------|-------------------------------------------------------------------|
| ~S1 | >50% customers impacted (possible company extinction level event) | | ~S1 | >50% customers impacted (possible company extinction level event) |
| ~S2 | Multiple customers impacted (but not apocalyptic) | | ~S2 | Multiple customers impacted (but not apocalyptic) |
| ~S3 | A single customer impacted | | ~S3 | A single customer impacted |
| ~S4 | No customer impact, or expected impact within 30 days | | ~S4 | No customer impact, or expected impact within 30 days |
...@@ -746,4 +746,3 @@ When your code contains more than 500 changes, any major breaking changes, or an ...@@ -746,4 +746,3 @@ When your code contains more than 500 changes, any major breaking changes, or an
[^1]: Please note that specs other than JavaScript specs are considered backend [^1]: Please note that specs other than JavaScript specs are considered backend
code. code.
\ No newline at end of file
...@@ -295,7 +295,6 @@ gem 'batch-loader', '~> 1.2.1' ...@@ -295,7 +295,6 @@ gem 'batch-loader', '~> 1.2.1'
gem 'peek', '~> 1.0.1' gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2' gem 'peek-gc', '~> 0.0.2'
gem 'peek-mysql2', '~> 1.1.0', group: :mysql gem 'peek-mysql2', '~> 1.1.0', group: :mysql
gem 'peek-performance_bar', '~> 1.3.0'
gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-pg', '~> 1.3.0', group: :postgres
gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-rblineprof', '~> 0.2.0'
gem 'peek-redis', '~> 1.2.0' gem 'peek-redis', '~> 1.2.0'
......
...@@ -632,8 +632,6 @@ GEM ...@@ -632,8 +632,6 @@ GEM
atomic (>= 1.0.0) atomic (>= 1.0.0)
mysql2 mysql2
peek peek
peek-performance_bar (1.3.1)
peek (>= 0.1.0)
peek-pg (1.3.0) peek-pg (1.3.0)
concurrent-ruby concurrent-ruby
concurrent-ruby-ext concurrent-ruby-ext
...@@ -1167,7 +1165,6 @@ DEPENDENCIES ...@@ -1167,7 +1165,6 @@ DEPENDENCIES
peek (~> 1.0.1) peek (~> 1.0.1)
peek-gc (~> 0.0.2) peek-gc (~> 0.0.2)
peek-mysql2 (~> 1.1.0) peek-mysql2 (~> 1.1.0)
peek-performance_bar (~> 1.3.0)
peek-pg (~> 1.3.0) peek-pg (~> 1.3.0)
peek-rblineprof (~> 0.2.0) peek-rblineprof (~> 0.2.0)
peek-redis (~> 1.2.0) peek-redis (~> 1.2.0)
......
...@@ -26,11 +26,18 @@ export default { ...@@ -26,11 +26,18 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
forceModifiedIcon: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
changedIcon() { changedIcon() {
const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : ''; const suffix = this.file.staged && !this.showStagedIcon ? '-solid' : '';
return this.file.tempFile ? `file-addition${suffix}` : `file-modified${suffix}`; return this.file.tempFile && !this.forceModifiedIcon
? `file-addition${suffix}`
: `file-modified${suffix}`;
}, },
stagedIcon() { stagedIcon() {
return `${this.changedIcon}-solid`; return `${this.changedIcon}-solid`;
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import newModal from './modal.vue'; import newModal from './modal.vue';
import upload from './upload.vue'; import upload from './upload.vue';
export default { export default {
components: { components: {
icon, icon,
newModal, newModal,
upload, upload,
},
props: {
branch: {
type: String,
required: true,
}, },
props: { path: {
branch: { type: String,
type: String, required: true,
required: true,
},
path: {
type: String,
required: true,
},
}, },
data() { },
return { data() {
openModal: false, return {
modalType: '', openModal: false,
dropdownOpen: false, modalType: '',
}; dropdownOpen: false,
};
},
watch: {
dropdownOpen() {
this.$nextTick(() => {
this.$refs.dropdownMenu.scrollIntoView();
});
}, },
methods: { },
...mapActions([ methods: {
'createTempEntry', ...mapActions(['createTempEntry']),
]), createNewItem(type) {
createNewItem(type) { this.modalType = type;
this.modalType = type; this.openModal = true;
this.openModal = true; this.dropdownOpen = false;
this.dropdownOpen = false;
},
hideModal() {
this.openModal = false;
},
openDropdown() {
this.dropdownOpen = !this.dropdownOpen;
},
}, },
}; hideModal() {
this.openModal = false;
},
openDropdown() {
this.dropdownOpen = !this.dropdownOpen;
},
},
};
</script> </script>
<template> <template>
...@@ -71,7 +76,10 @@ ...@@ -71,7 +76,10 @@
css-classes="pull-left" css-classes="pull-left"
/> />
</button> </button>
<ul class="dropdown-menu dropdown-menu-right"> <ul
class="dropdown-menu dropdown-menu-right"
ref="dropdownMenu"
>
<li> <li>
<a <a
href="#" href="#"
......
...@@ -40,13 +40,6 @@ export default { ...@@ -40,13 +40,6 @@ export default {
return __('Create file'); return __('Create file');
}, },
formLabelName() {
if (this.type === 'tree') {
return __('Directory name');
}
return __('File name');
},
}, },
mounted() { mounted() {
this.$refs.fieldName.focus(); this.$refs.fieldName.focus();
...@@ -82,8 +75,8 @@ export default { ...@@ -82,8 +75,8 @@ export default {
@submit.prevent="createEntryInStore" @submit.prevent="createEntryInStore"
> >
<fieldset class="form-group append-bottom-0"> <fieldset class="form-group append-bottom-0">
<label class="label-light col-sm-3"> <label class="label-light col-sm-3 ide-new-modal-label">
{{ formLabelName }} {{ __('Name') }}
</label> </label>
<div class="col-sm-9"> <div class="col-sm-9">
<input <input
......
...@@ -97,7 +97,7 @@ export default { ...@@ -97,7 +97,7 @@ export default {
:file="file" :file="file"
/> />
</span> </span>
<span class="pull-right"> <span class="pull-right ide-file-icon-holder">
<mr-file-icon <mr-file-icon
v-if="file.mrChange" v-if="file.mrChange"
/> />
...@@ -106,7 +106,8 @@ export default { ...@@ -106,7 +106,8 @@ export default {
:file="file" :file="file"
:show-tooltip="true" :show-tooltip="true"
:show-staged-icon="true" :show-staged-icon="true"
class="prepend-top-5 pull-right" :force-modified-icon="true"
class="pull-right"
/> />
</span> </span>
<new-dropdown <new-dropdown
......
...@@ -84,6 +84,7 @@ export default { ...@@ -84,6 +84,7 @@ export default {
<changed-file-icon <changed-file-icon
v-else v-else
:file="tab" :file="tab"
:force-modified-icon="true"
/> />
</button> </button>
......
...@@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { ...@@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
} }
}; };
export const toggleRightPanelCollapsed = ( export const toggleRightPanelCollapsed = ({ dispatch, state }, e = undefined) => {
{ dispatch, state },
e = undefined,
) => {
if (e) { if (e) {
$(e.currentTarget) $(e.currentTarget)
.tooltip('hide') .tooltip('hide')
...@@ -77,7 +74,7 @@ export const createTempEntry = ( ...@@ -77,7 +74,7 @@ export const createTempEntry = (
} }
worker.addEventListener('message', ({ data }) => { worker.addEventListener('message', ({ data }) => {
const { file } = data; const { file, parentPath } = data;
worker.terminate(); worker.terminate();
...@@ -93,6 +90,10 @@ export const createTempEntry = ( ...@@ -93,6 +90,10 @@ export const createTempEntry = (
dispatch('setFileActive', file.path); dispatch('setFileActive', file.path);
} }
if (parentPath && !state.entries[parentPath].opened) {
commit(types.TOGGLE_TREE_OPEN, parentPath);
}
resolve(file); resolve(file);
}); });
...@@ -137,6 +138,14 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => { ...@@ -137,6 +138,14 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay); commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay);
}; };
export const updateTempFlagForEntry = ({ commit, dispatch, state }, { file, tempFile }) => {
commit(types.UPDATE_TEMP_FLAG, { path: file.path, tempFile });
if (file.parentPath) {
dispatch('updateTempFlagForEntry', { file: state.entries[file.parentPath], tempFile });
}
};
export const toggleFileFinder = ({ commit }, fileFindVisible) => export const toggleFileFinder = ({ commit }, fileFindVisible) =>
commit(types.TOGGLE_FILE_FINDER, fileFindVisible); commit(types.TOGGLE_FILE_FINDER, fileFindVisible);
......
...@@ -63,7 +63,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive ...@@ -63,7 +63,7 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive
const file = state.entries[path]; const file = state.entries[path];
commit(types.TOGGLE_LOADING, { entry: file }); commit(types.TOGGLE_LOADING, { entry: file });
return service return service
.getFileData(file.url) .getFileData(`${gon.relative_url_root ? gon.relative_url_root : ''}${file.url}`)
.then(res => { .then(res => {
const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
setPageTitle(pageTitle); setPageTitle(pageTitle);
......
...@@ -110,6 +110,17 @@ export const updateFilesAfterCommit = ( ...@@ -110,6 +110,17 @@ export const updateFilesAfterCommit = (
{ root: true }, { root: true },
); );
commit(
rootTypes.TOGGLE_FILE_CHANGED,
{
file,
changed: false,
},
{ root: true },
);
dispatch('updateTempFlagForEntry', { file, tempFile: false }, { root: true });
eventHub.$emit(`editor.update.model.content.${file.key}`, { eventHub.$emit(`editor.update.model.content.${file.key}`, {
content: file.content, content: file.content,
changed: !!changedFile, changed: !!changedFile,
......
...@@ -59,4 +59,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; ...@@ -59,4 +59,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT';
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB'; export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB'; export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
export const UPDATE_TEMP_FLAG = 'UPDATE_TEMP_FLAG';
export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER'; export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER';
...@@ -4,6 +4,7 @@ import mergeRequestMutation from './mutations/merge_request'; ...@@ -4,6 +4,7 @@ import mergeRequestMutation from './mutations/merge_request';
import fileMutations from './mutations/file'; import fileMutations from './mutations/file';
import treeMutations from './mutations/tree'; import treeMutations from './mutations/tree';
import branchMutations from './mutations/branch'; import branchMutations from './mutations/branch';
import { sortTree } from './utils';
export default { export default {
[types.SET_INITIAL_DATA](state, data) { [types.SET_INITIAL_DATA](state, data) {
...@@ -73,7 +74,7 @@ export default { ...@@ -73,7 +74,7 @@ export default {
f => foundEntry.tree.find(e => e.path === f.path) === undefined, f => foundEntry.tree.find(e => e.path === f.path) === undefined,
); );
Object.assign(foundEntry, { Object.assign(foundEntry, {
tree: foundEntry.tree.concat(tree), tree: sortTree(foundEntry.tree.concat(tree)),
}); });
} }
...@@ -86,10 +87,16 @@ export default { ...@@ -86,10 +87,16 @@ export default {
if (!foundEntry) { if (!foundEntry) {
Object.assign(state.trees[`${projectId}/${branchId}`], { Object.assign(state.trees[`${projectId}/${branchId}`], {
tree: state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList), tree: sortTree(state.trees[`${projectId}/${branchId}`].tree.concat(data.treeList)),
}); });
} }
}, },
[types.UPDATE_TEMP_FLAG](state, { path, tempFile }) {
Object.assign(state.entries[path], {
tempFile,
changed: tempFile,
});
},
[types.UPDATE_VIEWER](state, viewer) { [types.UPDATE_VIEWER](state, viewer) {
Object.assign(state, { Object.assign(state, {
viewer, viewer,
......
...@@ -33,6 +33,7 @@ export const dataStructure = () => ({ ...@@ -33,6 +33,7 @@ export const dataStructure = () => ({
raw: '', raw: '',
content: '', content: '',
parentTreeUrl: '', parentTreeUrl: '',
parentPath: '',
renderError: false, renderError: false,
base64: false, base64: false,
editorRow: 1, editorRow: 1,
...@@ -65,6 +66,7 @@ export const decorateData = entity => { ...@@ -65,6 +66,7 @@ export const decorateData = entity => {
previewMode, previewMode,
file_lock, file_lock,
html, html,
parentPath = '',
} = entity; } = entity;
return { return {
...@@ -81,6 +83,7 @@ export const decorateData = entity => { ...@@ -81,6 +83,7 @@ export const decorateData = entity => {
opened, opened,
active, active,
parentTreeUrl, parentTreeUrl,
parentPath,
changed, changed,
renderError, renderError,
content, content,
...@@ -121,8 +124,8 @@ const sortTreesByTypeAndName = (a, b) => { ...@@ -121,8 +124,8 @@ const sortTreesByTypeAndName = (a, b) => {
} else if (a.type === 'blob' && b.type === 'tree') { } else if (a.type === 'blob' && b.type === 'tree') {
return 1; return 1;
} }
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1; if (a.name < b.name) return -1;
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1; if (a.name > b.name) return 1;
return 0; return 0;
}; };
......
...@@ -6,6 +6,7 @@ self.addEventListener('message', e => { ...@@ -6,6 +6,7 @@ self.addEventListener('message', e => {
const treeList = []; const treeList = [];
let file; let file;
let parentPath;
const entries = data.reduce((acc, path) => { const entries = data.reduce((acc, path) => {
const pathSplit = path.split('/'); const pathSplit = path.split('/');
const blobName = pathSplit.pop().trim(); const blobName = pathSplit.pop().trim();
...@@ -17,6 +18,8 @@ self.addEventListener('message', e => { ...@@ -17,6 +18,8 @@ self.addEventListener('message', e => {
const foundEntry = acc[folderPath]; const foundEntry = acc[folderPath];
if (!foundEntry) { if (!foundEntry) {
parentPath = parentFolder ? parentFolder.path : null;
const tree = decorateData({ const tree = decorateData({
projectId, projectId,
branchId, branchId,
...@@ -29,6 +32,7 @@ self.addEventListener('message', e => { ...@@ -29,6 +32,7 @@ self.addEventListener('message', e => {
tempFile, tempFile,
changed: tempFile, changed: tempFile,
opened: tempFile, opened: tempFile,
parentPath,
}); });
Object.assign(acc, { Object.assign(acc, {
...@@ -52,6 +56,8 @@ self.addEventListener('message', e => { ...@@ -52,6 +56,8 @@ self.addEventListener('message', e => {
if (blobName !== '') { if (blobName !== '') {
const fileFolder = acc[pathSplit.join('/')]; const fileFolder = acc[pathSplit.join('/')];
parentPath = fileFolder ? fileFolder.path : null;
file = decorateData({ file = decorateData({
projectId, projectId,
branchId, branchId,
...@@ -66,6 +72,7 @@ self.addEventListener('message', e => { ...@@ -66,6 +72,7 @@ self.addEventListener('message', e => {
content, content,
base64, base64,
previewMode: viewerInformationForPath(blobName), previewMode: viewerInformationForPath(blobName),
parentPath,
}); });
Object.assign(acc, { Object.assign(acc, {
...@@ -86,5 +93,6 @@ self.addEventListener('message', e => { ...@@ -86,5 +93,6 @@ self.addEventListener('message', e => {
entries, entries,
treeList: sortTree(treeList), treeList: sortTree(treeList),
file, file,
parentPath,
}); });
}); });
...@@ -12,7 +12,8 @@ import ModalStore from './boards/stores/modal_store'; ...@@ -12,7 +12,8 @@ import ModalStore from './boards/stores/modal_store';
export default class MilestoneSelect { export default class MilestoneSelect {
constructor(currentProject, els, options = {}) { constructor(currentProject, els, options = {}) {
if (currentProject !== null) { if (currentProject !== null) {
this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject; this.currentProject =
typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
} }
this.init(els, options); this.init(els, options);
...@@ -26,7 +27,10 @@ export default class MilestoneSelect { ...@@ -26,7 +27,10 @@ export default class MilestoneSelect {
} }
$els.each((i, dropdown) => { $els.each((i, dropdown) => {
let milestoneLinkNoneTemplate, milestoneLinkTemplate, selectedMilestone, selectedMilestoneDefault; let milestoneLinkNoneTemplate,
milestoneLinkTemplate,
selectedMilestone,
selectedMilestoneDefault;
const $dropdown = $(dropdown); const $dropdown = $(dropdown);
const projectId = $dropdown.data('projectId'); const projectId = $dropdown.data('projectId');
const milestonesUrl = $dropdown.data('milestones'); const milestonesUrl = $dropdown.data('milestones');
...@@ -46,45 +50,47 @@ export default class MilestoneSelect { ...@@ -46,45 +50,47 @@ export default class MilestoneSelect {
const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
const $value = $block.find('.value'); const $value = $block.find('.value');
const $loading = $block.find('.block-loading').fadeOut(); const $loading = $block.find('.block-loading').fadeOut();
selectedMilestoneDefault = (showAny ? '' : null); selectedMilestoneDefault = showAny ? '' : null;
selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); selectedMilestoneDefault = showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault;
selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
if (issueUpdateURL) { if (issueUpdateURL) {
milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); milestoneLinkTemplate = _.template(
'<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>',
);
milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; milestoneLinkNoneTemplate = '<span class="no-value">None</span>';
} }
return $dropdown.glDropdown({ return $dropdown.glDropdown({
showMenuAbove: showMenuAbove, showMenuAbove: showMenuAbove,
data: (term, callback) => axios.get(milestonesUrl) data: (term, callback) =>
.then(({ data }) => { axios.get(milestonesUrl).then(({ data }) => {
const extraOptions = []; const extraOptions = [];
if (showAny) { if (showAny) {
extraOptions.push({ extraOptions.push({
id: 0, id: null,
name: '', name: null,
title: 'Any Milestone' title: 'Any Milestone',
}); });
} }
if (showNo) { if (showNo) {
extraOptions.push({ extraOptions.push({
id: -1, id: -1,
name: 'No Milestone', name: 'No Milestone',
title: 'No Milestone' title: 'No Milestone',
}); });
} }
if (showUpcoming) { if (showUpcoming) {
extraOptions.push({ extraOptions.push({
id: -2, id: -2,
name: '#upcoming', name: '#upcoming',
title: 'Upcoming' title: 'Upcoming',
}); });
} }
if (showStarted) { if (showStarted) {
extraOptions.push({ extraOptions.push({
id: -3, id: -3,
name: '#started', name: '#started',
title: 'Started' title: 'Started',
}); });
} }
if (extraOptions.length) { if (extraOptions.length) {
...@@ -106,7 +112,7 @@ export default class MilestoneSelect { ...@@ -106,7 +112,7 @@ export default class MilestoneSelect {
`, `,
filterable: true, filterable: true,
search: { search: {
fields: ['title'] fields: ['title'],
}, },
selectable: true, selectable: true,
toggleLabel: (selected, el, e) => { toggleLabel: (selected, el, e) => {
...@@ -119,7 +125,7 @@ export default class MilestoneSelect { ...@@ -119,7 +125,7 @@ export default class MilestoneSelect {
defaultLabel: defaultLabel, defaultLabel: defaultLabel,
fieldName: $dropdown.data('fieldName'), fieldName: $dropdown.data('fieldName'),
text: milestone => _.escape(milestone.title), text: milestone => _.escape(milestone.title),
id: (milestone) => { id: milestone => {
if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) { if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
return milestone.name; return milestone.name;
} else { } else {
...@@ -131,7 +137,7 @@ export default class MilestoneSelect { ...@@ -131,7 +137,7 @@ export default class MilestoneSelect {
// display:block overrides the hide-collapse rule // display:block overrides the hide-collapse rule
return $value.css('display', ''); return $value.css('display', '');
}, },
opened: (e) => { opened: e => {
const $el = $(e.currentTarget); const $el = $(e.currentTarget);
if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) { if ($dropdown.hasClass('js-issue-board-sidebar') || options.handleClick) {
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
...@@ -140,7 +146,7 @@ export default class MilestoneSelect { ...@@ -140,7 +146,7 @@ export default class MilestoneSelect {
$(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active'); $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active');
}, },
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: (clickEvent) => { clicked: clickEvent => {
const { $el, e } = clickEvent; const { $el, e } = clickEvent;
let selected = clickEvent.selectedObj; let selected = clickEvent.selectedObj;
...@@ -155,11 +161,14 @@ export default class MilestoneSelect { ...@@ -155,11 +161,14 @@ export default class MilestoneSelect {
const page = $('body').attr('data-page'); const page = $('body').attr('data-page');
const isIssueIndex = page === 'projects:issues:index'; const isIssueIndex = page === 'projects:issues:index';
const isMRIndex = (page === page && page === 'projects:merge_requests:index'); const isMRIndex = page === page && page === 'projects:merge_requests:index';
const isSelecting = (selected.name !== selectedMilestone); const isSelecting = selected.name !== selectedMilestone;
selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { if (
$dropdown.hasClass('js-filter-bulk-update') ||
$dropdown.hasClass('js-issuable-form-dropdown')
) {
e.preventDefault(); e.preventDefault();
return; return;
} }
...@@ -177,10 +186,13 @@ export default class MilestoneSelect { ...@@ -177,10 +186,13 @@ export default class MilestoneSelect {
return $dropdown.closest('form').submit(); return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) { } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (selected.id !== -1 && isSelecting) { if (selected.id !== -1 && isSelecting) {
gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ gl.issueBoards.boardStoreIssueSet(
id: selected.id, 'milestone',
title: selected.name new ListMilestone({
})); id: selected.id,
title: selected.name,
}),
);
} else { } else {
gl.issueBoards.boardStoreIssueDelete('milestone'); gl.issueBoards.boardStoreIssueDelete('milestone');
} }
...@@ -188,7 +200,8 @@ export default class MilestoneSelect { ...@@ -188,7 +200,8 @@ export default class MilestoneSelect {
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update')) gl.issueBoards.BoardsStore.detail.issue
.update($dropdown.attr('data-issue-update'))
.then(() => { .then(() => {
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut(); $loading.fadeOut();
...@@ -203,7 +216,8 @@ export default class MilestoneSelect { ...@@ -203,7 +216,8 @@ export default class MilestoneSelect {
data[abilityName].milestone_id = selected != null ? selected : null; data[abilityName].milestone_id = selected != null ? selected : null;
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
return axios.put(issueUpdateURL, data) return axios
.put(issueUpdateURL, data)
.then(({ data }) => { .then(({ data }) => {
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut(); $loading.fadeOut();
...@@ -215,7 +229,10 @@ export default class MilestoneSelect { ...@@ -215,7 +229,10 @@ export default class MilestoneSelect {
data.milestone.name = data.milestone.title; data.milestone.name = data.milestone.title;
$value.html(milestoneLinkTemplate(data.milestone)); $value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue return $sidebarCollapsedValue
.attr('data-original-title', `${data.milestone.name}<br />${data.milestone.remaining}`) .attr(
'data-original-title',
`${data.milestone.name}<br />${data.milestone.remaining}`,
)
.find('span') .find('span')
.text(data.milestone.title); .text(data.milestone.title);
} else { } else {
...@@ -230,7 +247,7 @@ export default class MilestoneSelect { ...@@ -230,7 +247,7 @@ export default class MilestoneSelect {
$loading.fadeOut(); $loading.fadeOut();
}); });
} }
} },
}); });
}); });
} }
......
...@@ -52,16 +52,15 @@ ...@@ -52,16 +52,15 @@
text() { text() {
const keepContributionsText = s__(`AdminArea| const keepContributionsText = s__(`AdminArea|
You are about to permanently delete the user %{username}. You are about to permanently delete the user %{username}.
This will delete all of the issues, merge requests, and groups linked to them. Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user".
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
const deleteContributionsText = s__(`AdminArea| const deleteContributionsText = s__(`AdminArea|
You are about to permanently delete the user %{username}. You are about to permanently delete the user %{username}.
Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". This will delete all of the issues, merge requests, and groups linked to them.
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText, return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText,
{ {
username: `<strong>${_.escape(this.username)}</strong>`, username: `<strong>${_.escape(this.username)}</strong>`,
......
...@@ -188,11 +188,11 @@ export default class ActivityCalendar { ...@@ -188,11 +188,11 @@ export default class ActivityCalendar {
}, },
{ {
text: 'W', text: 'W',
y: 29 + this.dayYPos(2), y: 29 + this.dayYPos(3),
}, },
{ {
text: 'F', text: 'F',
y: 29 + this.dayYPos(3), y: 29 + this.dayYPos(5),
}, },
]; ];
this.svg this.svg
......
...@@ -5,7 +5,6 @@ import PerformanceBarService from '../services/performance_bar_service'; ...@@ -5,7 +5,6 @@ import PerformanceBarService from '../services/performance_bar_service';
import detailedMetric from './detailed_metric.vue'; import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue'; import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue'; import simpleMetric from './simple_metric.vue';
import upstreamPerformanceBar from './upstream_performance_bar.vue';
import Flash from '../../flash'; import Flash from '../../flash';
...@@ -14,7 +13,6 @@ export default { ...@@ -14,7 +13,6 @@ export default {
detailedMetric, detailedMetric,
requestSelector, requestSelector,
simpleMetric, simpleMetric,
upstreamPerformanceBar,
}, },
props: { props: {
store: { store: {
...@@ -128,9 +126,6 @@ export default { ...@@ -128,9 +126,6 @@ export default {
{{ currentRequest.details.host.hostname }} {{ currentRequest.details.host.hostname }}
</span> </span>
</div> </div>
<upstream-performance-bar
v-if="initialRequest && currentRequest.details"
/>
<detailed-metric <detailed-metric
v-for="metric in $options.detailedMetrics" v-for="metric in $options.detailedMetrics"
:key="metric.metric" :key="metric.metric"
......
<script>
export default {
mounted() {
const upstreamPerformanceBar = document
.getElementById('peek-view-performance-bar')
.cloneNode(true);
upstreamPerformanceBar.classList.remove('hidden');
this.$refs.wrapper.appendChild(upstreamPerformanceBar);
},
};
</script>
<template>
<div
id="peek-view-performance-bar-vue"
class="view"
ref="wrapper"
></div>
</template>
import 'vendor/peek.performance_bar';
import Vue from 'vue'; import Vue from 'vue';
import performanceBarApp from './components/performance_bar_app.vue'; import performanceBarApp from './components/performance_bar_app.vue';
import PerformanceBarStore from './stores/performance_bar_store'; import PerformanceBarStore from './stores/performance_bar_store';
......
<script> <script>
import getIconForFile from './file_icon/file_icon_map'; import getIconForFile from './file_icon/file_icon_map';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
/* This is a re-usable vue component for rendering a svg sprite /* This is a re-usable vue component for rendering a svg sprite
icon icon
Sample configuration: Sample configuration:
...@@ -15,60 +15,60 @@ ...@@ -15,60 +15,60 @@
/> />
*/ */
export default { export default {
components: { components: {
loadingIcon, loadingIcon,
icon, icon,
},
props: {
fileName: {
type: String,
required: true,
}, },
props: {
fileName: {
type: String,
required: true,
},
folder: { folder: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
opened: { opened: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
loading: { loading: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
size: { size: {
type: Number, type: Number,
required: false, required: false,
default: 16, default: 16,
}, },
cssClasses: { cssClasses: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
},
computed: {
spriteHref() {
const iconName = getIconForFile(this.fileName) || 'file';
return `${gon.sprite_file_icons}#${iconName}`;
},
folderIconName() {
return this.opened ? 'folder-open' : 'folder';
}, },
computed: { iconSizeClass() {
spriteHref() { return this.size ? `s${this.size}` : '';
const iconName = getIconForFile(this.fileName) || 'file';
return `${gon.sprite_file_icons}#${iconName}`;
},
folderIconName() {
return this.opened ? 'folder-open' : 'folder';
},
iconSizeClass() {
return this.size ? `s${this.size}` : '';
},
}, },
}; },
};
</script> </script>
<template> <template>
<span> <span>
...@@ -82,6 +82,7 @@ ...@@ -82,6 +82,7 @@
v-if="!loading && folder" v-if="!loading && folder"
:name="folderIconName" :name="folderIconName"
:size="size" :size="size"
css-classes="folder-icon"
/> />
<loading-icon <loading-icon
v-if="loading" v-if="loading"
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
.fork-svg { .fork-svg {
margin-right: 4px; margin-right: 4px;
vertical-align: bottom;
} }
} }
......
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
} }
.branch-info .commit-icon { .branch-info .commit-icon {
margin-right: 3px; margin-right: 8px;
svg { svg {
top: 3px; top: 3px;
......
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: inherit; max-width: inherit;
line-height: 22px;
svg { svg {
vertical-align: middle; vertical-align: middle;
...@@ -67,6 +68,11 @@ ...@@ -67,6 +68,11 @@
} }
} }
.ide-file-icon-holder {
display: flex;
align-items: center;
}
.ide-file-changed-icon { .ide-file-changed-icon {
margin-left: auto; margin-left: auto;
...@@ -77,7 +83,6 @@ ...@@ -77,7 +83,6 @@
.ide-new-btn { .ide-new-btn {
display: none; display: none;
margin-bottom: -4px;
margin-right: -8px; margin-right: -8px;
} }
...@@ -90,10 +95,8 @@ ...@@ -90,10 +95,8 @@
} }
} }
&.folder { .folder-icon {
svg { fill: $gl-text-color-secondary;
fill: $gl-text-color-secondary;
}
} }
} }
...@@ -111,6 +114,7 @@ ...@@ -111,6 +114,7 @@
.file-col-commit-message { .file-col-commit-message {
display: flex; display: flex;
overflow: visible; overflow: visible;
align-items: center;
padding: 6px 12px; padding: 6px 12px;
} }
...@@ -438,7 +442,7 @@ ...@@ -438,7 +442,7 @@
.projects-sidebar { .projects-sidebar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; flex: 1;
.context-header { .context-header {
width: auto; width: auto;
...@@ -967,3 +971,7 @@ ...@@ -967,3 +971,7 @@
background: transparent; background: transparent;
resize: none; resize: none;
} }
.ide-new-modal-label {
line-height: 34px;
}
@import 'framework/variables'; @import 'framework/variables';
@import 'peek/views/performance_bar';
@import 'peek/views/rblineprof'; @import 'peek/views/rblineprof';
#js-peek { #js-peek {
......
...@@ -17,7 +17,7 @@ module BlobHelper ...@@ -17,7 +17,7 @@ module BlobHelper
end end
def ide_edit_path(project = @project, ref = @ref, path = @path, options = {}) def ide_edit_path(project = @project, ref = @ref, path = @path, options = {})
"#{ide_path}/project#{edit_blob_path(project, ref, path, options)}" "#{ide_path}/project#{url_for([project, "edit", "blob", id: [ref, path], script_name: "/"])}"
end end
def edit_blob_button(project = @project, ref = @ref, path = @path, options = {}) def edit_blob_button(project = @project, ref = @ref, path = @path, options = {})
......
...@@ -63,7 +63,7 @@ module CommitsHelper ...@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link # Returns a link formatted as a commit branch link
def commit_branch_link(url, text) def commit_branch_link(url, text)
link_to(url, class: 'label label-gray ref-name branch-link') do link_to(url, class: 'label label-gray ref-name branch-link') do
sprite_icon('fork', size: 16, css_class: 'fork-svg') + "#{text}" sprite_icon('fork', size: 12, css_class: 'fork-svg') + "#{text}"
end end
end end
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
delete_user_url: admin_user_path(user), delete_user_url: admin_user_path(user),
block_user_url: block_admin_user_path(user), block_user_url: block_admin_user_path(user),
username: user.name, username: user.name,
delete_contributions: 'false' }, type: 'button' } delete_contributions: false }, type: 'button' }
= s_('AdminUsers|Delete user') = s_('AdminUsers|Delete user')
%li %li
...@@ -58,5 +58,5 @@ ...@@ -58,5 +58,5 @@
delete_user_url: admin_user_path(user, hard_delete: true), delete_user_url: admin_user_path(user, hard_delete: true),
block_user_url: block_admin_user_path(user), block_user_url: block_admin_user_path(user),
username: user.name, username: user.name,
delete_contributions: 'true' }, type: 'button' } delete_contributions: true }, type: 'button' }
= s_('AdminUsers|Delete user and contributions') = s_('AdminUsers|Delete user and contributions')
...@@ -194,7 +194,7 @@ ...@@ -194,7 +194,7 @@
delete_user_url: admin_user_path(@user), delete_user_url: admin_user_path(@user),
block_user_url: block_admin_user_path(@user), block_user_url: block_admin_user_path(@user),
username: @user.name, username: @user.name,
delete_contributions: 'false' }, type: 'button' } delete_contributions: false }, type: 'button' }
= s_('AdminUsers|Delete user') = s_('AdminUsers|Delete user')
- else - else
- if @user.solo_owned_groups.present? - if @user.solo_owned_groups.present?
...@@ -226,7 +226,7 @@ ...@@ -226,7 +226,7 @@
delete_user_url: admin_user_path(@user, hard_delete: true), delete_user_url: admin_user_path(@user, hard_delete: true),
block_user_url: block_admin_user_path(@user), block_user_url: block_admin_user_path(@user),
username: @user.name, username: @user.name,
delete_contributions: 'true' }, type: 'button' } delete_contributions: true }, type: 'button' }
= s_('AdminUsers|Delete user and contributions') = s_('AdminUsers|Delete user and contributions')
- else - else
%p %p
......
...@@ -5,8 +5,3 @@ ...@@ -5,8 +5,3 @@
peek_url: peek_routes.results_url, peek_url: peek_routes.results_url,
profile_url: url_for(params.merge(lineprofiler: 'true')) }, profile_url: url_for(params.merge(lineprofiler: 'true')) },
class: Peek.env } class: Peek.env }
#peek-view-performance-bar.hidden
= render_server_response_time
%span#serverstats
%ul.performance-bar
---
title: Correct text and functionality for delete user / delete user and contributions
modal.
merge_request: 18463
author: Marc Schwede
type: fixed
---
title: Improve performance of a service responsible for creating a pipeline
merge_request: 18582
author:
type: performance
---
title: Fix size and position for fork icon
merge_request: 18449
author: George Tsiolis
type: changed
---
title: Reset milestone filter when clicking "Any Milestone" in dashboard
merge_request: 18531
author:
type: fixed
Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) } Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) }
Peek.into Peek::Views::Host Peek.into Peek::Views::Host
Peek.into Peek::Views::PerformanceBar
if Gitlab::Database.mysql? if Gitlab::Database.mysql?
require 'peek-mysql2' require 'peek-mysql2'
......
...@@ -91,6 +91,29 @@ have been resolved to our satisfaction by the relicensing of the reference ...@@ -91,6 +91,29 @@ have been resolved to our satisfaction by the relicensing of the reference
implementations under MIT, and the use of the OWF license for the GraphQL implementations under MIT, and the use of the OWF license for the GraphQL
specification. specification.
## Compatibility Guidelines
The HTTP API is versioned using a single number, the current one being 4. This
number symbolises the same as the major version number as described by
[SemVer](https://semver.org/). This mean that backward incompatible changes
will require this version number to change. However, the minor version is
not explicit. This allows for a stable API endpoint, but also means new
features can be added to the API in the same version number.
New features and bug fixes are released in tandem with a new GitLab, and apart
from incidental patch and security releases, are released on the 22nd each
month. Backward incompatible changes (e.g. endpoints removal, parameters
removal etc.), as well as removal of entire API versions are done in tandem
with a major point release of GitLab itself. All deprecations and changes
between two versions should be listed in the documentation. For the changes
between v3 and v4; please read the [v3 to v4 documentation](v3_to_v4.md)
#### Current status
Currently two API versions are available, v3 and v4. v3 is deprecated and
will soon be removed. Deletion is scheduled for
[GitLab 11.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/36819).
## Basic usage ## Basic usage
API requests should be prefixed with `api` and the API version. The API version API requests should be prefixed with `api` and the API version. The API version
......
...@@ -40,7 +40,6 @@ comments: false ...@@ -40,7 +40,6 @@ comments: false
- [Sidekiq debugging](sidekiq_debugging.md) - [Sidekiq debugging](sidekiq_debugging.md)
- [Gotchas](gotchas.md) to avoid - [Gotchas](gotchas.md) to avoid
- [Avoid modules with instance variables](module_with_instance_variables.md) if possible - [Avoid modules with instance variables](module_with_instance_variables.md) if possible
- [Issue and merge requests state models](object_state_models.md)
- [How to dump production data to staging](db_dump.md) - [How to dump production data to staging](db_dump.md)
- [Working with the GitHub importer](github_importer.md) - [Working with the GitHub importer](github_importer.md)
- [Elasticsearch integration docs](elasticsearch.md) - [Elasticsearch integration docs](elasticsearch.md)
......
# Object state models
## Diagrams
[GitLab object state models](https://drive.google.com/drive/u/3/folders/0B5tDlHAM4iZINmpvYlJXcDVqMGc)
---
## Legend
![legend](img/state-model-legend.png)
---
## Issue
[`app/models/issue.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/models/issue.rb)
![issue](img/state-model-issue.png)
---
## Merge request
[`app/models/merge_request.rb`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/models/merge_request.rb)
![merge request](img/state-model-merge-request.png)
\ No newline at end of file
...@@ -14,14 +14,10 @@ module Gitlab ...@@ -14,14 +14,10 @@ module Gitlab
@command.seeds_block&.call(pipeline) @command.seeds_block&.call(pipeline)
## ##
# Populate pipeline with all stages and builds from pipeline seeds. # Populate pipeline with all stages, and stages with builds.
# #
pipeline.stage_seeds.each do |stage| pipeline.stage_seeds.each do |stage|
pipeline.stages << stage.to_resource pipeline.stages << stage.to_resource
stage.seeds.each do |build|
pipeline.builds << build.to_resource
end
end end
if pipeline.stages.none? if pipeline.stages.none?
......
...@@ -10,13 +10,16 @@ feature 'Dashboard > milestone filter', :js do ...@@ -10,13 +10,16 @@ feature 'Dashboard > milestone filter', :js do
let!(:issue) { create :issue, author: user, project: project, milestone: milestone } let!(:issue) { create :issue, author: user, project: project, milestone: milestone }
let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 } let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 }
dropdown_toggle_button = '.js-milestone-select'
before do before do
sign_in(user) sign_in(user)
visit issues_dashboard_path(author_id: user.id)
end end
context 'default state' do context 'default state' do
it 'shows issues with Any Milestone' do it 'shows issues with Any Milestone' do
visit issues_dashboard_path(author_id: user.id)
page.all('.issue-info').each do |issue_info| page.all('.issue-info').each do |issue_info|
expect(issue_info.text).to match(/v\d.0/) expect(issue_info.text).to match(/v\d.0/)
end end
...@@ -24,31 +27,51 @@ feature 'Dashboard > milestone filter', :js do ...@@ -24,31 +27,51 @@ feature 'Dashboard > milestone filter', :js do
end end
context 'filtering by milestone' do context 'filtering by milestone' do
milestone_select_selector = '.js-milestone-select'
before do before do
filter_item_select('v1.0', milestone_select_selector) visit issues_dashboard_path(author_id: user.id)
find(milestone_select_selector).click filter_item_select('v1.0', dropdown_toggle_button)
find(dropdown_toggle_button).click
wait_for_requests wait_for_requests
end end
it 'shows issues with Milestone v1.0' do it 'shows issues with Milestone v1.0' do
expect(find('.issues-list')).to have_selector('.issue', count: 1) expect(find('.issues-list')).to have_selector('.issue', count: 1)
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) expect(find('.milestone-filter .dropdown-content')).to have_selector('a.is-active', count: 1)
end end
it 'should not change active Milestone unless clicked' do it 'should not change active Milestone unless clicked' do
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) page.within '.milestone-filter' do
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
find('.dropdown-menu-close').click
# open & close dropdown expect(page).not_to have_selector('.dropdown.open')
find('.dropdown-menu-close').click
find(dropdown_toggle_button).click
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1)
expect(find('.dropdown-content a.is-active')).to have_content('v1.0')
end
end
end
context 'with milestone filter in URL' do
before do
visit issues_dashboard_path(author_id: user.id, milestone_title: milestone.title)
find(dropdown_toggle_button).click
wait_for_requests
end
it 'has milestone selected' do
expect(find('.milestone-filter .dropdown-content')).to have_css('.is-active', text: milestone.title)
end
expect(find('.milestone-filter')).not_to have_selector('.dropdown.open') it 'removes milestone filter from URL after clicking "Any Milestone"' do
expect(current_url).to include("milestone_title=#{milestone.title}")
find(milestone_select_selector).click find('.milestone-filter .dropdown-content li', text: 'Any Milestone').click
expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) expect(current_url).not_to include('milestone_title')
expect(find('.dropdown-content a.is-active')).to have_content('v1.0')
end end
end end
end end
...@@ -242,4 +242,29 @@ describe BlobHelper do ...@@ -242,4 +242,29 @@ describe BlobHelper do
end end
end end
end end
describe '#ide_edit_path' do
let(:project) { create(:project) }
around do |example|
old_script_name = Rails.application.routes.default_url_options[:script_name]
begin
example.run
ensure
Rails.application.routes.default_url_options[:script_name] = old_script_name
end
end
it 'returns full IDE path' do
Rails.application.routes.default_url_options[:script_name] = nil
expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master/")
end
it 'returns IDE path without relative_url_root' do
Rails.application.routes.default_url_options[:script_name] = "/gitlab"
expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master/")
end
end
end end
...@@ -32,12 +32,8 @@ describe('new dropdown component', () => { ...@@ -32,12 +32,8 @@ describe('new dropdown component', () => {
it('renders new file, upload and new directory links', () => { it('renders new file, upload and new directory links', () => {
expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file'); expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file');
expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe( expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file');
'Upload file', expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory');
);
expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe(
'New directory',
);
}); });
describe('createNewItem', () => { describe('createNewItem', () => {
...@@ -81,4 +77,18 @@ describe('new dropdown component', () => { ...@@ -81,4 +77,18 @@ describe('new dropdown component', () => {
.catch(done.fail); .catch(done.fail);
}); });
}); });
describe('dropdownOpen', () => {
it('scrolls dropdown into view', done => {
spyOn(vm.$refs.dropdownMenu, 'scrollIntoView');
vm.dropdownOpen = true;
setTimeout(() => {
expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalled();
done();
});
});
});
}); });
...@@ -25,25 +25,17 @@ describe('new file modal component', () => { ...@@ -25,25 +25,17 @@ describe('new file modal component', () => {
it(`sets modal title as ${type}`, () => { it(`sets modal title as ${type}`, () => {
const title = type === 'tree' ? 'directory' : 'file'; const title = type === 'tree' ? 'directory' : 'file';
expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe( expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`);
`Create new ${title}`,
);
}); });
it(`sets button label as ${type}`, () => { it(`sets button label as ${type}`, () => {
const title = type === 'tree' ? 'directory' : 'file'; const title = type === 'tree' ? 'directory' : 'file';
expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe( expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`);
`Create ${title}`,
);
}); });
it(`sets form label as ${type}`, () => { it(`sets form label as ${type}`, () => {
const title = type === 'tree' ? 'Directory' : 'File'; expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe('Name');
expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(
`${title} name`,
);
}); });
describe('createEntryInStore', () => { describe('createEntryInStore', () => {
......
import actions, { stageAllChanges, unstageAllChanges, toggleFileFinder } from '~/ide/stores/actions'; import actions, {
stageAllChanges,
unstageAllChanges,
toggleFileFinder,
updateTempFlagForEntry,
} from '~/ide/stores/actions';
import store from '~/ide/stores'; import store from '~/ide/stores';
import * as types from '~/ide/stores/mutation_types'; import * as types from '~/ide/stores/mutation_types';
import router from '~/ide/ide_router'; import router from '~/ide/ide_router';
...@@ -340,6 +345,49 @@ describe('Multi-file store actions', () => { ...@@ -340,6 +345,49 @@ describe('Multi-file store actions', () => {
}); });
}); });
describe('updateTempFlagForEntry', () => {
it('commits UPDATE_TEMP_FLAG', done => {
const f = {
...file(),
path: 'test',
tempFile: true,
};
store.state.entries[f.path] = f;
testAction(
updateTempFlagForEntry,
{ file: f, tempFile: false },
store.state,
[{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }],
[],
done,
);
});
it('commits UPDATE_TEMP_FLAG and dispatches for parent', done => {
const parent = {
...file(),
path: 'testing',
};
const f = {
...file(),
path: 'test',
parentPath: 'testing',
};
store.state.entries[parent.path] = parent;
store.state.entries[f.path] = f;
testAction(
updateTempFlagForEntry,
{ file: f, tempFile: false },
store.state,
[{ type: 'UPDATE_TEMP_FLAG', payload: { path: f.path, tempFile: false } }],
[{ type: 'updateTempFlagForEntry', payload: { file: parent, tempFile: false } }],
done,
);
});
});
describe('toggleFileFinder', () => { describe('toggleFileFinder', () => {
it('commits TOGGLE_FILE_FINDER', done => { it('commits TOGGLE_FILE_FINDER', done => {
testAction( testAction(
......
...@@ -87,6 +87,28 @@ describe('Multi-file store mutations', () => { ...@@ -87,6 +87,28 @@ describe('Multi-file store mutations', () => {
}); });
}); });
describe('UPDATE_TEMP_FLAG', () => {
beforeEach(() => {
localState.entries.test = {
...file(),
tempFile: true,
changed: true,
};
});
it('updates tempFile flag', () => {
mutations.UPDATE_TEMP_FLAG(localState, { path: 'test', tempFile: false });
expect(localState.entries.test.tempFile).toBe(false);
});
it('updates changed flag', () => {
mutations.UPDATE_TEMP_FLAG(localState, { path: 'test', tempFile: false });
expect(localState.entries.test.changed).toBe(false);
});
});
describe('TOGGLE_FILE_FINDER', () => { describe('TOGGLE_FILE_FINDER', () => {
it('updates fileFindVisible', () => { it('updates fileFindVisible', () => {
mutations.TOGGLE_FILE_FINDER(localState, true); mutations.TOGGLE_FILE_FINDER(localState, true);
......
...@@ -35,11 +35,6 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do ...@@ -35,11 +35,6 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'populates pipeline with stages' do it 'populates pipeline with stages' do
expect(pipeline.stages).to be_one expect(pipeline.stages).to be_one
expect(pipeline.stages.first).not_to be_persisted expect(pipeline.stages.first).not_to be_persisted
end
it 'populates pipeline with builds' do
expect(pipeline.builds).to be_one
expect(pipeline.builds.first).not_to be_persisted
expect(pipeline.stages.first.builds).to be_one expect(pipeline.stages.first.builds).to be_one
expect(pipeline.stages.first.builds.first).not_to be_persisted expect(pipeline.stages.first.builds.first).not_to be_persisted
end end
...@@ -151,8 +146,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do ...@@ -151,8 +146,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
step.perform! step.perform!
expect(pipeline.stages.size).to eq 1 expect(pipeline.stages.size).to eq 1
expect(pipeline.builds.size).to eq 1 expect(pipeline.stages.first.builds.size).to eq 1
expect(pipeline.builds.first.name).to eq 'rspec' expect(pipeline.stages.first.builds.first.name).to eq 'rspec'
end end
end end
end end
var PerformanceBar, ajaxStart, renderPerformanceBar, updateStatus;
PerformanceBar = (function() {
PerformanceBar.prototype.appInfo = null;
PerformanceBar.prototype.width = null;
PerformanceBar.formatTime = function(value) {
if (value >= 1000) {
return ((value / 1000).toFixed(3)) + "s";
} else {
return (value.toFixed(0)) + "ms";
}
};
function PerformanceBar(options) {
var k, v;
if (options == null) {
options = {};
}
this.el = $('#peek-view-performance-bar .performance-bar');
for (k in options) {
v = options[k];
this[k] = v;
}
if (this.width == null) {
this.width = this.el.width();
}
if (this.timing == null) {
this.timing = window.performance.timing;
}
}
PerformanceBar.prototype.render = function(serverTime) {
var networkTime, perfNetworkTime;
if (serverTime == null) {
serverTime = 0;
}
this.el.empty();
this.addBar('frontend', '#90d35b', 'domLoading', 'domInteractive');
perfNetworkTime = this.timing.responseEnd - this.timing.requestStart;
if (serverTime && serverTime <= perfNetworkTime) {
networkTime = perfNetworkTime - serverTime;
this.addBar('latency / receiving', '#f1faff', this.timing.requestStart + serverTime, this.timing.requestStart + serverTime + networkTime);
this.addBar('app', '#90afcf', this.timing.requestStart, this.timing.requestStart + serverTime, this.appInfo);
} else {
this.addBar('backend', '#c1d7ee', 'requestStart', 'responseEnd');
}
this.addBar('tcp / ssl', '#45688e', 'connectStart', 'connectEnd');
this.addBar('redirect', '#0c365e', 'redirectStart', 'redirectEnd');
this.addBar('dns', '#082541', 'domainLookupStart', 'domainLookupEnd');
return this.el;
};
PerformanceBar.prototype.isLoaded = function() {
return this.timing.domInteractive;
};
PerformanceBar.prototype.start = function() {
return this.timing.navigationStart;
};
PerformanceBar.prototype.end = function() {
return this.timing.domInteractive;
};
PerformanceBar.prototype.total = function() {
return this.end() - this.start();
};
PerformanceBar.prototype.addBar = function(name, color, start, end, info) {
var bar, left, offset, time, title, width;
if (typeof start === 'string') {
start = this.timing[start];
}
if (typeof end === 'string') {
end = this.timing[end];
}
if (!((start != null) && (end != null))) {
return;
}
time = end - start;
offset = start - this.start();
left = this.mapH(offset);
width = this.mapH(time);
title = name + ": " + (PerformanceBar.formatTime(time));
bar = $('<li></li>', {
'data-title': title,
'data-toggle': 'tooltip',
'data-container': 'body'
});
bar.css({
width: width + "px",
left: left + "px",
background: color
});
return this.el.append(bar);
};
PerformanceBar.prototype.mapH = function(offset) {
return offset * (this.width / this.total());
};
return PerformanceBar;
})();
renderPerformanceBar = function() {
var bar, resp, span, time;
resp = $('#peek-server_response_time');
time = Math.round(resp.data('time') * 1000);
bar = new PerformanceBar;
bar.render(time);
span = $('<span>', {
'data-toggle': 'tooltip',
'data-title': 'Total navigation time for this page.',
'data-container': 'body'
}).text(PerformanceBar.formatTime(bar.total()));
return updateStatus(span);
};
updateStatus = function(html) {
return $('#serverstats').html(html);
};
ajaxStart = null;
$(document).on('pjax:start page:fetch turbolinks:request-start', function(event) {
return ajaxStart = event.timeStamp;
});
$(document).on('pjax:end page:load turbolinks:load', function(event, xhr) {
var ajaxEnd, serverTime, total;
if (ajaxStart == null) {
return;
}
ajaxEnd = event.timeStamp;
total = ajaxEnd - ajaxStart;
serverTime = xhr ? parseInt(xhr.getResponseHeader('X-Runtime')) : 0;
return setTimeout(function() {
var bar, now, span, tech;
now = new Date().getTime();
bar = new PerformanceBar({
timing: {
requestStart: ajaxStart,
responseEnd: ajaxEnd,
domLoading: ajaxEnd,
domInteractive: now
},
isLoaded: function() {
return true;
},
start: function() {
return ajaxStart;
},
end: function() {
return now;
}
});
bar.render(serverTime);
if ($.fn.pjax != null) {
tech = 'PJAX';
} else {
tech = 'Turbolinks';
}
span = $('<span>', {
'data-toggle': 'tooltip',
'data-title': tech + " navigation time",
'data-container': 'body'
}).text(PerformanceBar.formatTime(total));
updateStatus(span);
return ajaxStart = null;
}, 0);
});
$(function() {
if (window.performance) {
return renderPerformanceBar();
} else {
return $('#peek-view-performance-bar').remove();
}
});
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment