Commit f61666c0 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into per-project-pipeline-iid

parents 1c636b80 c6f72ac9
...@@ -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:
...@@ -189,7 +206,7 @@ stages: ...@@ -189,7 +206,7 @@ stages:
<<: *dedicated-no-docs-and-no-qa-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
<<: *use-pg <<: *use-pg
variables: variables:
CREATE_DB_USER: "true" SETUP_DB: "false"
script: script:
# Manually clone gitlab-test and only seed this project in # Manually clone gitlab-test and only seed this project in
# db/fixtures/development/04_project.rb thanks to SIZE=1 below # db/fixtures/development/04_project.rb thanks to SIZE=1 below
...@@ -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
...@@ -233,7 +241,7 @@ stages: ...@@ -233,7 +241,7 @@ stages:
.migration-paths: &migration-paths .migration-paths: &migration-paths
<<: *dedicated-no-docs-and-no-qa-pull-cache-job <<: *dedicated-no-docs-and-no-qa-pull-cache-job
variables: variables:
CREATE_DB_USER: "true" SETUP_DB: "false"
script: script:
- git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0 - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0
- git checkout -f FETCH_HEAD - git checkout -f FETCH_HEAD
...@@ -242,7 +250,7 @@ stages: ...@@ -242,7 +250,7 @@ stages:
- cp config/gitlab.yml.example config/gitlab.yml - cp config/gitlab.yml.example config/gitlab.yml
- bundle exec rake db:drop db:create db:schema:load db:seed_fu - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- date - date
- git checkout $CI_COMMIT_SHA - git checkout -f $CI_COMMIT_SHA
- bundle install $BUNDLE_INSTALL_FLAGS - bundle install $BUNDLE_INSTALL_FLAGS
- date - date
- . scripts/prepare_build.sh - . scripts/prepare_build.sh
...@@ -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}
......
This diff is collapsed.
...@@ -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
......
...@@ -174,6 +174,9 @@ gem 'httparty', '~> 0.13.3' ...@@ -174,6 +174,9 @@ gem 'httparty', '~> 0.13.3'
# Colored output to console # Colored output to console
gem 'rainbow', '~> 2.2' gem 'rainbow', '~> 2.2'
# Progress bar
gem 'ruby-progressbar'
# GitLab settings # GitLab settings
gem 'settingslogic', '~> 2.0.9' gem 'settingslogic', '~> 2.0.9'
......
...@@ -1150,6 +1150,7 @@ DEPENDENCIES ...@@ -1150,6 +1150,7 @@ DEPENDENCIES
rubocop-rspec (~> 1.22.1) rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.17.0) ruby-prof (~> 0.17.0)
ruby-progressbar
ruby_parser (~> 3.8) ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4) rufus-scheduler (~> 3.4)
rugged (~> 0.27) rugged (~> 0.27)
......
10.8.0-pre 11.0.0-pre
...@@ -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;
......
...@@ -93,10 +93,13 @@ export default { ...@@ -93,10 +93,13 @@ export default {
v-html="actionTextHtml" v-html="actionTextHtml"
class="system-note-message"> class="system-note-message">
</span> </span>
<span class="system-note-separator">
&middot;
</span>
<a <a
:href="noteTimestampLink" :href="noteTimestampLink"
@click="updateTargetNoteHash" @click="updateTargetNoteHash"
class="note-timestamp"> class="note-timestamp system-note-separator">
<time-ago-tooltip <time-ago-tooltip
:time="createdAt" :time="createdAt"
tooltip-placement="bottom" tooltip-placement="bottom"
......
<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');
......
...@@ -40,7 +40,7 @@ export default { ...@@ -40,7 +40,7 @@ export default {
:class="cssClass" :class="cssClass"
:title="tooltipTitle(time)" :title="tooltipTitle(time)"
:data-placement="tooltipPlacement" :data-placement="tooltipPlacement"
data-container="body"> data-container="body"
{{ timeFormated(time) }} v-text="timeFormated(time)">
</time> </time>
</template> </template>
...@@ -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;
}
}
@import './issues/issue_count_badge';
[v-cloak] { [v-cloak] {
display: none; display: none;
} }
......
...@@ -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;
} }
} }
} }
......
@import "./issues/issue_count_badge";
.issues-list { .issues-list {
.issue { .issue {
padding: 10px 0 10px $gl-padding; padding: 10px 0 10px $gl-padding;
......
...@@ -455,6 +455,10 @@ ul.notes { ...@@ -455,6 +455,10 @@ ul.notes {
white-space: normal; white-space: normal;
} }
.system-note-separator {
color: $gl-text-color-disabled;
}
a:hover { a:hover {
text-decoration: underline; text-decoration: underline;
} }
......
...@@ -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?,
......
module AcceptsPendingInvitations
extend ActiveSupport::Concern
def accept_pending_invitations
return unless resource.active_for_authentication?
clear_stored_location_for_resource if resource.accept_pending_invitations!.any?
end
def clear_stored_location_for_resource
session_key = stored_location_key_for(resource)
session.delete(session_key)
end
end
class ConfirmationsController < Devise::ConfirmationsController class ConfirmationsController < Devise::ConfirmationsController
include AcceptsPendingInvitations
def almost_there def almost_there
flash[:notice] = nil flash[:notice] = nil
render layout: "devise_empty" render layout: "devise_empty"
...@@ -11,6 +13,8 @@ class ConfirmationsController < Devise::ConfirmationsController ...@@ -11,6 +13,8 @@ class ConfirmationsController < Devise::ConfirmationsController
end end
def after_confirmation_path_for(resource_name, resource) def after_confirmation_path_for(resource_name, resource)
accept_pending_invitations
# incoming resource can either be a :user or an :email # incoming resource can either be a :user or an :email
if signed_in?(:user) if signed_in?(:user)
after_sign_in(resource) after_sign_in(resource)
......
...@@ -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 }
......
...@@ -23,8 +23,12 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -23,8 +23,12 @@ class Projects::CommitController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.html { render } format.html { render }
format.diff { render text: @commit.to_diff } format.diff do
format.patch { render text: @commit.to_patch } send_git_diff(@project.repository, @commit.diff_refs)
end
format.patch do
send_git_patch(@project.repository, @commit.diff_refs)
end
end end
end end
......
...@@ -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
......
class RegistrationsController < Devise::RegistrationsController class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify include Recaptcha::Verify
include AcceptsPendingInvitations
before_action :whitelist_query_limiting, only: [:destroy] before_action :whitelist_query_limiting, only: [:destroy]
...@@ -16,6 +17,7 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -16,6 +17,7 @@ class RegistrationsController < Devise::RegistrationsController
end end
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
accept_pending_invitations
super super
else else
flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
...@@ -60,7 +62,7 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -60,7 +62,7 @@ class RegistrationsController < Devise::RegistrationsController
def after_sign_up_path_for(user) def after_sign_up_path_for(user)
Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}") Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}")
user.confirmed? ? dashboard_projects_path : users_almost_there_path user.confirmed? ? stored_location_for(user) || dashboard_projects_path : users_almost_there_path
end end
def after_inactive_sign_up_path_for(resource) def after_inactive_sign_up_path_for(resource)
......
...@@ -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
......
...@@ -411,7 +411,18 @@ module Ci ...@@ -411,7 +411,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
...@@ -517,9 +528,14 @@ module Ci ...@@ -517,9 +528,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_PIPELINE_IID', value: iid.to_s) .append(key: 'CI_PIPELINE_IID', value: iid.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)
......
...@@ -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)
......
...@@ -860,6 +860,16 @@ class User < ActiveRecord::Base ...@@ -860,6 +860,16 @@ class User < ActiveRecord::Base
confirmed? && !temp_oauth_email? confirmed? && !temp_oauth_email?
end end
def accept_pending_invitations!
pending_invitations.select do |member|
member.accept_invite!(self)
end
end
def pending_invitations
Member.where(invite_email: verified_emails).invite
end
def all_emails def all_emails
all_emails = [] all_emails = []
all_emails << email unless temp_oauth_email? all_emails << email unless temp_oauth_email?
......
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 .form-group
= f.label :starts_at, class: 'control-label' = f.label :starts_at, _("Starts at (UTC)"), class: 'control-label'
.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 .form-group
= f.label :ends_at, class: 'control-label' = f.label :ends_at, _("Ends at (UTC)"), class: 'control-label'
.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' }
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do = nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
= link_to project_path(@project) do = link_to project_path(@project) do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
= _('Overview') = _('Project')
%li.divider.fly-out-top-item %li.divider.fly-out-top-item
= nav_link(path: 'projects#show') do = nav_link(path: 'projects#show') do
= link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do = link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
...@@ -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
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
by by
= link_to member.created_by.name, user_url(member.created_by) = link_to member.created_by.name, user_url(member.created_by)
to join the to join the
= link_to member_source.human_name, member_source.web_url = link_to member_source.human_name, member_source.public? ? member_source.web_url : invite_url(@token)
#{member_source.model_name.singular} as #{member.human_access}. #{member_source.model_name.singular} as #{member.human_access}.
%p %p
......
File mode changed from 100755 to 100644
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
%br %br
%p %p
- deploy_token = link_to(_('deploy token'), help_page_path('user/project/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank') - deploy_token = link_to(_('deploy token'), help_page_path('user/project/deploy_tokens/index', anchor: 'read-container-registry-images'), target: '_blank')
= s_('ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token } = s_('ContainerRegistry|You can also use a %{deploy_token} for read-only access to the registry images.').html_safe % { deploy_token: deploy_token }
%br %br
%p %p
= s_('ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands').html_safe % { build: "<code>build</code>".html_safe, push: "<code>push</code>".html_safe } = s_('ContainerRegistry|Once you log in, you&rsquo;re free to create and upload a container image using the common %{build} and %{push} commands').html_safe % { build: "<code>build</code>".html_safe, push: "<code>push</code>".html_safe }
......
...@@ -41,8 +41,9 @@ ...@@ -41,8 +41,9 @@
- if note.system - if note.system
%span.system-note-message %span.system-note-message
= markdown_field(note, :note) = markdown_field(note, :note)
%a{ href: "##{dom_id(note)}" } %span.system-note-separator
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') &middot;
%a.system-note-separator{ href: "##{dom_id(note)}" }= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- unless note.system? - unless note.system?
.note-actions .note-actions
- if note.for_personal_snippet? - if note.for_personal_snippet?
......
...@@ -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: Introduce new ProjectCiCdSetting model with group_runners_enabled
merge_request: 18144
author:
type: performance
---
title: Add cron job to email users on issue due date
merge_request: 17985
author: Stuart Nelson
type: added
---
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: Improves wording in new pipeline page
merge_request:
author:
type: other
---
title: Improve tooltips in collapsed right sidebar
merge_request: 17714
author:
type: changed
---
title: Fix `joined` information on project members page
merge_request: 18290
author: Fabian Schneider
type: fixed
---
title: Fix template selector menu visibility when toggling preview mode in file edit
view
merge_request: 18118
author: Fabian Schneider
type: fixed
---
title: Prevent pipeline actions in dropdown to redirct to a new page
merge_request:
author:
type: fixed
---
title: Keep current labels visible when editing them in the sidebar
merge_request:
author:
type: changed
---
title: Reconcile project templates with Auto DevOps
merge_request: 18737
author:
type: changed
--- ---
title: Remove ahead/behind graphs on project branches on mobile title: Apply NestingDepth (level 5) (pages/pipelines.scss)
merge_request: 18415 merge_request: 18830
author: Takuya Noguchi author: Takuya Noguchi
type: other type: other
---
title: Add a comma to the time estimate system notes
merge_request: 18326
author:
type: changed
title: Replace vue resource with axios in pipelines table
merge_request:
author:
type: other
\ No newline at end of file
---
title: Improve DB performance of calculating total artifacts size
merge_request: 17839
author:
type: performance
---
title: Make project deploy keys table more clearly structured
merge_request: 18279
author:
type: changed
---
title: Refactor CSS to eliminate vertical misalignment of login nav
merge_request: 16275
author: Takuya Noguchi
type: fixed
---
title: 'Allow group owner to enable runners from subgroups (#41981)'
merge_request: 18009
author:
type: fixed
---
title: Adds push mirrors to GitLab Community Edition
merge_request: 18715
author:
type: changed
---
title: Automatically accepts project/group invite by email after user signup
merge_request: 17634
author: Jacopo Beschi @jacopo-beschi
type: changed
---
title: Show new branch/mr button even when branch exists
merge_request: 17712
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Fix discussions API setting created_at for notable in a group or notable in
a project in a group with owners
merge_request: 18464
author:
type: fixed
---
title: Reduce queries on merge requests list page for merge requests from forks
merge_request: 18561
author:
type: performance
---
title: Create settings section for autodevops
merge_request: 18321
author:
type: changed
---
title: Add GCP signup offer to cluster index / create pages
merge_request: 18684
author:
type: added
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.
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.
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