Commit c0a029bd authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into bootstrap4

parents 7d224dfa 592b8d71
...@@ -10,3 +10,7 @@ lib/gitlab/background_migration/* ...@@ -10,3 +10,7 @@ lib/gitlab/background_migration/*
app/models/project_services/kubernetes_service.rb app/models/project_services/kubernetes_service.rb
lib/gitlab/workhorse.rb lib/gitlab/workhorse.rb
lib/gitlab/ci/trace/chunked_io.rb lib/gitlab/ci/trace/chunked_io.rb
lib/gitlab/gitaly_client/ref_service.rb
lib/gitlab/gitaly_client/commit_service.rb
lib/gitlab/git/commit.rb
lib/gitlab/git/tag.rb
...@@ -126,6 +126,23 @@ stages: ...@@ -126,6 +126,23 @@ stages:
<<: *dedicated-no-docs-pull-cache-job <<: *dedicated-no-docs-pull-cache-job
<<: *except-docs-and-qa <<: *except-docs-and-qa
.single-script-job: &single-script-job
image: ruby:2.4-alpine
before_script: []
stage: build
cache: {}
dependencies: []
variables: &single-script-job-variables
GIT_STRATEGY: none
before_script:
# We need to download the script rather than clone the repo since the
# package-and-qa job will not be able to run when the branch gets
# deleted (when merging the MR).
- export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}"
- apk add --update openssl
- wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME
- chmod 755 $SCRIPT_NAME
.rake-exec: &rake-exec .rake-exec: &rake-exec
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-no-db-pull-cache-job
script: script:
...@@ -207,19 +224,10 @@ stages: ...@@ -207,19 +224,10 @@ stages:
.review-docs: &review-docs .review-docs: &review-docs
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-qa <<: *except-qa
image: ruby:2.4-alpine <<: *single-script-job
before_script:
- gem install gitlab --no-doc
# We need to download the script rather than clone the repo since the
# review-docs-cleanup job will not be able to run when the branch gets
# deleted (when merging the MR).
- apk add --update openssl
- wget https://gitlab.com/gitlab-org/gitlab-ce/raw/master/scripts/trigger-build-docs
- chmod 755 trigger-build-docs
cache: {}
dependencies: []
variables: variables:
GIT_STRATEGY: none <<: *single-script-job-variables
SCRIPT_NAME: trigger-build-docs
when: manual when: manual
only: only:
- branches - branches
...@@ -253,23 +261,14 @@ stages: ...@@ -253,23 +261,14 @@ stages:
# Trigger a package build in omnibus-gitlab repository # Trigger a package build in omnibus-gitlab repository
# #
package-and-qa: package-and-qa:
image: ruby:2.4-alpine <<: *single-script-job
before_script: []
stage: build
cache: {}
when: manual
variables: variables:
GIT_STRATEGY: none <<: *single-script-job-variables
SCRIPT_NAME: trigger-build-omnibus
retry: 0 retry: 0
before_script:
# We need to download the script rather than clone the repo since the
# package-and-qa job will not be able to run when the branch gets
# deleted (when merging the MR).
- apk add --update openssl
- wget https://gitlab.com/$CI_PROJECT_PATH/raw/$CI_COMMIT_SHA/scripts/trigger-build-omnibus
- chmod 755 trigger-build-omnibus
script: script:
- ./trigger-build-omnibus - ./$SCRIPT_NAME
when: manual
only: only:
- //@gitlab-org/gitlab-ce - //@gitlab-org/gitlab-ce
- //@gitlab-org/gitlab-ee - //@gitlab-org/gitlab-ee
...@@ -286,7 +285,8 @@ review-docs-deploy: ...@@ -286,7 +285,8 @@ review-docs-deploy:
url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup on_stop: review-docs-cleanup
script: script:
- ./trigger-build-docs deploy - gem install gitlab --no-ri --no-rdoc
- ./$SCRIPT_NAME deploy
# Cleanup remote environment of gitlab-docs # Cleanup remote environment of gitlab-docs
review-docs-cleanup: review-docs-cleanup:
...@@ -296,7 +296,26 @@ review-docs-cleanup: ...@@ -296,7 +296,26 @@ review-docs-cleanup:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_NAME
action: stop action: stop
script: script:
- ./trigger-build-docs cleanup - gem install gitlab --no-ri --no-rdoc
- ./SCRIPT_NAME cleanup
##
# Trigger a docker image build in CNG (Cloud Native GitLab) repository
#
cloud-native-image:
image: ruby:2.4-alpine
before_script: []
stage: build
allow_failure: true
cache: {}
before_script:
- gem install gitlab --no-rdoc --no-ri
- chmod 755 ./scripts/trigger-build-cloud-native
script:
- ./scripts/trigger-build-cloud-native
only:
- tags@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ee
# Retrieve knapsack and rspec_flaky reports # Retrieve knapsack and rspec_flaky reports
retrieve-tests-metadata: retrieve-tests-metadata:
...@@ -325,7 +344,7 @@ update-tests-metadata: ...@@ -325,7 +344,7 @@ update-tests-metadata:
- rspec_flaky/ - rspec_flaky/
policy: push policy: push
script: script:
- retry gem install fog-aws mime-types activesupport - retry gem install fog-aws mime-types activesupport --no-ri --no-rdoc
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json - scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json - scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
- FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH} - FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
......
...@@ -2,6 +2,13 @@ ...@@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.7.4 (2018-05-21)
### Fixed (1 change)
- Fix error when deleting an empty list of refs.
## 10.7.3 (2018-05-02) ## 10.7.3 (2018-05-02)
### Fixed (8 changes) ### Fixed (8 changes)
......
...@@ -168,7 +168,7 @@ hits. They are not always necessary, but very convenient. ...@@ -168,7 +168,7 @@ hits. They are not always necessary, but very convenient.
If you are an expert in a particular area, it makes it easier to find issues to If you are an expert in a particular area, it makes it easier to find issues to
work on. You can also subscribe to those labels to receive an email each time an work on. You can also subscribe to those labels to receive an email each time an
issue is labelled with a subject label corresponding to your expertise. issue is labeled with a subject label corresponding to your expertise.
Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api, Examples of subject labels are ~wiki, ~"container registry", ~ldap, ~api,
~issues, ~"merge requests", ~labels, and ~"container registry". ~issues, ~"merge requests", ~labels, and ~"container registry".
...@@ -296,7 +296,24 @@ any potential community contributor to @-mention per above. ...@@ -296,7 +296,24 @@ any potential community contributor to @-mention per above.
## Implement design & UI elements ## Implement design & UI elements
Please see the [UX Guide for GitLab]. For guidance on UX implementation at GitLab, please refer to our [Design System](https://design.gitlab.com/).
The UX team uses labels to manage their workflow.
The ~"UX" label on an issue is a signal to the UX team that it will need UX attention.
To better understand the priority by which UX tackles issues, see the [UX section](https://about.gitlab.com/handbook/ux/) of the handbook.
Once an issue has been worked on and is ready for development, a UXer applies the ~"UX ready" label to that issue.
The UX team has a special type label called ~"design artifact". This label indicates that the final output
for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone.
Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is
needed until the solution has been decided.
~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
Once the ~"design artifact" issue has been completed, the UXer removes the ~"design artifact" label and applies the ~"UX ready" label. The Product Manager can use the
existing issue or decide to create a whole new issue for the purpose of development.
## Issue tracker ## Issue tracker
......
...@@ -68,8 +68,7 @@ ...@@ -68,8 +68,7 @@
this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader); this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', showLoader);
this.service.getFolderContent(folder.folder_path) this.service.getFolderContent(folder.folder_path)
.then(resp => resp.json()) .then(response => this.store.setfolderContent(folder, response.data.environments))
.then(response => this.store.setfolderContent(folder, response.environments))
.then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false)) .then(() => this.store.updateEnvironmentProp(folder, 'isLoadingFolderContent', false))
.catch(() => { .catch(() => {
Flash(s__('Environments|An error occurred while fetching the environments.')); Flash(s__('Environments|An error occurred while fetching the environments.'));
......
...@@ -6,7 +6,6 @@ import Visibility from 'visibilityjs'; ...@@ -6,7 +6,6 @@ import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import { import {
getParameterByName, getParameterByName,
parseQueryStringIntoObject,
} from '../../lib/utils/common_utils'; } from '../../lib/utils/common_utils';
import { s__ } from '../../locale'; import { s__ } from '../../locale';
import Flash from '../../flash'; import Flash from '../../flash';
...@@ -46,17 +45,14 @@ export default { ...@@ -46,17 +45,14 @@ export default {
methods: { methods: {
saveData(resp) { saveData(resp) {
const headers = resp.headers; this.isLoading = false;
return resp.json().then((response) => {
this.isLoading = false; if (_.isEqual(resp.config.params, this.requestData)) {
this.store.storeAvailableCount(resp.data.available_count);
if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) { this.store.storeStoppedCount(resp.data.stopped_count);
this.store.storeAvailableCount(response.available_count); this.store.storeEnvironments(resp.data.environments);
this.store.storeStoppedCount(response.stopped_count); this.store.setPagination(resp.headers);
this.store.storeEnvironments(response.environments); }
this.store.setPagination(headers);
}
});
}, },
/** /**
...@@ -70,7 +66,7 @@ export default { ...@@ -70,7 +66,7 @@ export default {
updateContent(parameters) { updateContent(parameters) {
this.updateInternalState(parameters); this.updateInternalState(parameters);
// fetch new data // fetch new data
return this.service.get(this.requestData) return this.service.fetchEnvironments(this.requestData)
.then(response => this.successCallback(response)) .then(response => this.successCallback(response))
.then(() => { .then(() => {
// restart polling // restart polling
...@@ -105,7 +101,7 @@ export default { ...@@ -105,7 +101,7 @@ export default {
fetchEnvironments() { fetchEnvironments() {
this.isLoading = true; this.isLoading = true;
return this.service.get(this.requestData) return this.service.fetchEnvironments(this.requestData)
.then(this.successCallback) .then(this.successCallback)
.catch(this.errorCallback); .catch(this.errorCallback);
}, },
...@@ -141,7 +137,7 @@ export default { ...@@ -141,7 +137,7 @@ export default {
this.poll = new Poll({ this.poll = new Poll({
resource: this.service, resource: this.service,
method: 'get', method: 'fetchEnvironments',
data: this.requestData, data: this.requestData,
successCallback: this.successCallback, successCallback: this.successCallback,
errorCallback: this.errorCallback, errorCallback: this.errorCallback,
......
/* eslint-disable class-methods-use-this */ import axios from '~/lib/utils/axios_utils';
import Vue from 'vue';
import VueResource from 'vue-resource';
Vue.use(VueResource);
export default class EnvironmentsService { export default class EnvironmentsService {
constructor(endpoint) { constructor(endpoint) {
this.environments = Vue.resource(endpoint); this.environmentsEndpoint = endpoint;
this.folderResults = 3; this.folderResults = 3;
} }
get(options = {}) { fetchEnvironments(options = {}) {
const { scope, page } = options; const { scope, page } = options;
return this.environments.get({ scope, page }); return axios.get(this.environmentsEndpoint, { params: { scope, page } });
} }
// eslint-disable-next-line class-methods-use-this
postAction(endpoint) { postAction(endpoint) {
return Vue.http.post(endpoint, {}, { emulateJSON: true }); return axios.post(endpoint, {}, { emulateJSON: true });
} }
getFolderContent(folderUrl) { getFolderContent(folderUrl) {
return Vue.http.get(`${folderUrl}.json?per_page=${this.folderResults}`); return axios.get(`${folderUrl}.json?per_page=${this.folderResults}`);
} }
} }
...@@ -5,7 +5,7 @@ import LoadingButton from '~/vue_shared/components/loading_button.vue'; ...@@ -5,7 +5,7 @@ import LoadingButton from '~/vue_shared/components/loading_button.vue';
import CommitMessageField from './message_field.vue'; import CommitMessageField from './message_field.vue';
import Actions from './actions.vue'; import Actions from './actions.vue';
import SuccessMessage from './success_message.vue'; import SuccessMessage from './success_message.vue';
import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT, COMMIT_ITEM_PADDING } from '../../constants'; import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants';
export default { export default {
components: { components: {
...@@ -70,7 +70,7 @@ export default { ...@@ -70,7 +70,7 @@ export default {
? this.$refs.formEl && this.$refs.formEl.offsetHeight ? this.$refs.formEl && this.$refs.formEl.offsetHeight
: this.$refs.compactEl && this.$refs.compactEl.offsetHeight; : this.$refs.compactEl && this.$refs.compactEl.offsetHeight;
this.componentHeight = elHeight + COMMIT_ITEM_PADDING; this.componentHeight = elHeight;
}, },
enterTransition() { enterTransition() {
this.$nextTick(() => { this.$nextTick(() => {
...@@ -78,7 +78,7 @@ export default { ...@@ -78,7 +78,7 @@ export default {
? this.$refs.compactEl && this.$refs.compactEl.offsetHeight ? this.$refs.compactEl && this.$refs.compactEl.offsetHeight
: this.$refs.formEl && this.$refs.formEl.offsetHeight; : this.$refs.formEl && this.$refs.formEl.offsetHeight;
this.componentHeight = elHeight + COMMIT_ITEM_PADDING; this.componentHeight = elHeight;
}); });
}, },
afterEndTransition() { afterEndTransition() {
......
...@@ -122,11 +122,11 @@ export default { ...@@ -122,11 +122,11 @@ export default {
<div <div
class="file" class="file"
:class="fileClass" :class="fileClass"
@click="clickFile"
role="button"
> >
<div <div
class="file-name" class="file-name"
@click="clickFile"
role="button"
> >
<span <span
class="ide-file-name str-truncated" class="ide-file-name str-truncated"
......
...@@ -5,8 +5,6 @@ export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; ...@@ -5,8 +5,6 @@ export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33;
export const MAX_WINDOW_HEIGHT_COMPACT = 750; export const MAX_WINDOW_HEIGHT_COMPACT = 750;
export const COMMIT_ITEM_PADDING = 32;
// Commit message textarea // Commit message textarea
export const MAX_TITLE_LENGTH = 50; export const MAX_TITLE_LENGTH = 50;
export const MAX_BODY_LENGTH = 72; export const MAX_BODY_LENGTH = 72;
......
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import tooltip from '../../../vue_shared/directives/tooltip'; import axios from '~/lib/utils/axios_utils';
import Icon from '../../../vue_shared/components/icon.vue'; import { dasherize } from '~/lib/utils/text_utility';
import { dasherize } from '../../../lib/utils/text_utility'; import { __ } from '~/locale';
import eventHub from '../../event_hub'; import createFlash from '~/flash';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
/** /**
* Renders either a cancel, retry or play icon pointing to the given path. * Renders either a cancel, retry or play icon button and handles the post request
*
* Used in:
* - mr widget mini pipeline graph: `mr_widget_pipeline.vue`
* - pipelines table
* - pipelines table in merge request page
* - pipelines table in commit page
* - pipelines detail page in big graph
*/ */
export default { export default {
components: { components: {
...@@ -32,16 +42,10 @@ export default { ...@@ -32,16 +42,10 @@ export default {
required: true, required: true,
}, },
requestFinishedFor: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
return { return {
isDisabled: false, isDisabled: false,
linkRequested: '',
}; };
}, },
...@@ -51,19 +55,28 @@ export default { ...@@ -51,19 +55,28 @@ export default {
return `${actionIconDash} js-icon-${actionIconDash}`; return `${actionIconDash} js-icon-${actionIconDash}`;
}, },
}, },
watch: {
requestFinishedFor() {
if (this.requestFinishedFor === this.linkRequested) {
this.isDisabled = false;
}
},
},
methods: { methods: {
/**
* The request should not be handled here.
* However due to this component being used in several
* different apps it avoids repetition & complexity.
*
*/
onClickAction() { onClickAction() {
$(this.$el).tooltip('hide'); $(this.$el).tooltip('hide');
eventHub.$emit('postAction', this.link);
this.linkRequested = this.link;
this.isDisabled = true; this.isDisabled = true;
axios.post(`${this.link}.json`)
.then(() => {
this.isDisabled = false;
this.$emit('pipelineActionRequestComplete');
})
.catch(() => {
this.isDisabled = false;
createFlash(__('An error occurred while making the request.'));
});
}, },
}, },
}; };
...@@ -80,6 +93,6 @@ btn-transparent ci-action-icon-container ci-action-icon-wrapper" ...@@ -80,6 +93,6 @@ btn-transparent ci-action-icon-container ci-action-icon-wrapper"
data-container="body" data-container="body"
:disabled="isDisabled" :disabled="isDisabled"
> >
<icon :name="actionIcon" /> <icon :name="actionIcon"/>
</button> </button>
</template> </template>
...@@ -42,11 +42,6 @@ export default { ...@@ -42,11 +42,6 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
requestFinishedFor: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
...@@ -76,11 +71,15 @@ export default { ...@@ -76,11 +71,15 @@ export default {
e.stopPropagation(); e.stopPropagation();
}); });
}, },
pipelineActionRequestComplete() {
this.$emit('pipelineActionRequestComplete');
},
}, },
}; };
</script> </script>
<template> <template>
<div class="ci-job-dropdown-container"> <div class="ci-job-dropdown-container dropdown">
<button <button
v-tooltip v-tooltip
type="button" type="button"
...@@ -110,7 +109,7 @@ export default { ...@@ -110,7 +109,7 @@ export default {
<job-component <job-component
:job="item" :job="item"
css-class-job-name="mini-pipeline-graph-dropdown-item" css-class-job-name="mini-pipeline-graph-dropdown-item"
:request-finished-for="requestFinishedFor" @pipelineActionRequestComplete="pipelineActionRequestComplete"
/> />
</li> </li>
</ul> </ul>
......
...@@ -16,11 +16,6 @@ export default { ...@@ -16,11 +16,6 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
requestFinishedFor: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
...@@ -51,6 +46,10 @@ export default { ...@@ -51,6 +46,10 @@ export default {
return className; return className;
}, },
refreshPipelineGraph() {
this.$emit('refreshPipelineGraph');
},
}, },
}; };
</script> </script>
...@@ -74,7 +73,7 @@ export default { ...@@ -74,7 +73,7 @@ export default {
:key="stage.name" :key="stage.name"
:stage-connector-class="stageConnectorClass(index, stage)" :stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)" :is-first-column="isFirstColumn(index)"
:request-finished-for="requestFinishedFor" @refreshPipelineGraph="refreshPipelineGraph"
/> />
</ul> </ul>
</div> </div>
......
...@@ -46,11 +46,6 @@ export default { ...@@ -46,11 +46,6 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
requestFinishedFor: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
status() { status() {
...@@ -84,6 +79,11 @@ export default { ...@@ -84,6 +79,11 @@ export default {
return this.job.status && this.job.status.action && this.job.status.action.path; return this.job.status && this.job.status.action && this.job.status.action.path;
}, },
}, },
methods: {
pipelineActionRequestComplete() {
this.$emit('pipelineActionRequestComplete');
},
},
}; };
</script> </script>
<template> <template>
...@@ -126,7 +126,7 @@ export default { ...@@ -126,7 +126,7 @@ export default {
:tooltip-text="status.action.title" :tooltip-text="status.action.title"
:link="status.action.path" :link="status.action.path"
:action-icon="status.action.icon" :action-icon="status.action.icon"
:request-finished-for="requestFinishedFor" @pipelineActionRequestComplete="pipelineActionRequestComplete"
/> />
</div> </div>
</template> </template>
...@@ -29,12 +29,6 @@ export default { ...@@ -29,12 +29,6 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
requestFinishedFor: {
type: String,
required: false,
default: '',
},
}, },
methods: { methods: {
...@@ -49,6 +43,10 @@ export default { ...@@ -49,6 +43,10 @@ export default {
buildConnnectorClass(index) { buildConnnectorClass(index) {
return index === 0 && !this.isFirstColumn ? 'left-connector' : ''; return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
}, },
pipelineActionRequestComplete() {
this.$emit('refreshPipelineGraph');
},
}, },
}; };
</script> </script>
...@@ -75,12 +73,13 @@ export default { ...@@ -75,12 +73,13 @@ export default {
v-if="job.size === 1" v-if="job.size === 1"
:job="job" :job="job"
css-class-job-name="build-content" css-class-job-name="build-content"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/> />
<dropdown-job-component <dropdown-job-component
v-if="job.size > 1" v-if="job.size > 1"
:job="job" :job="job"
:request-finished-for="requestFinishedFor" @pipelineActionRequestComplete="pipelineActionRequestComplete"
/> />
</li> </li>
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
import CommitComponent from '../../vue_shared/components/commit.vue'; import CommitComponent from '../../vue_shared/components/commit.vue';
import LoadingButton from '../../vue_shared/components/loading_button.vue'; import LoadingButton from '../../vue_shared/components/loading_button.vue';
import Icon from '../../vue_shared/components/icon.vue'; import Icon from '../../vue_shared/components/icon.vue';
import { PIPELINES_TABLE } from '../constants';
/** /**
* Pipeline table row. * Pipeline table row.
...@@ -46,6 +47,7 @@ ...@@ -46,6 +47,7 @@
required: true, required: true,
}, },
}, },
pipelinesTable: PIPELINES_TABLE,
data() { data() {
return { return {
isRetrying: false, isRetrying: false,
...@@ -297,6 +299,7 @@ ...@@ -297,6 +299,7 @@
v-for="(stage, index) in pipeline.details.stages" v-for="(stage, index) in pipeline.details.stages"
:key="index"> :key="index">
<pipeline-stage <pipeline-stage
:type="$options.pipelinesTable"
:stage="stage" :stage="stage"
:update-dropdown="updateGraphDropdown" :update-dropdown="updateGraphDropdown"
/> />
......
...@@ -21,6 +21,7 @@ import Icon from '../../vue_shared/components/icon.vue'; ...@@ -21,6 +21,7 @@ import Icon from '../../vue_shared/components/icon.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import JobComponent from './graph/job_component.vue'; import JobComponent from './graph/job_component.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import { PIPELINES_TABLE } from '../constants';
export default { export default {
components: { components: {
...@@ -44,6 +45,12 @@ export default { ...@@ -44,6 +45,12 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
type: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
...@@ -133,6 +140,16 @@ export default { ...@@ -133,6 +140,16 @@ export default {
isDropdownOpen() { isDropdownOpen() {
return this.$el.classList.contains('open'); return this.$el.classList.contains('open');
}, },
pipelineActionRequestComplete() {
if (this.type === PIPELINES_TABLE) {
// warn the table to update
eventHub.$emit('refreshPipelinesTable');
} else {
// close the dropdown in mr widget
$(this.$refs.dropdown).dropdown('toggle');
}
},
}, },
}; };
</script> </script>
...@@ -151,6 +168,7 @@ export default { ...@@ -151,6 +168,7 @@ export default {
id="stageDropdown" id="stageDropdown"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false" aria-expanded="false"
ref="dropdown"
> >
<span <span
...@@ -188,6 +206,7 @@ export default { ...@@ -188,6 +206,7 @@ export default {
<job-component <job-component
:job="job" :job="job"
css-class-job-name="mini-pipeline-graph-dropdown-item" css-class-job-name="mini-pipeline-graph-dropdown-item"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/> />
</li> </li>
</ul> </ul>
......
// eslint-disable-next-line import/prefer-default-export
export const CANCEL_REQUEST = 'CANCEL_REQUEST'; export const CANCEL_REQUEST = 'CANCEL_REQUEST';
export const PIPELINES_TABLE = 'PIPELINES_TABLE';
...@@ -55,11 +55,13 @@ export default { ...@@ -55,11 +55,13 @@ export default {
eventHub.$on('postAction', this.postAction); eventHub.$on('postAction', this.postAction);
eventHub.$on('retryPipeline', this.postAction); eventHub.$on('retryPipeline', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable); eventHub.$on('clickedDropdown', this.updateTable);
eventHub.$on('refreshPipelinesTable', this.fetchPipelines);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('postAction', this.postAction); eventHub.$off('postAction', this.postAction);
eventHub.$off('retryPipeline', this.postAction); eventHub.$off('retryPipeline', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable); eventHub.$off('clickedDropdown', this.updateTable);
eventHub.$off('refreshPipelinesTable', this.fetchPipelines);
}, },
destroyed() { destroyed() {
this.poll.stop(); this.poll.stop();
......
...@@ -25,30 +25,14 @@ export default () => { ...@@ -25,30 +25,14 @@ export default () => {
data() { data() {
return { return {
mediator, mediator,
requestFinishedFor: null,
}; };
}, },
created() {
eventHub.$on('postAction', this.postAction);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
},
methods: { methods: {
postAction(action) { requestRefreshPipelineGraph() {
// Click was made, reset this variable // When an action is clicked
this.requestFinishedFor = null; // (wether in the dropdown or in the main nodes, we refresh the big graph)
this.mediator.refreshPipeline()
this.mediator.service .catch(() => Flash(__('An error occurred while making the request.')));
.postAction(action)
.then(() => {
this.mediator.refreshPipeline();
this.requestFinishedFor = action;
})
.catch(() => {
this.requestFinishedFor = action;
Flash(__('An error occurred while making the request.'));
});
}, },
}, },
render(createElement) { render(createElement) {
...@@ -56,7 +40,9 @@ export default () => { ...@@ -56,7 +40,9 @@ export default () => {
props: { props: {
isLoading: this.mediator.state.isLoading, isLoading: this.mediator.state.isLoading,
pipeline: this.mediator.store.state.pipeline, pipeline: this.mediator.store.state.pipeline,
requestFinishedFor: this.requestFinishedFor, },
on: {
refreshPipelineGraph: this.requestRefreshPipelineGraph,
}, },
}); });
}, },
......
...@@ -7,7 +7,7 @@ export default class ShortcutsNavigation extends Shortcuts { ...@@ -7,7 +7,7 @@ export default class ShortcutsNavigation extends Shortcuts {
super(); super();
Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project')); Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project'));
Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity')); Mousetrap.bind('g v', () => findAndFollowLink('.shortcuts-project-activity'));
Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree')); Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree'));
Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits')); Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits'));
Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds')); Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds'));
...@@ -16,9 +16,10 @@ export default class ShortcutsNavigation extends Shortcuts { ...@@ -16,9 +16,10 @@ export default class ShortcutsNavigation extends Shortcuts {
Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues')); Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues'));
Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards')); Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards'));
Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests')); Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests'));
Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki')); Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki'));
Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets')); Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets'));
Mousetrap.bind('g k', () => findAndFollowLink('.shortcuts-kubernetes'));
Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-environments'));
Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue')); Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
this.enabledHelp.push('.hidden-shortcut.project'); this.enabledHelp.push('.hidden-shortcut.project');
......
...@@ -279,251 +279,14 @@ ul.indent-list { ...@@ -279,251 +279,14 @@ ul.indent-list {
padding: 10px 0 0 30px; padding: 10px 0 0 30px;
} }
// Specific styles for tree list // Specific styles for tree list
@keyframes spin-avatar { @keyframes spin-avatar {
from { transform: rotate(0deg); } from { transform: rotate(0deg); }
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
.groups-list-tree-container {
.has-no-search-results {
text-align: center;
padding: $gl-padding;
font-style: italic;
color: $well-light-text-color;
}
> .group-list-tree > .group-row.has-children:first-child {
border-top: 0;
}
}
.group-list-tree {
.avatar-container.content-loading {
position: relative;
> a,
> a .avatar {
height: 100%;
border-radius: 50%;
}
> a {
padding: 2px;
.avatar {
border: 2px solid $white-normal;
&.identicon {
line-height: 15px;
}
}
}
&::after {
content: "";
position: absolute;
height: 100%;
width: 100%;
background-color: transparent;
border: 2px outset $kdb-border;
border-radius: 50%;
animation: spin-avatar 3s infinite linear;
}
}
.folder-toggle-wrap {
float: left;
line-height: $list-text-height;
font-size: 0;
span {
font-size: $gl-font-size;
}
}
.folder-caret,
.item-type-icon {
display: inline-block;
}
.folder-caret {
width: 15px;
svg {
margin-bottom: 2px;
}
}
.item-type-icon {
margin-top: 2px;
width: 20px;
}
> .group-row:not(.has-children) {
.folder-caret {
opacity: 0;
}
}
.content-list li:last-child {
padding-bottom: 0;
}
.group-list-tree {
margin-bottom: 0;
margin-left: 30px;
position: relative;
&::before {
content: '';
display: block;
width: 0;
position: absolute;
top: 5px;
bottom: 0;
left: -16px;
border-left: 2px solid $border-white-normal;
}
.group-row {
position: relative;
&::before {
content: "";
display: block;
width: 10px;
height: 0;
border-top: 2px solid $border-white-normal;
position: absolute;
top: 30px;
left: -16px;
}
&:last-child::before {
background: $white-light;
height: auto;
top: 30px;
bottom: 0;
}
&.being-removed {
opacity: 0.5;
}
}
}
.group-row {
padding: 0;
&.has-children {
border-top: 0;
}
&:first-child {
border-top: 1px solid $white-normal;
}
&:last-of-type {
.group-row-contents:not(:hover) {
border-bottom: 1px solid transparent;
}
}
}
.group-row-contents {
padding: 10px 10px 8px;
border-top: solid 1px transparent;
border-bottom: solid 1px $white-normal;
&:hover {
border-color: $row-hover-border;
background-color: $row-hover;
cursor: pointer;
}
.avatar-container > a {
width: 100%;
text-decoration: none;
}
&.has-more-items {
display: block;
padding: 20px 10px;
}
.stats {
position: relative;
line-height: 46px;
> span {
display: inline-flex;
align-items: center;
height: 16px;
min-width: 30px;
}
> span:last-child {
margin-right: 0;
}
.stat-value {
margin: 2px 0 0 5px;
}
}
.controls {
margin-left: 5px;
> .btn {
margin-right: $btn-xs-side-margin;
}
}
}
.project-row-contents .stats {
line-height: inherit;
> span:first-child {
margin-left: 25px;
}
.item-visibility {
margin-right: 0;
}
.last-updated {
position: absolute;
right: 12px;
min-width: 250px;
text-align: right;
color: $gl-text-color-secondary;
}
}
}
.namespace-title { .namespace-title {
.tooltip-inner { .tooltip-inner {
max-width: 350px; max-width: 350px;
} }
} }
ul.group-list-tree {
li.group-row {
> .group-row-contents .title {
line-height: $list-text-height;
}
&.has-description > .group-row-contents .title {
line-height: inherit;
}
}
}
.js-groups-list-holder {
.groups-list-loading {
font-size: 34px;
text-align: center;
}
}
...@@ -18,6 +18,10 @@ ...@@ -18,6 +18,10 @@
.group-row { .group-row {
@include basic-list-stats; @include basic-list-stats;
.description p {
margin-bottom: 0;
}
} }
.ldap-group-links { .ldap-group-links {
...@@ -237,3 +241,231 @@ ...@@ -237,3 +241,231 @@
overflow-y: unset; overflow-y: unset;
} }
} }
.groups-list-tree-container {
.has-no-search-results {
text-align: center;
padding: $gl-padding;
font-style: italic;
color: $well-light-text-color;
}
> .group-list-tree > .group-row.has-children:first-child {
border-top: 0;
}
}
.group-list-tree {
.avatar-container.content-loading {
position: relative;
> a,
> a .avatar {
height: 100%;
border-radius: 50%;
}
> a {
padding: 2px;
.avatar {
border: 2px solid $white-normal;
&.identicon {
line-height: 15px;
}
}
}
&::after {
content: "";
position: absolute;
height: 100%;
width: 100%;
background-color: transparent;
border: 2px outset $kdb-border;
border-radius: 50%;
animation: spin-avatar 3s infinite linear;
}
}
.folder-toggle-wrap {
float: left;
line-height: $list-text-height;
font-size: 0;
span {
font-size: $gl-font-size;
}
}
.folder-caret,
.item-type-icon {
display: inline-block;
}
.folder-caret {
width: 15px;
svg {
margin-bottom: 2px;
}
}
.item-type-icon {
margin-top: 2px;
width: 20px;
}
> .group-row:not(.has-children) {
.folder-caret {
opacity: 0;
}
}
.content-list li:last-child {
padding-bottom: 0;
}
.group-list-tree {
margin-bottom: 0;
margin-left: 30px;
position: relative;
&::before {
content: '';
display: block;
width: 0;
position: absolute;
top: 5px;
bottom: 0;
left: -16px;
border-left: 2px solid $border-white-normal;
}
.group-row {
position: relative;
&::before {
content: "";
display: block;
width: 10px;
height: 0;
border-top: 2px solid $border-white-normal;
position: absolute;
top: 30px;
left: -16px;
}
&:last-child::before {
background: $white-light;
height: auto;
top: 30px;
bottom: 0;
}
&.being-removed {
opacity: 0.5;
}
}
}
.group-row {
padding: 0;
&.has-children {
border-top: 0;
}
&:first-child {
border-top: 1px solid $white-normal;
}
}
.group-row-contents {
padding: $gl-padding-top;
&:hover {
border-color: $row-hover-border;
background-color: $row-hover;
cursor: pointer;
}
.avatar-container > a {
width: 100%;
text-decoration: none;
}
&.has-more-items {
display: block;
padding: 20px 10px;
}
.stats {
position: relative;
line-height: 46px;
> span {
display: inline-flex;
align-items: center;
height: 16px;
min-width: 30px;
}
> span:last-child {
margin-right: 0;
}
.stat-value {
margin: 2px 0 0 5px;
}
}
.controls {
margin-left: 5px;
> .btn {
margin-right: $btn-xs-side-margin;
}
}
}
.project-row-contents .stats {
line-height: inherit;
> span:first-child {
margin-left: 25px;
}
.item-visibility {
margin-right: 0;
}
.last-updated {
position: absolute;
right: 12px;
min-width: 250px;
text-align: right;
color: $gl-text-color-secondary;
}
}
}
ul.group-list-tree {
li.group-row {
> .group-row-contents .title {
line-height: $list-text-height;
}
&.has-description > .group-row-contents .title {
line-height: inherit;
}
}
}
.js-groups-list-holder {
.groups-list-loading {
font-size: 34px;
text-align: center;
}
}
...@@ -197,9 +197,21 @@ ...@@ -197,9 +197,21 @@
} }
&.assignee { &.assignee {
.author_link:hover { .author_link {
.author { display: block;
text-decoration: underline; padding-left: 42px;
position: relative;
&:hover {
.author {
text-decoration: underline;
}
}
.avatar {
left: 0;
position: absolute;
top: 0;
} }
} }
} }
......
...@@ -66,13 +66,9 @@ ...@@ -66,13 +66,9 @@
} }
} }
.btn-group { .btn-group.open .btn-default {
&.open { background-color: $white-normal;
.btn-default { border-color: $border-white-normal;
background-color: $white-normal;
border-color: $border-white-normal;
}
}
} }
.btn .text-center { .btn .text-center {
...@@ -361,16 +357,14 @@ ...@@ -361,16 +357,14 @@
&:not(:first-child) { &:not(:first-child) {
margin-left: 44px; margin-left: 44px;
.left-connector { .left-connector::before {
&::before { content: '';
content: ''; position: absolute;
position: absolute; top: 48%;
top: 48%; left: -44px;
left: -44px; border-top: 2px solid $border-color;
border-top: 2px solid $border-color; width: 44px;
width: 44px; height: 1px;
height: 1px;
}
} }
} }
} }
...@@ -386,22 +380,16 @@ ...@@ -386,22 +380,16 @@
&:last-child { &:last-child {
.build { .build {
// Remove right connecting horizontal line from first build in last stage // Remove right connecting horizontal line from first build in last stage
&:first-child { &:first-child::after {
&::after { border: 0;
border: 0;
}
} }
// Remove right curved connectors from all builds in last stage // Remove right curved connectors from all builds in last stage
&:not(:first-child) { &:not(:first-child)::after {
&::after { border: 0;
border: 0;
}
} }
// Remove opposite curve // Remove opposite curve
.curve { .curve::before {
&::before { display: none;
display: none;
}
} }
} }
} }
...@@ -409,16 +397,12 @@ ...@@ -409,16 +397,12 @@
&:first-child { &:first-child {
.build { .build {
// Remove left curved connectors from all builds in first stage // Remove left curved connectors from all builds in first stage
&:not(:first-child) { &:not(:first-child)::before {
&::before { border: 0;
border: 0;
}
} }
// Remove opposite curve // Remove opposite curve
.curve { .curve::after {
&::after { display: none;
display: none;
}
} }
} }
} }
......
...@@ -39,12 +39,15 @@ ...@@ -39,12 +39,15 @@
.ide-file-list { .ide-file-list {
flex: 1; flex: 1;
padding-left: $gl-padding;
padding-right: $gl-padding;
padding-bottom: $grid-size;
.file { .file {
cursor: pointer; cursor: pointer;
&.file-open { &.file-open {
background: $link-active-background; background: $white-normal;
} }
&.file-active { &.file-active {
...@@ -84,12 +87,11 @@ ...@@ -84,12 +87,11 @@
.ide-new-btn { .ide-new-btn {
display: none; display: none;
margin-right: -8px;
} }
&:hover, &:hover,
&:focus { &:focus {
background: $link-active-background; background: $white-normal;
.ide-new-btn { .ide-new-btn {
display: block; display: block;
...@@ -111,12 +113,11 @@ ...@@ -111,12 +113,11 @@
} }
} }
.file-name, .file-name {
.file-col-commit-message {
display: flex; display: flex;
overflow: visible; overflow: visible;
align-items: center; align-items: center;
padding: 6px 12px; width: 100%;
} }
.multi-file-loading-container { .multi-file-loading-container {
...@@ -306,8 +307,18 @@ ...@@ -306,8 +307,18 @@
} }
.preview-container { .preview-container {
height: 100%; flex-grow: 1;
overflow: auto; position: relative;
.md-previewer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: auto;
padding: $gl-padding;
}
.file-container { .file-container {
background-color: $gray-darker; background-color: $gray-darker;
...@@ -347,10 +358,6 @@ ...@@ -347,10 +358,6 @@
color: $diff-image-info-color; color: $diff-image-info-color;
} }
} }
.md-previewer {
padding: $gl-padding;
}
} }
.ide-mode-tabs { .ide-mode-tabs {
...@@ -501,7 +508,7 @@ ...@@ -501,7 +508,7 @@
align-items: center; align-items: center;
margin-bottom: 0; margin-bottom: 0;
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
padding: $gl-btn-padding $gl-padding; padding: 12px 0;
} }
.multi-file-commit-panel-header-title { .multi-file-commit-panel-header-title {
...@@ -523,32 +530,31 @@ ...@@ -523,32 +530,31 @@
.multi-file-commit-list { .multi-file-commit-list {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
padding: $gl-padding; padding: $grid-size 0;
margin-left: -$grid-size;
margin-right: -$grid-size;
min-height: 60px; min-height: 60px;
.multi-file-commit-list-item {
margin-left: 0;
margin-right: 0;
}
&.help-block {
margin-left: 0;
right: 0;
}
} }
.multi-file-commit-list-item { .multi-file-commit-list-item {
display: flex;
padding: 0;
align-items: center;
border-radius: $border-radius-default;
.multi-file-discard-btn { .multi-file-discard-btn {
display: none; display: none;
margin-top: -2px; margin-top: -2px;
margin-left: auto; margin-left: auto;
margin-right: $grid-size;
color: $gl-link-color; color: $gl-link-color;
&:focus,
&:hover {
text-decoration: underline;
}
} }
&:hover { &:hover {
background: $white-normal;
.multi-file-discard-btn { .multi-file-discard-btn {
display: flex; display: flex;
} }
...@@ -584,25 +590,39 @@ ...@@ -584,25 +590,39 @@
} }
} }
.multi-file-commit-list-item,
.ide-file-list .file {
display: flex;
align-items: center;
margin-left: -$grid-size;
margin-right: -$grid-size;
padding: $grid-size / 2 $grid-size;
border-radius: $border-radius-default;
text-align: left;
&:hover,
&:focus {
background: $white-normal;
}
}
.multi-file-commit-list-path { .multi-file-commit-list-path {
padding: $grid-size / 2; padding: 0;
padding-left: $grid-size;
background: none; background: none;
border: 0; border: 0;
text-align: left; text-align: left;
width: 100%; width: 100%;
min-width: 0;
&:hover,
&:focus {
outline: 0;
}
svg { svg {
min-width: 16px; min-width: 16px;
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
} }
&:hover,
&:focus {
outline: 0;
}
} }
.multi-file-commit-list-file-path { .multi-file-commit-list-file-path {
...@@ -619,12 +639,18 @@ ...@@ -619,12 +639,18 @@
.multi-file-commit-form { .multi-file-commit-form {
position: relative; position: relative;
padding: $gl-padding;
background-color: $white-light; background-color: $white-light;
border-top: 1px solid $white-dark;
border-left: 1px solid $white-dark; border-left: 1px solid $white-dark;
transition: all 0.3s ease; transition: all 0.3s ease;
> form,
> .commit-form-compact {
padding: $gl-padding 0;
margin-left: $gl-padding;
margin-right: $gl-padding;
border-top: 1px solid $white-dark;
}
.btn { .btn {
font-size: $gl-font-size; font-size: $gl-font-size;
} }
...@@ -787,8 +813,9 @@ ...@@ -787,8 +813,9 @@
display: flex; display: flex;
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
width: 100%;
min-height: 140px; min-height: 140px;
margin-left: $gl-padding;
margin-right: $gl-padding;
&.is-first { &.is-first {
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
...@@ -979,9 +1006,8 @@ ...@@ -979,9 +1006,8 @@
.ide-tree-header { .ide-tree-header {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px 0; margin-bottom: 8px;
margin-left: 10px; padding: 12px 0;
margin-right: 10px;
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
.ide-new-btn { .ide-new-btn {
...@@ -1012,9 +1038,9 @@ ...@@ -1012,9 +1038,9 @@
.commit-form-slide-up-enter-active, .commit-form-slide-up-enter-active,
.commit-form-slide-up-leave-active { .commit-form-slide-up-leave-active {
position: absolute; position: absolute;
top: 16px; top: 0;
left: 16px; left: 0;
right: 16px; right: 0;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
......
...@@ -94,7 +94,7 @@ module Boards ...@@ -94,7 +94,7 @@ module Boards
def serialize_as_json(resource) def serialize_as_json(resource)
resource.as_json( resource.as_json(
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position], only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight],
labels: true, labels: true,
issue_endpoints: true, issue_endpoints: true,
include_full_project_path: board.group_board?, include_full_project_path: board.group_board?,
......
...@@ -23,7 +23,7 @@ class Profiles::KeysController < Profiles::ApplicationController ...@@ -23,7 +23,7 @@ class Profiles::KeysController < Profiles::ApplicationController
def destroy def destroy
@key = current_user.keys.find(params[:id]) @key = current_user.keys.find(params[:id])
@key.destroy Keys::DestroyService.new(current_user).execute(@key)
respond_to do |format| respond_to do |format|
format.html { redirect_to profile_keys_url, status: 302 } format.html { redirect_to profile_keys_url, status: 302 }
......
...@@ -18,19 +18,12 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -18,19 +18,12 @@ class Projects::PipelinesController < Projects::ApplicationController
.page(params[:page]) .page(params[:page])
.per(30) .per(30)
@running_count = PipelinesFinder @running_count = limited_pipelines_count(project, 'running')
.new(project, scope: 'running').execute.count @pending_count = limited_pipelines_count(project, 'pending')
@finished_count = limited_pipelines_count(project, 'finished')
@pipelines_count = limited_pipelines_count(project)
@pending_count = PipelinesFinder Gitlab::Ci::Pipeline::Preloader.preload(@pipelines)
.new(project, scope: 'pending').execute.count
@finished_count = PipelinesFinder
.new(project, scope: 'finished').execute.count
@pipelines_count = PipelinesFinder
.new(project).execute.count
@pipelines.map(&:commit) # List commits for batch loading
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -41,7 +34,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -41,7 +34,7 @@ class Projects::PipelinesController < Projects::ApplicationController
pipelines: PipelineSerializer pipelines: PipelineSerializer
.new(project: @project, current_user: @current_user) .new(project: @project, current_user: @current_user)
.with_pagination(request, response) .with_pagination(request, response)
.represent(@pipelines), .represent(@pipelines, disable_coverage: true),
count: { count: {
all: @pipelines_count, all: @pipelines_count,
running: @running_count, running: @running_count,
...@@ -185,4 +178,10 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -185,4 +178,10 @@ class Projects::PipelinesController < Projects::ApplicationController
def authorize_update_pipeline! def authorize_update_pipeline!
return access_denied! unless can?(current_user, :update_pipeline, @pipeline) return access_denied! unless can?(current_user, :update_pipeline, @pipeline)
end end
def limited_pipelines_count(project, scope = nil)
finder = PipelinesFinder.new(project, scope: scope)
view_context.limited_counter_with_delimiter(finder.execute)
end
end end
...@@ -11,7 +11,14 @@ module Projects ...@@ -11,7 +11,14 @@ module Projects
@hook = ProjectHook.new @hook = ProjectHook.new
# Services # Services
@services = @project.find_or_initialize_services @services = @project.find_or_initialize_services(exceptions: service_exceptions)
end
private
# Returns a list of services that should be hidden from the list
def service_exceptions
@project.disabled_services.dup
end end
end end
end end
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
# klass - actual class like Issue or MergeRequest # klass - actual class like Issue or MergeRequest
# current_user - which user use # current_user - which user use
# params: # params:
# scope: 'created-by-me' or 'assigned-to-me' or 'all' # scope: 'created_by_me' or 'assigned_to_me' or 'all'
# state: 'opened' or 'closed' or 'all' # state: 'opened' or 'closed' or 'all'
# group_id: integer # group_id: integer
# project_id: integer # project_id: integer
...@@ -282,9 +282,9 @@ class IssuableFinder ...@@ -282,9 +282,9 @@ class IssuableFinder
return items.none if current_user_related? && !current_user return items.none if current_user_related? && !current_user
case params[:scope] case params[:scope]
when 'created-by-me', 'authored' when 'created_by_me', 'authored'
items.where(author_id: current_user.id) items.where(author_id: current_user.id)
when 'assigned-to-me' when 'assigned_to_me'
items.assigned_to(current_user) items.assigned_to(current_user)
else else
items items
...@@ -426,6 +426,7 @@ class IssuableFinder ...@@ -426,6 +426,7 @@ class IssuableFinder
end end
def current_user_related? def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' scope = params[:scope]
scope == 'created_by_me' || scope == 'authored' || scope == 'assigned_to_me'
end end
end end
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
# Arguments: # Arguments:
# current_user - which user use # current_user - which user use
# params: # params:
# scope: 'created-by-me' or 'assigned-to-me' or 'all' # scope: 'created_by_me' or 'assigned_to_me' or 'all'
# state: 'open' or 'closed' or 'all' # state: 'open' or 'closed' or 'all'
# group_id: integer # group_id: integer
# project_id: integer # project_id: integer
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
# Arguments: # Arguments:
# current_user - which user use # current_user - which user use
# params: # params:
# scope: 'created-by-me' or 'assigned-to-me' or 'all' # scope: 'created_by_me' or 'assigned_to_me' or 'all'
# state: 'open', 'closed', 'merged', or 'all' # state: 'open', 'closed', 'merged', or 'all'
# group_id: integer # group_id: integer
# project_id: integer # project_id: integer
......
...@@ -13,7 +13,7 @@ class PersonalProjectsFinder < UnionFinder ...@@ -13,7 +13,7 @@ class PersonalProjectsFinder < UnionFinder
def execute(current_user = nil) def execute(current_user = nil)
segments = all_projects(current_user) segments = all_projects(current_user)
find_union(segments, Project).includes(:namespace).order_id_desc find_union(segments, Project).includes(:namespace).order_updated_desc
end end
private private
......
...@@ -184,7 +184,7 @@ module Ci ...@@ -184,7 +184,7 @@ module Ci
end end
def playable? def playable?
action? && (manual? || complete?) action? && (manual? || retryable?)
end end
def action? def action?
...@@ -599,6 +599,7 @@ module Ci ...@@ -599,6 +599,7 @@ module Ci
break variables unless persisted? break variables unless persisted?
variables variables
.concat(pipeline.persisted_variables)
.append(key: 'CI_JOB_ID', value: id.to_s) .append(key: 'CI_JOB_ID', value: id.to_s)
.append(key: 'CI_JOB_TOKEN', value: token, public: false) .append(key: 'CI_JOB_TOKEN', value: token, public: false)
.append(key: 'CI_BUILD_ID', value: id.to_s) .append(key: 'CI_BUILD_ID', value: id.to_s)
...@@ -661,7 +662,7 @@ module Ci ...@@ -661,7 +662,7 @@ module Ci
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless gitlab_deploy_token break variables unless gitlab_deploy_token
variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.name) variables.append(key: 'CI_DEPLOY_USER', value: gitlab_deploy_token.username)
variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false) variables.append(key: 'CI_DEPLOY_PASSWORD', value: gitlab_deploy_token.token, public: false)
end end
end end
......
...@@ -406,7 +406,18 @@ module Ci ...@@ -406,7 +406,18 @@ module Ci
end end
def has_warnings? def has_warnings?
builds.latest.failed_but_allowed.any? number_of_warnings.positive?
end
def number_of_warnings
BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader|
Build.where(commit_id: pipeline_ids)
.latest
.failed_but_allowed
.group(:commit_id)
.count
.each { |id, amount| loader.call(id, amount) }
end
end end
def set_config_source def set_config_source
...@@ -512,9 +523,14 @@ module Ci ...@@ -512,9 +523,14 @@ module Ci
strong_memoize(:legacy_trigger) { trigger_requests.first } strong_memoize(:legacy_trigger) { trigger_requests.first }
end end
def persisted_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_PIPELINE_ID', value: id.to_s) if persisted?
end
end
def predefined_variables def predefined_variables
Gitlab::Ci::Variables::Collection.new Gitlab::Ci::Variables::Collection.new
.append(key: 'CI_PIPELINE_ID', value: id.to_s)
.append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path) .append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s) .append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message) .append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message)
......
...@@ -75,7 +75,7 @@ module Ci ...@@ -75,7 +75,7 @@ module Ci
project_type: 3 project_type: 3
} }
cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address cached_attr_reader :version, :revision, :platform, :architecture, :ip_address, :contacted_at
chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout
......
...@@ -49,6 +49,11 @@ module Clusters ...@@ -49,6 +49,11 @@ module Clusters
# ensures headers containing auth data are appended to original k8s client options # ensures headers containing auth data are appended to original k8s client options
options = kube_client.rest_client.options.merge(headers: kube_client.headers) options = kube_client.rest_client.options.merge(headers: kube_client.headers)
RestClient::Resource.new(proxy_url, options) RestClient::Resource.new(proxy_url, options)
rescue Kubeclient::HttpError
# If users have mistakenly set parameters or removed the depended clusters,
# `proxy_url` could raise an exception because gitlab can not communicate with the cluster.
# Since `PrometheusAdapter#can_query?` is eargely loaded on environement pages in gitlab,
# we need to silence the exceptions
end end
private private
......
...@@ -224,8 +224,34 @@ class Commit ...@@ -224,8 +224,34 @@ class Commit
Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message)
end end
def lazy_author
BatchLoader.for(author_email.downcase).batch do |emails, loader|
# A Hash that maps user Emails to the corresponding User objects. The
# Emails at this point are the _primary_ Emails of the Users.
users_for_emails = User
.by_any_email(emails)
.each_with_object({}) { |user, hash| hash[user.email] = user }
users_for_ids = users_for_emails
.values
.each_with_object({}) { |user, hash| hash[user.id] = user }
# Some commits may have used an alternative Email address. In this case we
# need to query the "emails" table to map those addresses to User objects.
Email
.where(email: emails - users_for_emails.keys)
.pluck(:email, :user_id)
.each { |(email, id)| users_for_emails[email] = users_for_ids[id] }
users_for_emails.each { |email, user| loader.call(email, user) }
end
end
def author def author
User.find_by_any_email(author_email.downcase) # We use __sync so that we get the actual objects back (including an actual
# nil), instead of a wrapper, as returning a wrapped nil breaks a lot of
# code.
lazy_author.__sync
end end
request_cache(:author) { author_email.downcase } request_cache(:author) { author_email.downcase }
......
...@@ -2,6 +2,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -2,6 +2,7 @@ class CommitStatus < ActiveRecord::Base
include HasStatus include HasStatus
include Importable include Importable
include AfterCommitQueue include AfterCommitQueue
include Presentable
self.table_name = 'ci_builds' self.table_name = 'ci_builds'
......
...@@ -7,7 +7,11 @@ module RedisCacheable ...@@ -7,7 +7,11 @@ module RedisCacheable
class_methods do class_methods do
def cached_attr_reader(*attributes) def cached_attr_reader(*attributes)
attributes.each do |attribute| attributes.each do |attribute|
define_method("#{attribute}") do define_method(attribute) do
unless self.has_attribute?(attribute)
raise ArgumentError, "`cached_attr_reader` requires the #{self.class.name}\##{attribute} attribute to have a database column"
end
cached_attribute(attribute) || read_attribute(attribute) cached_attribute(attribute) || read_attribute(attribute)
end end
end end
...@@ -15,13 +19,16 @@ module RedisCacheable ...@@ -15,13 +19,16 @@ module RedisCacheable
end end
def cached_attribute(attribute) def cached_attribute(attribute)
(cached_attributes || {})[attribute] cached_value = (cached_attributes || {})[attribute]
cast_value_from_cache(attribute, cached_value) if cached_value
end end
def cache_attributes(values) def cache_attributes(values)
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME) redis.set(cache_attribute_key, values.to_json, ex: CACHED_ATTRIBUTES_EXPIRY_TIME)
end end
clear_memoization(:cached_attributes)
end end
private private
...@@ -38,4 +45,12 @@ module RedisCacheable ...@@ -38,4 +45,12 @@ module RedisCacheable
end end
end end
end end
def cast_value_from_cache(attribute, value)
if Gitlab.rails5?
self.class.type_for_attribute(attribute).cast(value)
else
self.class.column_for_attribute(attribute).type_cast_from_database(value)
end
end
end end
...@@ -12,8 +12,8 @@ module Sortable ...@@ -12,8 +12,8 @@ module Sortable
scope :order_created_asc, -> { reorder(created_at: :asc) } scope :order_created_asc, -> { reorder(created_at: :asc) }
scope :order_updated_desc, -> { reorder(updated_at: :desc) } scope :order_updated_desc, -> { reorder(updated_at: :desc) }
scope :order_updated_asc, -> { reorder(updated_at: :asc) } scope :order_updated_asc, -> { reorder(updated_at: :asc) }
scope :order_name_asc, -> { reorder(name: :asc) } scope :order_name_asc, -> { reorder("lower(name) asc") }
scope :order_name_desc, -> { reorder(name: :desc) } scope :order_name_desc, -> { reorder("lower(name) desc") }
end end
module ClassMethods module ClassMethods
......
...@@ -53,6 +53,10 @@ module TimeTrackable ...@@ -53,6 +53,10 @@ module TimeTrackable
Gitlab::TimeTrackingFormatter.output(time_estimate) Gitlab::TimeTrackingFormatter.output(time_estimate)
end end
def time_estimate=(val)
val.is_a?(Integer) ? super([val, Gitlab::Database::MAX_INT_VALUE].min) : super(val)
end
private private
def touchable? def touchable?
......
...@@ -997,7 +997,7 @@ class Project < ActiveRecord::Base ...@@ -997,7 +997,7 @@ class Project < ActiveRecord::Base
available_services_names = Service.available_services_names - exceptions available_services_names = Service.available_services_names - exceptions
available_services_names.map do |service_name| available_services = available_services_names.map do |service_name|
service = find_service(services, service_name) service = find_service(services, service_name)
if service if service
...@@ -1014,6 +1014,14 @@ class Project < ActiveRecord::Base ...@@ -1014,6 +1014,14 @@ class Project < ActiveRecord::Base
end end
end end
end end
available_services.reject do |service|
disabled_services.include?(service.to_param)
end
end
def disabled_services
[]
end end
def find_or_initialize_service(name) def find_or_initialize_service(name)
......
module Ci module Ci
class BuildPresenter < Gitlab::View::Presenter::Delegated class BuildPresenter < CommitStatusPresenter
CALLOUT_FAILURE_MESSAGES = {
unknown_failure: 'There is an unknown failure, please try again',
script_failure: 'There has been a script failure. Check the job log for more information',
api_failure: 'There has been an API failure, please try again',
stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again',
runner_system_failure: 'There has been a runner system failure, please try again',
missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information'
}.freeze
presents :build
def erased_by_user? def erased_by_user?
# Build can be erased through API, therefore it does not have # Build can be erased through API, therefore it does not have
# `erased_by` user assigned in that case. # `erased_by` user assigned in that case.
...@@ -44,14 +33,6 @@ module Ci ...@@ -44,14 +33,6 @@ module Ci
"#{subject.name} - #{detailed_status.status_tooltip}" "#{subject.name} - #{detailed_status.status_tooltip}"
end end
def callout_failure_message
CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym]
end
def recoverable?
failed? && !unrecoverable?
end
private private
def tooltip_for_badge def tooltip_for_badge
...@@ -61,9 +42,5 @@ module Ci ...@@ -61,9 +42,5 @@ module Ci
def detailed_status def detailed_status
@detailed_status ||= subject.detailed_status(user) @detailed_status ||= subject.detailed_status(user)
end end
def unrecoverable?
script_failure? || missing_dependency_failure?
end
end end
end end
class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
CALLOUT_FAILURE_MESSAGES = {
unknown_failure: 'There is an unknown failure, please try again',
script_failure: 'There has been a script failure. Check the job log for more information',
api_failure: 'There has been an API failure, please try again',
stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again',
runner_system_failure: 'There has been a runner system failure, please try again',
missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information'
}.freeze
presents :build
def callout_failure_message
CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym]
end
def recoverable?
failed? && !unrecoverable?
end
def unrecoverable?
script_failure? || missing_dependency_failure?
end
end
class GenericCommitStatusPresenter < CommitStatusPresenter
end
...@@ -4,7 +4,11 @@ class PipelineEntity < Grape::Entity ...@@ -4,7 +4,11 @@ class PipelineEntity < Grape::Entity
expose :id expose :id
expose :user, using: UserEntity expose :user, using: UserEntity
expose :active?, as: :active expose :active?, as: :active
expose :coverage
# Coverage isn't always necessary (e.g. when displaying project pipelines in
# the UI). Instead of creating an entirely different entity we just allow the
# disabling of this specific field whenever necessary.
expose :coverage, unless: proc { options[:disable_coverage] }
expose :source expose :source
expose :created_at, :updated_at expose :created_at, :updated_at
......
...@@ -2,7 +2,7 @@ module Keys ...@@ -2,7 +2,7 @@ module Keys
class BaseService class BaseService
attr_accessor :user, :params attr_accessor :user, :params
def initialize(user, params) def initialize(user, params = {})
@user, @params = user, params @user, @params = user, params
@ip_address = @params.delete(:ip_address) @ip_address = @params.delete(:ip_address)
end end
......
module Keys
class DestroyService < ::Keys::BaseService
def execute(key)
key.destroy if destroy_possible?(key)
end
# overriden in EE::Keys::DestroyService
def destroy_possible?(key)
true
end
end
end
...@@ -2,14 +2,14 @@ module Lfs ...@@ -2,14 +2,14 @@ module Lfs
class UnlockFileService < BaseService class UnlockFileService < BaseService
def execute def execute
unless can?(current_user, :push_code, project) unless can?(current_user, :push_code, project)
raise Gitlab::GitAccess::UnauthorizedError, 'You have no permissions' raise Gitlab::GitAccess::UnauthorizedError, _('You have no permissions')
end end
unlock_file unlock_file
rescue Gitlab::GitAccess::UnauthorizedError => ex rescue Gitlab::GitAccess::UnauthorizedError => ex
error(ex.message, 403) error(ex.message, 403)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
error('Lock not found', 404) error(_('Lock not found'), 404)
rescue => ex rescue => ex
error(ex.message, 500) error(ex.message, 500)
end end
...@@ -24,9 +24,9 @@ module Lfs ...@@ -24,9 +24,9 @@ module Lfs
success(lock: lock, http_status: :ok) success(lock: lock, http_status: :ok)
elsif forced elsif forced
error('You must have master access to force delete a lock', 403) error(_('You must have master access to force delete a lock'), 403)
else else
error("#{lock.path} is locked by GitLab User #{lock.user_id}", 403) error(_("%{lock_path} is locked by GitLab User %{lock_user_id}") % { lock_path: lock.path, lock_user_id: lock.user_id }, 403)
end end
end end
......
...@@ -5,6 +5,7 @@ module Milestones ...@@ -5,6 +5,7 @@ module Milestones
def initialize(parent, user, params = {}) def initialize(parent, user, params = {})
@parent, @current_user, @params = parent, user, params.dup @parent, @current_user, @params = parent, user, params.dup
super
end end
end end
end end
...@@ -27,11 +27,11 @@ ...@@ -27,11 +27,11 @@
.col-sm-10 .col-sm-10
= f.color_field :font, class: "form-control" = f.color_field :font, class: "form-control"
.form-group.row .form-group.row
= f.label :starts_at, class: 'col-form-label col-sm-2' = f.label :starts_at, _("Starts at (UTC)"), class: 'col-form-label col-sm-2'
.col-sm-10.datetime-controls .col-sm-10.datetime-controls
= f.datetime_select :starts_at, {}, class: 'form-control form-control-inline' = f.datetime_select :starts_at, {}, class: 'form-control form-control-inline'
.form-group.row .form-group.row
= f.label :ends_at, class: 'col-form-label col-sm-2' = f.label :ends_at, _("Ends at (UTC)"), class: 'col-form-label col-sm-2'
.col-sm-10.datetime-controls .col-sm-10.datetime-controls
= f.datetime_select :ends_at, {}, class: 'form-control form-control-inline' = f.datetime_select :ends_at, {}, class: 'form-control form-control-inline'
.form-actions .form-actions
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
= tag = tag
%td %td
- if runner.contacted_at - if runner.contacted_at
#{time_ago_in_words(runner.contacted_at)} ago = time_ago_with_tooltip runner.contacted_at
- else - else
Never Never
%td.admin-runner-btn-group-cell %td.admin-runner-btn-group-cell
......
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
.key e .key v
%td %td
Go to the project's activity feed Go to the project's activity feed
%tr %tr
...@@ -172,6 +172,18 @@ ...@@ -172,6 +172,18 @@
.key m .key m
%td %td
Go to merge requests Go to merge requests
%tr
%td.shortcut
.key g
.key e
%td
Go to environments
%tr
%td.shortcut
.key g
.key k
%td
Go to kubernetes
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
...@@ -219,6 +231,17 @@ ...@@ -219,6 +231,17 @@
%td.shortcut %td.shortcut
.key y .key y
%td Go to file permalink %td Go to file permalink
%tbody
%tr
%th
%th Web IDE
%tr
%td.shortcut
- if browser.platform.mac?
.key &#8984; p
- else
.key ctrl p
%td Go to file
.col-lg-4 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody.hidden-shortcut.network{ style: 'display:none' } %tbody.hidden-shortcut.network{ style: 'display:none' }
......
...@@ -154,7 +154,7 @@ ...@@ -154,7 +154,7 @@
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do
= link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines' do
.nav-icon-container .nav-icon-container
= sprite_icon('pipeline') = sprite_icon('rocket')
%span.nav-item-name %span.nav-item-name
= _('CI / CD') = _('CI / CD')
...@@ -212,7 +212,7 @@ ...@@ -212,7 +212,7 @@
- if project_nav_tab? :clusters - if project_nav_tab? :clusters
- show_cluster_hint = show_gke_cluster_integration_callout?(@project) - show_cluster_hint = show_gke_cluster_integration_callout?(@project)
= nav_link(controller: [:clusters, :user, :gcp]) do = nav_link(controller: [:clusters, :user, :gcp]) do
= link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-cluster' do = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do
%span %span
= _('Kubernetes') = _('Kubernetes')
- if show_cluster_hint - if show_cluster_hint
......
File mode changed from 100755 to 100644
...@@ -66,6 +66,6 @@ ...@@ -66,6 +66,6 @@
%td Last contact %td Last contact
%td %td
- if @runner.contacted_at - if @runner.contacted_at
#{time_ago_in_words(@runner.contacted_at)} ago = time_ago_with_tooltip @runner.contacted_at
- else - else
Never Never
---
title: Fix double-brackets being linkified in wiki markdown
merge_request: 18524
author: brewingcode
type: fixed
---
title: Add API endpoint to render markdown text
merge_request: 18926
author: "@blackst0ne"
type: added
---
title: Apply NestingDepth (level 5) (pages/pipelines.scss)
merge_request: 18830
author: Takuya Noguchi
type: other
---
title: Rename issue scope created-by-me to created_by_me, and assigned-to-me to assigned_to_me
merge_request: 44799
author:
type: deprecated
---
title: Order UsersController#projects.json by updated_at
merge_request: 18227
author: Takuya Noguchi
type: other
---
title: Fix unscrollable Markdown preview of WebIDE on Firefox
merge_request:
author:
type: fixed
---
title: Fix Runner contacted at tooltip cache.
merge_request: 18810
author:
type: fixed
---
title: Allow CommitStatus class to use presentable methods
merge_request: 18979
author:
type: fixed
---
title: Fixes 500 error on /estimate BIG_VALUE
merge_request: 18964
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Adds keyboard shortcut `g e` for Environments on Project pages
merge_request: 19002
author:
type: added
---
title: Adds keyboard shortcut `g k` for Kubernetes on Project pages
merge_request: 19002
author:
type: added
---
title: Changes keyboard shortcut of Activity feed to `g v`
merge_request: 19002
author:
type: changed
---
title: Removes outdated `g t` shortcut for TODO in favor of `Shift+T`
merge_request: 19002
author:
type: removed
---
title: Fixes deploy token variables on Ci::Build
merge_request: 19047
author:
type: fixed
title: Add anchor for incoming email regex
merge_request: !18917
type: added
---
title: Forbid to patch traces for finished jobs
merge_request: 18969
author:
type: fixed
---
title: Add support for variables expression pattern matching syntax
merge_request: 18902
author:
type: added
---
title: Wrapping problem on the issues page has been fixed
merge_request:
author:
type: fixed
---
title: Exclude CI_PIPELINE_ID from variables supported in dynamic environment name
merge_request: 19032
author:
type: fixed
---
title: Do not allow to trigger manual actions that were skipped
merge_request: 18985
author:
type: fixed
---
title: Fix corrupted environment pages with unathorized proxy url
merge_request: 18989
author:
type: fixed
---
title: Add shortcuts to Web IDE docs and modal
merge_request: 19044
author:
type: changed
---
title: Memoize Gitlab::Database.version
merge_request:
author:
type: performance
---
title: Improve performance of project pipelines pages
merge_request:
author:
type: performance
---
title: Fix api_json.log not always reporting the right HTTP status code
merge_request:
author:
type: fixed
---
title: Move API group deletion to Sidekiq
merge_request:
author:
type: changed
---
title: "Use case in-sensitive ordering by name for dashboard"
merge_request: 18553
author: "@vedharish"
type: fixed
---
title: Remove shellout implementation for Repository checksums
merge_request:
author:
type: other
---
title: Workhorse will use Gitaly to create archives
merge_request:
author:
type: other
if Gitlab.dev_env_or_com? if Rails.env.development? || ENV['GITLAB_LEGACY_PATH_LOG_MESSAGE']
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab') deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
deprecator.behavior = -> (message, callstack) { deprecator.behavior = -> (message, callstack) {
......
require 'flipper/adapters/active_record'
require 'flipper/adapters/active_support_cache_store'
Flipper.configure do |config|
config.default do
adapter = Flipper::Adapters::ActiveRecord.new(
feature_class: Feature::FlipperFeature, gate_class: Feature::FlipperGate)
cached_adapter = Flipper::Adapters::ActiveSupportCacheStore.new(
adapter,
Rails.cache,
expires_in: 1.hour)
Flipper.new(cached_adapter)
end
end
Feature.register_feature_groups Feature.register_feature_groups
unless Rails.env.test?
require 'flipper/middleware/memoizer'
Rails.application.config.middleware.use Flipper::Middleware::Memoizer
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class ProjectNameLowerIndex < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
INDEX_NAME = 'index_projects_on_lower_name'
disable_ddl_transaction!
def up
return unless Gitlab::Database.postgresql?
disable_statement_timeout
execute "CREATE INDEX CONCURRENTLY #{INDEX_NAME} ON projects (LOWER(name))"
end
def down
return unless Gitlab::Database.postgresql?
disable_statement_timeout
if supports_drop_index_concurrently?
execute "DROP INDEX CONCURRENTLY IF EXISTS #{INDEX_NAME}"
else
execute "DROP INDEX IF EXISTS #{INDEX_NAME}"
end
end
end
# GitLab Prometheus metrics # GitLab Prometheus metrics
>**Note:** >**Note:**
Available since [Omnibus GitLab 9.3][29118]. Currently experimental. For Available since [Omnibus GitLab 9.3][29118]. For
installations from source you'll have to configure it yourself. installations from source you'll have to configure it yourself.
To enable the GitLab Prometheus metrics: To enable the GitLab Prometheus metrics:
...@@ -24,7 +24,7 @@ server, because the embedded server configuration is overwritten once every ...@@ -24,7 +24,7 @@ server, because the embedded server configuration is overwritten once every
## Metrics available ## Metrics available
In this experimental phase, only a few metrics are available: The following metrics are available:
| Metric | Type | Since | Description | | Metric | Type | Since | Description |
|:--------------------------------- |:--------- |:----- |:----------- | |:--------------------------------- |:--------- |:----- |:----------- |
......
...@@ -120,7 +120,7 @@ To disable the monitoring of Kubernetes: ...@@ -120,7 +120,7 @@ To disable the monitoring of Kubernetes:
## GitLab Prometheus metrics ## GitLab Prometheus metrics
> Introduced as an experimental feature in GitLab 9.3. > Introduced in GitLab 9.3.
GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic. GitLab monitors its own internal service metrics, and makes them available at the `/-/metrics` endpoint. Unlike other exporters, this endpoint requires authentication as it is available on the same URL and port as user traffic.
......
...@@ -33,6 +33,7 @@ following locations: ...@@ -33,6 +33,7 @@ following locations:
- [Jobs](jobs.md) - [Jobs](jobs.md)
- [Keys](keys.md) - [Keys](keys.md)
- [Labels](labels.md) - [Labels](labels.md)
- [Markdown](markdown.md)
- [Merge Requests](merge_requests.md) - [Merge Requests](merge_requests.md)
- [Project milestones](milestones.md) - [Project milestones](milestones.md)
- [Group milestones](group_milestones.md) - [Group milestones](group_milestones.md)
......
...@@ -487,6 +487,9 @@ Parameters: ...@@ -487,6 +487,9 @@ Parameters:
- `id` (required) - The ID or path of a user group - `id` (required) - The ID or path of a user group
This will queue a background job to delete all projects in the group. The
response will be a 202 Accepted if the user has authorization.
## Search for group ## Search for group
Get all groups that match your string in their name or path. Get all groups that match your string in their name or path.
......
...@@ -38,8 +38,8 @@ GET /issues?my_reaction_emoji=star ...@@ -38,8 +38,8 @@ GET /issues?my_reaction_emoji=star
| `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string | no | The milestone title | | `milestone` | string | no | The milestone title |
| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` _([Introduced][ce-13004] in GitLab 9.5)_ | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
...@@ -152,7 +152,7 @@ GET /groups/:id/issues?my_reaction_emoji=star ...@@ -152,7 +152,7 @@ GET /groups/:id/issues?my_reaction_emoji=star
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `iids[]` | Array[integer] | no | Return only the issues having the given `iid` | | `iids[]` | Array[integer] | no | Return only the issues having the given `iid` |
| `milestone` | string | no | The milestone title | | `milestone` | string | no | The milestone title |
| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
...@@ -266,7 +266,7 @@ GET /projects/:id/issues?my_reaction_emoji=star ...@@ -266,7 +266,7 @@ GET /projects/:id/issues?my_reaction_emoji=star
| `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `state` | string | no | Return all issues or just those that are `opened` or `closed` |
| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels |
| `milestone` | string | no | The milestone title | | `milestone` | string | no | The milestone title |
| `scope` | string | no | Return issues for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13004] in GitLab 9.5)_ | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `author_id` | integer | no | Return issues created by the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
...@@ -1254,3 +1254,4 @@ Example response: ...@@ -1254,3 +1254,4 @@ Example response:
[ce-13004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13004 [ce-13004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13004
[ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016
[ce-17042]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17042 [ce-17042]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17042
[ce-18935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18935
# Markdown API
> [Introduced][ce-18926] in GitLab 11.0.
Available only in APIv4.
## Render an arbitrary Markdown document
```
POST /api/v4/markdown
```
| Attribute | Type | Required | Description |
| --------- | ------- | ------------- | ------------------------------------------ |
| `text` | string | yes | The markdown text to render |
| `gfm` | boolean | no (optional) | Render text using GitLab Flavored Markdown. Default is `false` |
| `project` | string | no (optional) | Use `project` as a context when creating references using GitLab Flavored Markdown. [Authentication](README.html#authentication) is required if a project is not public. |
```bash
curl --header Content-Type:application/json --data '{"text":"Hello world! :tada:", "gfm":true, "project":"group_example/project_example"}' https://gitlab.example.com/api/v4/markdown
```
Response example:
```json
{ "html": "<p dir=\"auto\">Hello world! <gl-emoji title=\"party popper\" data-name=\"tada\" data-unicode-version=\"6.0\">🎉</gl-emoji></p>" }
```
[ce-18926]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18926
...@@ -28,7 +28,7 @@ GET /merge_requests?milestone=release ...@@ -28,7 +28,7 @@ GET /merge_requests?milestone=release
GET /merge_requests?labels=bug,reproduced GET /merge_requests?labels=bug,reproduced
GET /merge_requests?author_id=5 GET /merge_requests?author_id=5
GET /merge_requests?my_reaction_emoji=star GET /merge_requests?my_reaction_emoji=star
GET /merge_requests?scope=assigned-to-me GET /merge_requests?scope=assigned_to_me
``` ```
Parameters: Parameters:
...@@ -45,8 +45,8 @@ Parameters: ...@@ -45,8 +45,8 @@ Parameters:
| `created_before` | datetime | no | Return merge requests created on or before the given time | | `created_before` | datetime | no | Return merge requests created on or before the given time |
| `updated_after` | datetime | no | Return merge requests updated on or after the given time | | `updated_after` | datetime | no | Return merge requests updated on or after the given time |
| `updated_before` | datetime | no | Return merge requests updated on or before the given time | | `updated_before` | datetime | no | Return merge requests updated on or before the given time |
| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` | | `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead. |
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` | | `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me` |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
| `source_branch` | string | no | Return merge requests with the given source branch | | `source_branch` | string | no | Return merge requests with the given source branch |
...@@ -164,7 +164,7 @@ Parameters: ...@@ -164,7 +164,7 @@ Parameters:
| `created_before` | datetime | no | Return merge requests created on or before the given time | | `created_before` | datetime | no | Return merge requests created on or before the given time |
| `updated_after` | datetime | no | Return merge requests updated on or after the given time | | `updated_after` | datetime | no | Return merge requests updated on or after the given time |
| `updated_before` | datetime | no | Return merge requests updated on or before the given time | | `updated_before` | datetime | no | Return merge requests updated on or before the given time |
| `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ | | `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13060] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ | | `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji` _([Introduced][ce-14016] in GitLab 10.0)_ |
...@@ -1460,3 +1460,4 @@ Example response: ...@@ -1460,3 +1460,4 @@ Example response:
[ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060 [ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060
[ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016
[ce-15454]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15454 [ce-15454]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15454
[ce-18935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18935
...@@ -252,6 +252,7 @@ including predefined, secure variables and `.gitlab-ci.yml` ...@@ -252,6 +252,7 @@ including predefined, secure variables and `.gitlab-ci.yml`
[`variables`](yaml/README.md#variables). You however cannot use variables [`variables`](yaml/README.md#variables). You however cannot use variables
defined under `script` or on the Runner's side. There are other variables that defined under `script` or on the Runner's side. There are other variables that
are unsupported in environment name context: are unsupported in environment name context:
- `CI_PIPELINE_ID`
- `CI_JOB_ID` - `CI_JOB_ID`
- `CI_JOB_TOKEN` - `CI_JOB_TOKEN`
- `CI_BUILD_ID` - `CI_BUILD_ID`
......
...@@ -530,6 +530,16 @@ Below you can find supported syntax reference: ...@@ -530,6 +530,16 @@ Below you can find supported syntax reference:
`$STAGING` value needs to a string, with length higher than zero. `$STAGING` value needs to a string, with length higher than zero.
Variable that contains only whitespace characters is not an empty variable. Variable that contains only whitespace characters is not an empty variable.
1. Pattern matching _(added in 11.0)_
> Example: `$VARIABLE =~ /^content.*/`
It is possible perform pattern matching against a variable and regular
expression. Expression like this evaluates to truth if matches are found.
Pattern matching is case-sensitive by default. Use `i` flag modifier, like
`/pattern/i` to make a pattern case-insensitive.
### Unsupported predefined variables ### Unsupported predefined variables
Because GitLab evaluates variables before creating jobs, we do not support a Because GitLab evaluates variables before creating jobs, we do not support a
...@@ -543,6 +553,7 @@ We do not support variables containing tokens because of security reasons. ...@@ -543,6 +553,7 @@ We do not support variables containing tokens because of security reasons.
You can find a full list of unsupported variables below: You can find a full list of unsupported variables below:
- `CI_PIPELINE_ID`
- `CI_JOB_ID` - `CI_JOB_ID`
- `CI_JOB_TOKEN` - `CI_JOB_TOKEN`
- `CI_BUILD_ID` - `CI_BUILD_ID`
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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