summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_bundle.js32
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js191
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue90
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue6
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue28
-rw-r--r--app/assets/javascripts/issue_show/index.js1
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js21
-rw-r--r--app/assets/javascripts/merge_request_tabs.js15
-rw-r--r--app/assets/javascripts/pipelines/components/async_button.vue41
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue87
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue16
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue (renamed from app/assets/javascripts/vue_shared/components/pipelines_table.vue)5
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue (renamed from app/assets/javascripts/vue_shared/components/pipelines_table_row.vue)23
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js103
-rw-r--r--app/assets/stylesheets/pages/issuable.scss30
-rw-r--r--app/assets/stylesheets/pages/note_form.scss36
-rw-r--r--app/helpers/issuables_helper.rb7
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb2
-rw-r--r--app/models/project_services/chat_message/push_message.rb4
-rw-r--r--app/views/admin/application_settings/_form.html.haml4
-rw-r--r--app/views/projects/_md_preview.html.haml6
-rw-r--r--app/views/projects/issues/show.html.haml8
-rw-r--r--changelogs/unreleased/33260-allow-admins-to-list-admins.yml4
-rw-r--r--config/boot.rb4
-rw-r--r--config/karma.config.js9
-rw-r--r--doc/api/README.md2
-rw-r--r--doc/api/users.md3
-rw-r--r--doc/user/project/issues/confidential_issues.md5
-rwxr-xr-xdoc/user/project/issues/img/confidential_issues_issue_page.pngbin90001 -> 14230 bytes
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/gitlab/metrics/prometheus.rb16
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js27
-rw-r--r--spec/javascripts/fixtures/pipelines_table.html.haml1
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js12
-rw-r--r--spec/javascripts/pipelines/async_button_spec.js44
-rw-r--r--spec/javascripts/pipelines/pipelines_actions_spec.js31
-rw-r--r--spec/javascripts/pipelines/pipelines_table_row_spec.js (renamed from spec/javascripts/vue_shared/components/pipelines_table_row_spec.js)2
-rw-r--r--spec/javascripts/pipelines/pipelines_table_spec.js (renamed from spec/javascripts/vue_shared/components/pipelines_table_spec.js)6
-rw-r--r--spec/lib/gitlab/metrics_spec.rb30
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb10
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb16
-rw-r--r--spec/requests/api/users_spec.rb9
-rw-r--r--spec/requests/api/v3/users_spec.rb32
44 files changed, 489 insertions, 539 deletions
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
index 86d99dd87da..c7dc6863160 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
@@ -1,29 +1,25 @@
-/* eslint-disable no-param-reassign */
-
import Vue from 'vue';
-import VueResource from 'vue-resource';
-import CommitPipelinesTable from './pipelines_table';
-
-Vue.use(VueResource);
+import commitPipelinesTable from './pipelines_table.vue';
/**
- * Commits View > Pipelines Tab > Pipelines Table.
- *
- * Renders Pipelines table in pipelines tab in the commits show view.
+ * Used in:
+ * - Commit details View > Pipelines Tab > Pipelines Table.
+ * - Merge Request details View > Pipelines Tab > Pipelines Table.
+ * - New Merge Request View > Pipelines Tab > Pipelines Table.
*/
-// export for use in merge_request_tabs.js (TODO: remove this hack)
-window.gl = window.gl || {};
-window.gl.CommitPipelinesTable = CommitPipelinesTable;
-
-$(() => {
- gl.commits = gl.commits || {};
- gl.commits.pipelines = gl.commits.pipelines || {};
+const CommitPipelinesTable = Vue.extend(commitPipelinesTable);
+document.addEventListener('DOMContentLoaded', () => {
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
- gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount();
- pipelineTableViewEl.appendChild(gl.commits.pipelines.PipelinesTableBundle.$el);
+ const table = new CommitPipelinesTable({
+ propsData: {
+ endpoint: pipelineTableViewEl.dataset.endpoint,
+ helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
+ },
+ }).$mount();
+ pipelineTableViewEl.appendChild(table.$el);
}
});
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
deleted file mode 100644
index 70ba83ce5b9..00000000000
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ /dev/null
@@ -1,191 +0,0 @@
-import Vue from 'vue';
-import Visibility from 'visibilityjs';
-import pipelinesTableComponent from '../../vue_shared/components/pipelines_table.vue';
-import PipelinesService from '../../pipelines/services/pipelines_service';
-import PipelineStore from '../../pipelines/stores/pipelines_store';
-import eventHub from '../../pipelines/event_hub';
-import emptyState from '../../pipelines/components/empty_state.vue';
-import errorState from '../../pipelines/components/error_state.vue';
-import loadingIcon from '../../vue_shared/components/loading_icon.vue';
-import '../../lib/utils/common_utils';
-import '../../vue_shared/vue_resource_interceptor';
-import Poll from '../../lib/utils/poll';
-
-/**
- *
- * Uses `pipelines-table-component` to render Pipelines table with an API call.
- * Endpoint is provided in HTML and passed as `endpoint`.
- * We need a store to store the received environemnts.
- * We need a service to communicate with the server.
- *
- */
-
-export default Vue.component('pipelines-table', {
-
- components: {
- pipelinesTableComponent,
- errorState,
- emptyState,
- loadingIcon,
- },
-
- /**
- * Accesses the DOM to provide the needed data.
- * Returns the necessary props to render `pipelines-table-component` component.
- *
- * @return {Object}
- */
- data() {
- const store = new PipelineStore();
-
- return {
- endpoint: null,
- helpPagePath: null,
- store,
- state: store.state,
- isLoading: false,
- hasError: false,
- isMakingRequest: false,
- updateGraphDropdown: false,
- hasMadeRequest: false,
- };
- },
-
- computed: {
- shouldRenderErrorState() {
- return this.hasError && !this.isLoading;
- },
-
- /**
- * Empty state is only rendered if after the first request we receive no pipelines.
- *
- * @return {Boolean}
- */
- shouldRenderEmptyState() {
- return !this.state.pipelines.length &&
- !this.isLoading &&
- this.hasMadeRequest &&
- !this.hasError;
- },
-
- shouldRenderTable() {
- return !this.isLoading &&
- this.state.pipelines.length > 0 &&
- !this.hasError;
- },
- },
-
- /**
- * When the component is about to be mounted, tell the service to fetch the data
- *
- * A request to fetch the pipelines will be made.
- * In case of a successfull response we will store the data in the provided
- * store, in case of a failed response we need to warn the user.
- *
- */
- beforeMount() {
- const element = document.querySelector('#commit-pipeline-table-view');
-
- this.endpoint = element.dataset.endpoint;
- this.helpPagePath = element.dataset.helpPagePath;
- this.service = new PipelinesService(this.endpoint);
-
- this.poll = new Poll({
- resource: this.service,
- method: 'getPipelines',
- successCallback: this.successCallback,
- errorCallback: this.errorCallback,
- notificationCallback: this.setIsMakingRequest,
- });
-
- if (!Visibility.hidden()) {
- this.isLoading = true;
- this.poll.makeRequest();
- } else {
- // If tab is not visible we need to make the first request so we don't show the empty
- // state without knowing if there are any pipelines
- this.fetchPipelines();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- this.poll.restart();
- } else {
- this.poll.stop();
- }
- });
-
- eventHub.$on('refreshPipelines', this.fetchPipelines);
- },
-
- beforeDestroy() {
- eventHub.$off('refreshPipelines');
- },
-
- destroyed() {
- this.poll.stop();
- },
-
- methods: {
- fetchPipelines() {
- this.isLoading = true;
-
- return this.service.getPipelines()
- .then(response => this.successCallback(response))
- .catch(() => this.errorCallback());
- },
-
- successCallback(resp) {
- const response = resp.json();
-
- this.hasMadeRequest = true;
-
- // depending of the endpoint the response can either bring a `pipelines` key or not.
- const pipelines = response.pipelines || response;
- this.store.storePipelines(pipelines);
- this.isLoading = false;
- this.updateGraphDropdown = true;
- },
-
- errorCallback() {
- this.hasError = true;
- this.isLoading = false;
- this.updateGraphDropdown = false;
- },
-
- setIsMakingRequest(isMakingRequest) {
- this.isMakingRequest = isMakingRequest;
-
- if (isMakingRequest) {
- this.updateGraphDropdown = false;
- }
- },
- },
-
- template: `
- <div class="content-list pipelines">
-
- <loading-icon
- label="Loading pipelines"
- size="3"
- v-if="isLoading"
- />
-
- <empty-state
- v-if="shouldRenderEmptyState"
- :help-page-path="helpPagePath" />
-
- <error-state v-if="shouldRenderErrorState" />
-
- <div
- class="table-holder"
- v-if="shouldRenderTable">
- <pipelines-table-component
- :pipelines="state.pipelines"
- :service="service"
- :update-graph-dropdown="updateGraphDropdown"
- />
- </div>
- </div>
- `,
-});
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
new file mode 100644
index 00000000000..3c77f14d533
--- /dev/null
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -0,0 +1,90 @@
+<script>
+ import PipelinesService from '../../pipelines/services/pipelines_service';
+ import PipelineStore from '../../pipelines/stores/pipelines_store';
+ import pipelinesMixin from '../../pipelines/mixins/pipelines';
+
+ export default {
+ props: {
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ },
+ mixins: [
+ pipelinesMixin,
+ ],
+
+ data() {
+ const store = new PipelineStore();
+
+ return {
+ store,
+ state: store.state,
+ };
+ },
+
+ computed: {
+ /**
+ * Empty state is only rendered if after the first request we receive no pipelines.
+ *
+ * @return {Boolean}
+ */
+ shouldRenderEmptyState() {
+ return !this.state.pipelines.length &&
+ !this.isLoading &&
+ this.hasMadeRequest &&
+ !this.hasError;
+ },
+
+ shouldRenderTable() {
+ return !this.isLoading &&
+ this.state.pipelines.length > 0 &&
+ !this.hasError;
+ },
+ },
+ created() {
+ this.service = new PipelinesService(this.endpoint);
+ },
+ methods: {
+ successCallback(resp) {
+ const response = resp.json();
+
+ // depending of the endpoint the response can either bring a `pipelines` key or not.
+ const pipelines = response.pipelines || response;
+ this.setCommonData(pipelines);
+ },
+ },
+ };
+</script>
+<template>
+ <div class="content-list pipelines">
+
+ <loading-icon
+ label="Loading pipelines"
+ size="3"
+ v-if="isLoading"
+ />
+
+ <empty-state
+ v-if="shouldRenderEmptyState"
+ :help-page-path="helpPagePath"
+ />
+
+ <error-state
+ v-if="shouldRenderErrorState"
+ />
+
+ <div
+ class="table-holder"
+ v-if="shouldRenderTable">
+ <pipelines-table-component
+ :pipelines="state.pipelines"
+ :update-graph-dropdown="updateGraphDropdown"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index e14414d3f68..8473a81bc88 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -51,6 +51,11 @@ export default {
required: false,
default: '',
},
+ initialTaskStatus: {
+ type: String,
+ required: false,
+ default: '',
+ },
updatedAt: {
type: String,
required: false,
@@ -105,6 +110,7 @@ export default {
updatedAt: this.updatedAt,
updatedByName: this.updatedByName,
updatedByPath: this.updatedByPath,
+ taskStatus: this.initialTaskStatus,
});
return {
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index bb95ff0101b..43db66c8e08 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -37,18 +37,7 @@
});
},
taskStatus() {
- const taskRegexMatches = this.taskStatus.match(/(\d+) of (\d+)/);
- const $issuableHeader = $('.issuable-meta');
- const $tasks = $('#task_status', $issuableHeader);
- const $tasksShort = $('#task_status_short', $issuableHeader);
-
- if (taskRegexMatches) {
- $tasks.text(this.taskStatus);
- $tasksShort.text(`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`);
- } else {
- $tasks.text('');
- $tasksShort.text('');
- }
+ this.updateTaskStatusText();
},
},
methods: {
@@ -64,9 +53,24 @@
});
}
},
+ updateTaskStatusText() {
+ const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
+ const $issuableHeader = $('.issuable-meta');
+ const $tasks = $('#task_status', $issuableHeader);
+ const $tasksShort = $('#task_status_short', $issuableHeader);
+
+ if (taskRegexMatches) {
+ $tasks.text(this.taskStatus);
+ $tasksShort.text(`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`);
+ } else {
+ $tasks.text('');
+ $tasksShort.text('');
+ }
+ },
},
mounted() {
this.renderGFM();
+ this.updateTaskStatusText();
},
};
</script>
diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index 14b2a1e18e9..ad8cb6465e2 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -45,6 +45,7 @@ document.addEventListener('DOMContentLoaded', () => {
updatedAt: this.updatedAt,
updatedByName: this.updatedByName,
updatedByPath: this.updatedByPath,
+ initialTaskStatus: this.initialTaskStatus,
},
});
},
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index 27c2d349f52..f2b822f3cbb 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -1,23 +1,6 @@
export default class Store {
- constructor({
- titleHtml,
- titleText,
- descriptionHtml,
- descriptionText,
- updatedAt,
- updatedByName,
- updatedByPath,
- }) {
- this.state = {
- titleHtml,
- titleText,
- descriptionHtml,
- descriptionText,
- taskStatus: '',
- updatedAt,
- updatedByName,
- updatedByPath,
- };
+ constructor(initialState) {
+ this.state = initialState;
this.formState = {
title: '',
confidential: false,
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 894ed81b044..f503cd38c24 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -3,10 +3,12 @@
/* global Flash */
/* global notes */
+import Vue from 'vue';
import Cookies from 'js-cookie';
import './breakpoints';
import './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
+import commitPipelinesTable from './commit/pipelines/pipelines_table.vue';
/* eslint-disable max-len */
// MergeRequestTabs
@@ -233,11 +235,18 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
}
mountPipelinesView() {
- this.commitPipelinesTable = new gl.CommitPipelinesTable().$mount();
+ const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
+ const CommitPipelinesTable = Vue.extend(commitPipelinesTable);
+ this.commitPipelinesTable = new CommitPipelinesTable({
+ propsData: {
+ endpoint: pipelineTableViewEl.dataset.endpoint,
+ helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
+ },
+ }).$mount();
+
// $mount(el) replaces the el with the new rendered component. We need it in order to mount
// it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
- document.querySelector('#commit-pipeline-table-view')
- .appendChild(this.commitPipelinesTable.$el);
+ pipelineTableViewEl.appendChild(this.commitPipelinesTable.$el);
}
loadDiff(source) {
diff --git a/app/assets/javascripts/pipelines/components/async_button.vue b/app/assets/javascripts/pipelines/components/async_button.vue
index 37a6f02d8fd..abcd0c4ecea 100644
--- a/app/assets/javascripts/pipelines/components/async_button.vue
+++ b/app/assets/javascripts/pipelines/components/async_button.vue
@@ -1,9 +1,9 @@
<script>
/* eslint-disable no-new, no-alert */
-/* global Flash */
-import '~/flash';
+
import eventHub from '../event_hub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import tooltipMixin from '../../vue_shared/mixins/tooltip';
export default {
props: {
@@ -11,53 +11,42 @@ export default {
type: String,
required: true,
},
-
- service: {
- type: Object,
- required: true,
- },
-
title: {
type: String,
required: true,
},
-
icon: {
type: String,
required: true,
},
-
cssClass: {
type: String,
required: true,
},
-
confirmActionMessage: {
type: String,
required: false,
},
},
-
components: {
loadingIcon,
},
-
+ mixins: [
+ tooltipMixin,
+ ],
data() {
return {
isLoading: false,
};
},
-
computed: {
iconClass() {
return `fa fa-${this.icon}`;
},
-
buttonClass() {
- return `btn has-tooltip ${this.cssClass}`;
+ return `btn ${this.cssClass}`;
},
},
-
methods: {
onClick() {
if (this.confirmActionMessage && confirm(this.confirmActionMessage)) {
@@ -66,21 +55,11 @@ export default {
this.makeRequest();
}
},
-
makeRequest() {
this.isLoading = true;
- $(this.$el).tooltip('destroy');
-
- this.service.postAction(this.endpoint)
- .then(() => {
- this.isLoading = false;
- eventHub.$emit('refreshPipelines');
- })
- .catch(() => {
- this.isLoading = false;
- new Flash('An error occured while making the request.');
- });
+ $(this.$refs.tooltip).tooltip('destroy');
+ eventHub.$emit('postAction', this.endpoint);
},
},
};
@@ -95,10 +74,12 @@ export default {
:aria-label="title"
data-container="body"
data-placement="top"
+ ref="tooltip"
:disabled="isLoading">
<i
:class="iconClass"
- aria-hidden="true" />
+ aria-hidden="true">
+ </i>
<loading-icon v-if="isLoading" />
</button>
</template>
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index fed42d23112..01ae07aad65 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -1,15 +1,9 @@
<script>
- import Visibility from 'visibilityjs';
import PipelinesService from '../services/pipelines_service';
- import eventHub from '../event_hub';
- import pipelinesTableComponent from '../../vue_shared/components/pipelines_table.vue';
+ import pipelinesMixin from '../mixins/pipelines';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
- import emptyState from './empty_state.vue';
- import errorState from './error_state.vue';
import navigationTabs from './navigation_tabs.vue';
import navigationControls from './nav_controls.vue';
- import loadingIcon from '../../vue_shared/components/loading_icon.vue';
- import Poll from '../../lib/utils/poll';
export default {
props: {
@@ -20,13 +14,12 @@
},
components: {
tablePagination,
- pipelinesTableComponent,
- emptyState,
- errorState,
navigationTabs,
navigationControls,
- loadingIcon,
},
+ mixins: [
+ pipelinesMixin,
+ ],
data() {
const pipelinesData = document.querySelector('#pipelines-list-vue').dataset;
@@ -47,11 +40,6 @@
state: this.store.state,
apiScope: 'all',
pagenum: 1,
- isLoading: false,
- hasError: false,
- isMakingRequest: false,
- updateGraphDropdown: false,
- hasMadeRequest: false,
};
},
computed: {
@@ -62,9 +50,6 @@
const scope = gl.utils.getParameterByName('scope');
return scope === null ? 'all' : scope;
},
- shouldRenderErrorState() {
- return this.hasError && !this.isLoading;
- },
/**
* The empty state should only be rendered when the request is made to fetch all pipelines
@@ -106,7 +91,6 @@
this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage;
},
-
hasCiEnabled() {
return this.hasCi !== undefined;
},
@@ -129,37 +113,7 @@
},
created() {
this.service = new PipelinesService(this.endpoint);
-
- const poll = new Poll({
- resource: this.service,
- method: 'getPipelines',
- data: { page: this.pageParameter, scope: this.scopeParameter },
- successCallback: this.successCallback,
- errorCallback: this.errorCallback,
- notificationCallback: this.setIsMakingRequest,
- });
-
- if (!Visibility.hidden()) {
- this.isLoading = true;
- poll.makeRequest();
- } else {
- // If tab is not visible we need to make the first request so we don't show the empty
- // state without knowing if there are any pipelines
- this.fetchPipelines();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- poll.restart();
- } else {
- poll.stop();
- }
- });
-
- eventHub.$on('refreshPipelines', this.fetchPipelines);
- },
- beforeDestroy() {
- eventHub.$off('refreshPipelines');
+ this.requestData = { page: this.pageParameter, scope: this.scopeParameter };
},
methods: {
/**
@@ -174,15 +128,6 @@
return param;
},
- fetchPipelines() {
- if (!this.isMakingRequest) {
- this.isLoading = true;
-
- this.service.getPipelines({ scope: this.scopeParameter, page: this.pageParameter })
- .then(response => this.successCallback(response))
- .catch(() => this.errorCallback());
- }
- },
successCallback(resp) {
const response = {
headers: resp.headers,
@@ -190,33 +135,14 @@
};
this.store.storeCount(response.body.count);
- this.store.storePipelines(response.body.pipelines);
this.store.storePagination(response.headers);
-
- this.isLoading = false;
- this.updateGraphDropdown = true;
- this.hasMadeRequest = true;
- },
-
- errorCallback() {
- this.hasError = true;
- this.isLoading = false;
- this.updateGraphDropdown = false;
- },
-
- setIsMakingRequest(isMakingRequest) {
- this.isMakingRequest = isMakingRequest;
-
- if (isMakingRequest) {
- this.updateGraphDropdown = false;
- }
+ this.setCommonData(response.body.pipelines);
},
},
};
</script>
<template>
<div :class="cssClass">
-
<div
class="top-area scrolling-tabs-container inner-page-scroll-tabs"
v-if="!isLoading && !shouldRenderEmptyState">
@@ -274,7 +200,6 @@
<pipelines-table-component
:pipelines="state.pipelines"
- :service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 97b4de26214..a6fc4f04237 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -11,10 +11,6 @@
type: Array,
required: true,
},
- service: {
- type: Object,
- required: true,
- },
},
components: {
loadingIcon,
@@ -31,17 +27,9 @@
$(this.$refs.tooltip).tooltip('destroy');
- this.service.postAction(endpoint)
- .then(() => {
- this.isLoading = false;
- eventHub.$emit('refreshPipelines');
- })
- .catch(() => {
- this.isLoading = false;
- // eslint-disable-next-line no-new
- new Flash('An error occured while making the request.');
- });
+ eventHub.$emit('postAction', endpoint);
},
+
isActionDisabled(action) {
if (action.playable === undefined) {
return false;
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index 884f1ce9689..5088d92209f 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -12,10 +12,6 @@
type: Array,
required: true,
},
- service: {
- type: Object,
- required: true,
- },
updateGraphDropdown: {
type: Boolean,
required: false,
@@ -57,7 +53,6 @@
v-for="model in pipelines"
:key="model.id"
:pipeline="model"
- :service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index 4d5ebe2e9ed..c3f1c426d8a 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -1,13 +1,13 @@
<script>
/* eslint-disable no-param-reassign */
-import asyncButtonComponent from '../../pipelines/components/async_button.vue';
-import pipelinesActionsComponent from '../../pipelines/components/pipelines_actions.vue';
-import pipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts.vue';
-import ciBadge from './ci_badge_link.vue';
-import pipelineStage from '../../pipelines/components/stage.vue';
-import pipelineUrl from '../../pipelines/components/pipeline_url.vue';
-import pipelinesTimeago from '../../pipelines/components/time_ago.vue';
-import commitComponent from './commit.vue';
+import asyncButtonComponent from './async_button.vue';
+import pipelinesActionsComponent from './pipelines_actions.vue';
+import pipelinesArtifactsComponent from './pipelines_artifacts.vue';
+import ciBadge from '../../vue_shared/components/ci_badge_link.vue';
+import pipelineStage from './stage.vue';
+import pipelineUrl from './pipeline_url.vue';
+import pipelinesTimeago from './time_ago.vue';
+import commitComponent from '../../vue_shared/components/commit.vue';
/**
* Pipeline table row.
@@ -20,10 +20,6 @@ export default {
type: Object,
required: true,
},
- service: {
- type: Object,
- required: true,
- },
updateGraphDropdown: {
type: Boolean,
required: false,
@@ -271,7 +267,6 @@ export default {
<pipelines-actions-component
v-if="pipeline.details.manual_actions.length"
:actions="pipeline.details.manual_actions"
- :service="service"
/>
<pipelines-artifacts-component
@@ -282,7 +277,6 @@ export default {
<async-button-component
v-if="pipeline.flags.retryable"
- :service="service"
:endpoint="pipeline.retry_path"
css-class="js-pipelines-retry-button btn-default btn-retry"
title="Retry"
@@ -291,7 +285,6 @@ export default {
<async-button-component
v-if="pipeline.flags.cancelable"
- :service="service"
:endpoint="pipeline.cancel_path"
css-class="js-pipelines-cancel-button btn-remove"
title="Cancel"
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
new file mode 100644
index 00000000000..9adc15e6266
--- /dev/null
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -0,0 +1,103 @@
+/* global Flash */
+import '~/flash';
+import Visibility from 'visibilityjs';
+import Poll from '../../lib/utils/poll';
+import emptyState from '../components/empty_state.vue';
+import errorState from '../components/error_state.vue';
+import loadingIcon from '../../vue_shared/components/loading_icon.vue';
+import pipelinesTableComponent from '../components/pipelines_table.vue';
+import eventHub from '../event_hub';
+
+export default {
+ components: {
+ pipelinesTableComponent,
+ errorState,
+ emptyState,
+ loadingIcon,
+ },
+ computed: {
+ shouldRenderErrorState() {
+ return this.hasError && !this.isLoading;
+ },
+ },
+ data() {
+ return {
+ isLoading: false,
+ hasError: false,
+ isMakingRequest: false,
+ updateGraphDropdown: false,
+ hasMadeRequest: false,
+ };
+ },
+ beforeMount() {
+ this.poll = new Poll({
+ resource: this.service,
+ method: 'getPipelines',
+ data: this.requestData ? this.requestData : undefined,
+ successCallback: this.successCallback,
+ errorCallback: this.errorCallback,
+ notificationCallback: this.setIsMakingRequest,
+ });
+
+ if (!Visibility.hidden()) {
+ this.isLoading = true;
+ this.poll.makeRequest();
+ } else {
+ // If tab is not visible we need to make the first request so we don't show the empty
+ // state without knowing if there are any pipelines
+ this.fetchPipelines();
+ }
+
+ Visibility.change(() => {
+ if (!Visibility.hidden()) {
+ this.poll.restart();
+ } else {
+ this.poll.stop();
+ }
+ });
+
+ eventHub.$on('refreshPipelines', this.fetchPipelines);
+ eventHub.$on('postAction', this.postAction);
+ },
+ beforeDestroy() {
+ eventHub.$off('refreshPipelines');
+ eventHub.$on('postAction', this.postAction);
+ },
+ destroyed() {
+ this.poll.stop();
+ },
+ methods: {
+ fetchPipelines() {
+ if (!this.isMakingRequest) {
+ this.isLoading = true;
+
+ this.service.getPipelines(this.requestData)
+ .then(response => this.successCallback(response))
+ .catch(() => this.errorCallback());
+ }
+ },
+ setCommonData(pipelines) {
+ this.store.storePipelines(pipelines);
+ this.isLoading = false;
+ this.updateGraphDropdown = true;
+ this.hasMadeRequest = true;
+ },
+ errorCallback() {
+ this.hasError = true;
+ this.isLoading = false;
+ this.updateGraphDropdown = false;
+ },
+ setIsMakingRequest(isMakingRequest) {
+ this.isMakingRequest = isMakingRequest;
+
+ if (isMakingRequest) {
+ this.updateGraphDropdown = false;
+ }
+ },
+ postAction(endpoint) {
+ this.service.postAction(endpoint)
+ .then(() => eventHub.$emit('refreshPipelines'))
+ .catch(() => new Flash('An error occured while making the request.'));
+ },
+ },
+};
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index b3f310ff67d..4abad3f2697 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -729,33 +729,3 @@
}
}
}
-
-.confidential-issue-warning {
- background-color: $gl-gray;
- border-radius: 3px;
- padding: $gl-btn-padding $gl-padding;
- margin-top: $gl-padding-top;
- font-size: 14px;
- color: $white-light;
-
- .fa {
- margin-right: 8px;
- }
-
- a {
- color: $white-light;
- text-decoration: underline;
- }
-
- &.affix {
- position: static;
- width: initial;
-
- @media (min-width: $screen-sm-min) {
- position: sticky;
- position: -webkit-sticky;
- top: 60px;
- z-index: 200;
- }
- }
-}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index aa307414737..69fed4e6bf7 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -103,6 +103,42 @@
}
}
+.confidential-issue-warning {
+ background-color: $gray-normal;
+ border-radius: 3px;
+ padding: 3px 12px;
+ margin: auto;
+ margin-top: 0;
+ text-align: center;
+ font-size: 12px;
+ align-items: center;
+
+ @media (max-width: $screen-md-max) {
+ // On smaller devices the warning becomes the fourth item in the list,
+ // rather than centering, and grows to span the full width of the
+ // comment area.
+ order: 4;
+ margin: 6px auto;
+ width: 100%;
+ }
+
+ .fa {
+ margin-right: 8px;
+ }
+}
+
+.right-sidebar-expanded {
+ .confidential-issue-warning {
+ // When the sidebar is open the warning becomes the fourth item in the list,
+ // rather than centering, and grows to span the full width of the
+ // comment area.
+ order: 4;
+ margin: 6px auto;
+ width: 100%;
+ }
+}
+
+
.discussion-form {
padding: $gl-padding-top $gl-padding $gl-padding;
background-color: $white-light;
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 5e8f0849969..3259a9c1933 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -138,8 +138,8 @@ module IssuablesHelper
end
output << "&ensp;".html_safe
- output << content_tag(:span, issuable.task_status, id: "task_status", class: "hidden-xs hidden-sm")
- output << content_tag(:span, issuable.task_status_short, id: "task_status_short", class: "hidden-md hidden-lg")
+ output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm")
+ output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg")
output
end
@@ -216,7 +216,8 @@ module IssuablesHelper
initialTitleHtml: markdown_field(issuable, :title),
initialTitleText: issuable.title,
initialDescriptionHtml: markdown_field(issuable, :description),
- initialDescriptionText: issuable.description
+ initialDescriptionText: issuable.description,
+ initialTaskStatus: issuable.task_status
}
data.merge!(updated_at_by(issuable))
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 3edc395033c..d63d4ec2b12 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -70,7 +70,7 @@ module ChatMessage
end
def branch_link
- "`[#{ref}](#{branch_url})`"
+ "[#{ref}](#{branch_url})"
end
def project_link
diff --git a/app/models/project_services/chat_message/push_message.rb b/app/models/project_services/chat_message/push_message.rb
index 04a59d559ca..c52dd6ef8ef 100644
--- a/app/models/project_services/chat_message/push_message.rb
+++ b/app/models/project_services/chat_message/push_message.rb
@@ -61,7 +61,7 @@ module ChatMessage
end
def removed_branch_message
- "#{user_name} removed #{ref_type} `#{ref}` from #{project_link}"
+ "#{user_name} removed #{ref_type} #{ref} from #{project_link}"
end
def push_message
@@ -102,7 +102,7 @@ module ChatMessage
end
def branch_link
- "`[#{ref}](#{branch_url})`"
+ "[#{ref}](#{branch_url})"
end
def project_link
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 95dffdafabe..b21d5665970 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -325,6 +325,10 @@
= f.label :prometheus_metrics_enabled do
= f.check_box :prometheus_metrics_enabled
Enable Prometheus Metrics
+ - unless Gitlab::Metrics.metrics_folder_present?
+ .help-block
+ %strong.cred WARNING:
+ Environment variable `prometheus_multiproc_dir` does not exist or is not pointing to a valid directory.
%fieldset
%legend Background Jobs
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 07445434cf3..d0698285f84 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -9,6 +9,12 @@
%li
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview
+
+ - if defined?(@issue) && @issue.confidential?
+ %li.confidential-issue-warning
+ = icon('warning')
+ %span This is a confidential issue. Your comment will not be visible to the public.
+
%li.pull-right
.toolbar-group
= markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 5f92d020eef..d909b0bfbbd 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -5,13 +5,6 @@
- can_update_issue = can?(current_user, :update_issue, @issue)
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
-- if defined?(@issue) && @issue.confidential?
- .confidential-issue-warning{ data: { spy: 'affix' } }
- %span.confidential-issue-text
- #{confidential_icon(@issue)} This issue is confidential.
- %a{ href: help_page_path('user/project/issues/confidential_issues'), target: '_blank' }
- What are confidential issues?
-
.clearfix.detail-page-header
.issuable-header
.issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
@@ -26,6 +19,7 @@
= icon('angle-double-left')
.issuable-meta
+ = confidential_icon(@issue)
= issuable_meta(@issue, @project, "Issue")
.issuable-actions
diff --git a/changelogs/unreleased/33260-allow-admins-to-list-admins.yml b/changelogs/unreleased/33260-allow-admins-to-list-admins.yml
new file mode 100644
index 00000000000..a3b2053e005
--- /dev/null
+++ b/changelogs/unreleased/33260-allow-admins-to-list-admins.yml
@@ -0,0 +1,4 @@
+---
+title: Reinstate is_admin flag in users api when authenticated user is an admin
+merge_request: 12211
+author: rickettm
diff --git a/config/boot.rb b/config/boot.rb
index db5ab918021..16de55d7a86 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -6,7 +6,9 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
# set default directory for multiproces metrics gathering
-ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
+if ENV['RAILS_ENV'] == 'development' || ENV['RAILS_ENV'] == 'test'
+ ENV['prometheus_multiproc_dir'] ||= 'tmp/prometheus_multiproc_dir'
+end
# Default Bootsnap configuration from https://github.com/Shopify/bootsnap#usage
require 'bootsnap'
diff --git a/config/karma.config.js b/config/karma.config.js
index 5911a9a7e10..2f571978e08 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -66,5 +66,14 @@ module.exports = function(config) {
karmaConfig.customLaunchers.ChromeHeadlessCustom.flags.push('--enable-logging', '--v=1');
}
+ if (process.env.DEBUG) {
+ karmaConfig.logLevel = config.LOG_DEBUG;
+ process.env.CHROME_LOG_FILE = process.env.CHROME_LOG_FILE || 'chrome_debug.log';
+ }
+
+ if (process.env.CHROME_LOG_FILE) {
+ karmaConfig.customLaunchers.ChromeHeadlessCustom.flags.push('--enable-logging', '--v=1');
+ }
+
config.set(karmaConfig);
};
diff --git a/doc/api/README.md b/doc/api/README.md
index 4f189c16673..b7f6ee69193 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -29,10 +29,10 @@ following locations:
- [Labels](labels.md)
- [Merge Requests](merge_requests.md)
- [Milestones](milestones.md)
-- [Open source license templates](templates/licenses.md)
- [Namespaces](namespaces.md)
- [Notes](notes.md) (comments)
- [Notification settings](notification_settings.md)
+- [Open source license templates](templates/licenses.md)
- [Pipelines](pipelines.md)
- [Pipeline Triggers](pipeline_triggers.md)
- [Pipeline Schedules](pipeline_schedules.md)
diff --git a/doc/api/users.md b/doc/api/users.md
index b1ebd7b0c47..cf09b8f44aa 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -62,6 +62,7 @@ GET /users
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
@@ -94,6 +95,7 @@ GET /users
"avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg",
"web_url": "http://localhost:3000/jack_smith",
"created_at": "2012-05-23T08:01:01Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
@@ -197,6 +199,7 @@ Parameters:
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/index.jpg",
"web_url": "http://localhost:3000/john_smith",
"created_at": "2012-05-23T08:00:58Z",
+ "is_admin": false,
"bio": null,
"location": null,
"skype": "",
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 208be7d0ed5..1760b182114 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -43,8 +43,9 @@ next to the issues that are marked as confidential.
---
-While inside the issue, you can see a persistent dark banner at the top of the
-screen.
+Likewise, while inside the issue, you can see the eye-slash icon right next to
+the issue number, but there is also an indicator in the comment area that the
+issue you are commenting on is confidential.
![Confidential issue page](img/confidential_issues_issue_page.png)
diff --git a/doc/user/project/issues/img/confidential_issues_issue_page.png b/doc/user/project/issues/img/confidential_issues_issue_page.png
index 91f7cc8d3ca..f04ec8ff32b 100755
--- a/doc/user/project/issues/img/confidential_issues_issue_page.png
+++ b/doc/user/project/issues/img/confidential_issues_issue_page.png
Binary files differ
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 412443a2405..8bce79529e6 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -43,11 +43,14 @@ module API
expose :external
end
- class UserWithPrivateDetails < UserPublic
- expose :private_token
+ class UserWithAdmin < UserPublic
expose :admin?, as: :is_admin
end
+ class UserWithPrivateDetails < UserWithAdmin
+ expose :private_token
+ end
+
class Email < Grape::Entity
expose :id, :email
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 7257ecb5b67..bfb69d6dc18 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -59,7 +59,7 @@ module API
users = UsersFinder.new(current_user, params).execute
- entity = current_user.admin? ? Entities::UserPublic : Entities::UserBasic
+ entity = current_user.admin? ? Entities::UserWithAdmin : Entities::UserBasic
present paginate(users), with: entity
end
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index 60686509332..9d314a56e58 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -5,8 +5,16 @@ module Gitlab
module Prometheus
include Gitlab::CurrentSettings
+ def metrics_folder_present?
+ ENV.has_key?('prometheus_multiproc_dir') &&
+ ::Dir.exist?(ENV['prometheus_multiproc_dir']) &&
+ ::File.writable?(ENV['prometheus_multiproc_dir'])
+ end
+
def prometheus_metrics_enabled?
- @prometheus_metrics_enabled ||= current_application_settings[:prometheus_metrics_enabled] || false
+ return @prometheus_metrics_enabled if defined?(@prometheus_metrics_enabled)
+
+ @prometheus_metrics_enabled = prometheus_metrics_enabled_unmemoized
end
def registry
@@ -36,6 +44,12 @@ module Gitlab
NullMetric.new
end
end
+
+ private
+
+ def prometheus_metrics_enabled_unmemoized
+ metrics_folder_present? && current_application_settings[:prometheus_metrics_enabled] || false
+ end
end
end
end
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index ebfd60198b2..694f94efcff 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -1,15 +1,15 @@
import Vue from 'vue';
-import PipelinesTable from '~/commit/pipelines/pipelines_table';
+import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
describe('Pipelines table in Commits and Merge requests', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
let pipeline;
+ let PipelinesTable;
- preloadFixtures('static/pipelines_table.html.raw');
preloadFixtures(jsonFixtureName);
beforeEach(() => {
- loadFixtures('static/pipelines_table.html.raw');
+ PipelinesTable = Vue.extend(pipelinesTable);
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
pipeline = pipelines.find(p => p.id === 1);
});
@@ -26,8 +26,11 @@ describe('Pipelines table in Commits and Merge requests', () => {
Vue.http.interceptors.push(pipelinesEmptyResponse);
this.component = new PipelinesTable({
- el: document.querySelector('#commit-pipeline-table-view'),
- });
+ propsData: {
+ endpoint: 'endpoint',
+ helpPagePath: 'foo',
+ },
+ }).$mount();
});
afterEach(function () {
@@ -58,8 +61,11 @@ describe('Pipelines table in Commits and Merge requests', () => {
Vue.http.interceptors.push(pipelinesResponse);
this.component = new PipelinesTable({
- el: document.querySelector('#commit-pipeline-table-view'),
- });
+ propsData: {
+ endpoint: 'endpoint',
+ helpPagePath: 'foo',
+ },
+ }).$mount();
});
afterEach(() => {
@@ -92,8 +98,11 @@ describe('Pipelines table in Commits and Merge requests', () => {
Vue.http.interceptors.push(pipelinesErrorResponse);
this.component = new PipelinesTable({
- el: document.querySelector('#commit-pipeline-table-view'),
- });
+ propsData: {
+ endpoint: 'endpoint',
+ helpPagePath: 'foo',
+ },
+ }).$mount();
});
afterEach(function () {
diff --git a/spec/javascripts/fixtures/pipelines_table.html.haml b/spec/javascripts/fixtures/pipelines_table.html.haml
deleted file mode 100644
index ad1682704bb..00000000000
--- a/spec/javascripts/fixtures/pipelines_table.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-#commit-pipeline-table-view{ data: { endpoint: "endpoint", "help-page-path": "foo" } }
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 886462c4b9a..f3fdbff01a6 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -95,6 +95,18 @@ describe('Description component', () => {
done();
});
});
+
+ it('clears task status text when no tasks are present', (done) => {
+ vm.taskStatus = '0 of 0';
+
+ setTimeout(() => {
+ expect(
+ document.querySelector('.issuable-meta #task_status').textContent.trim(),
+ ).toBe('');
+
+ done();
+ });
+ });
});
it('applies syntax highlighting and math when description changed', (done) => {
diff --git a/spec/javascripts/pipelines/async_button_spec.js b/spec/javascripts/pipelines/async_button_spec.js
index 28c9c7ab282..48620898357 100644
--- a/spec/javascripts/pipelines/async_button_spec.js
+++ b/spec/javascripts/pipelines/async_button_spec.js
@@ -1,25 +1,20 @@
import Vue from 'vue';
import asyncButtonComp from '~/pipelines/components/async_button.vue';
+import eventHub from '~/pipelines/event_hub';
describe('Pipelines Async Button', () => {
let component;
- let spy;
let AsyncButtonComponent;
beforeEach(() => {
AsyncButtonComponent = Vue.extend(asyncButtonComp);
- spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
-
component = new AsyncButtonComponent({
propsData: {
endpoint: '/foo',
title: 'Foo',
icon: 'fa fa-foo',
cssClass: 'bar',
- service: {
- postAction: spy,
- },
},
}).$mount();
});
@@ -33,7 +28,7 @@ describe('Pipelines Async Button', () => {
});
it('should render the provided title', () => {
- expect(component.$el.getAttribute('title')).toContain('Foo');
+ expect(component.$el.getAttribute('data-original-title')).toContain('Foo');
expect(component.$el.getAttribute('aria-label')).toContain('Foo');
});
@@ -41,37 +36,12 @@ describe('Pipelines Async Button', () => {
expect(component.$el.getAttribute('class')).toContain('bar');
});
- it('should call the service when it is clicked with the provided endpoint', () => {
- component.$el.click();
- expect(spy).toHaveBeenCalledWith('/foo');
- });
-
- it('should hide loading if request fails', () => {
- spy = jasmine.createSpy('spy').and.returnValue(Promise.reject());
-
- component = new AsyncButtonComponent({
- propsData: {
- endpoint: '/foo',
- title: 'Foo',
- icon: 'fa fa-foo',
- cssClass: 'bar',
- dataAttributes: {
- 'data-foo': 'foo',
- },
- service: {
- postAction: spy,
- },
- },
- }).$mount();
-
- component.$el.click();
- expect(component.$el.querySelector('.fa-spinner')).toBe(null);
- });
-
describe('With confirm dialog', () => {
it('should call the service when confimation is positive', () => {
spyOn(window, 'confirm').and.returnValue(true);
- spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
+ eventHub.$on('postAction', (endpoint) => {
+ expect(endpoint).toEqual('/foo');
+ });
component = new AsyncButtonComponent({
propsData: {
@@ -79,15 +49,11 @@ describe('Pipelines Async Button', () => {
title: 'Foo',
icon: 'fa fa-foo',
cssClass: 'bar',
- service: {
- postAction: spy,
- },
confirmActionMessage: 'bar',
},
}).$mount();
component.$el.click();
- expect(spy).toHaveBeenCalledWith('/foo');
});
});
});
diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js
index 8a58b77f1e3..72fb0a8f9ef 100644
--- a/spec/javascripts/pipelines/pipelines_actions_spec.js
+++ b/spec/javascripts/pipelines/pipelines_actions_spec.js
@@ -3,7 +3,6 @@ import pipelinesActionsComp from '~/pipelines/components/pipelines_actions.vue';
describe('Pipelines Actions dropdown', () => {
let component;
- let spy;
let actions;
let ActionsComponent;
@@ -22,14 +21,9 @@ describe('Pipelines Actions dropdown', () => {
},
];
- spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
-
component = new ActionsComponent({
propsData: {
actions,
- service: {
- postAction: spy,
- },
},
}).$mount();
});
@@ -40,31 +34,6 @@ describe('Pipelines Actions dropdown', () => {
).toEqual(actions.length);
});
- it('should call the service when an action is clicked', () => {
- component.$el.querySelector('.js-pipeline-dropdown-manual-actions').click();
- component.$el.querySelector('.js-pipeline-action-link').click();
-
- expect(spy).toHaveBeenCalledWith(actions[0].path);
- });
-
- it('should hide loading if request fails', () => {
- spy = jasmine.createSpy('spy').and.returnValue(Promise.reject());
-
- component = new ActionsComponent({
- propsData: {
- actions,
- service: {
- postAction: spy,
- },
- },
- }).$mount();
-
- component.$el.querySelector('.js-pipeline-dropdown-manual-actions').click();
- component.$el.querySelector('.js-pipeline-action-link').click();
-
- expect(component.$el.querySelector('.fa-spinner')).toEqual(null);
- });
-
it('should render a disabled action when it\'s not playable', () => {
expect(
component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
index 9475ee28a03..7ce39dca112 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import tableRowComp from '~/vue_shared/components/pipelines_table_row.vue';
+import tableRowComp from '~/pipelines/components/pipelines_table_row.vue';
describe('Pipelines Table Row', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js b/spec/javascripts/pipelines/pipelines_table_spec.js
index 4c35d702004..3afe89c8db4 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import pipelinesTableComp from '~/vue_shared/components/pipelines_table.vue';
+import pipelinesTableComp from '~/pipelines/components/pipelines_table.vue';
import '~/lib/utils/datetime_utility';
describe('Pipelines Table', () => {
@@ -22,7 +22,6 @@ describe('Pipelines Table', () => {
component = new PipelinesTableComponent({
propsData: {
pipelines: [],
- service: {},
},
}).$mount();
});
@@ -48,7 +47,6 @@ describe('Pipelines Table', () => {
const component = new PipelinesTableComponent({
propsData: {
pipelines: [],
- service: {},
},
}).$mount();
expect(component.$el.querySelectorAll('.commit.gl-responsive-table-row').length).toEqual(0);
@@ -58,10 +56,8 @@ describe('Pipelines Table', () => {
describe('with data', () => {
it('should render rows', () => {
const component = new PipelinesTableComponent({
- el: document.querySelector('.test-dom-element'),
propsData: {
pipelines: [pipeline],
- service: {},
},
}).$mount();
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 5a87b906609..58a84cd3fe1 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -15,6 +15,36 @@ describe Gitlab::Metrics do
end
end
+ describe '.prometheus_metrics_enabled_unmemoized' do
+ subject { described_class.send(:prometheus_metrics_enabled_unmemoized) }
+
+ context 'prometheus metrics enabled in config' do
+ before do
+ allow(described_class).to receive(:current_application_settings).and_return(prometheus_metrics_enabled: true)
+ end
+
+ context 'when metrics folder is present' do
+ before do
+ allow(described_class).to receive(:metrics_folder_present?).and_return(true)
+ end
+
+ it 'metrics are enabled' do
+ expect(subject).to eq(true)
+ end
+ end
+
+ context 'when metrics folder is missing' do
+ before do
+ allow(described_class).to receive(:metrics_folder_present?).and_return(false)
+ end
+
+ it 'metrics are disabled' do
+ expect(subject).to eq(false)
+ end
+ end
+ end
+ end
+
describe '.prometheus_metrics_enabled?' do
it 'returns a boolean' do
expect(described_class.prometheus_metrics_enabled?).to be_in([true, false])
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index 7d2599dc703..43b02568cb9 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -62,7 +62,7 @@ describe ChatMessage::PipelineMessage do
def build_message(status_text = status, name = user[:name])
"<http://example.gitlab.com|project_name>:" \
" Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
- " of branch `<http://example.gitlab.com/commits/develop|develop>`" \
+ " of branch <http://example.gitlab.com/commits/develop|develop>" \
" by #{name} #{status_text} in 02:00:10"
end
end
@@ -81,7 +81,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by hacker passed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by hacker passed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -98,7 +98,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by hacker failed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by hacker failed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -113,7 +113,7 @@ describe ChatMessage::PipelineMessage do
expect(subject.pretext).to be_empty
expect(subject.attachments).to eq(message)
expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch `[develop](http://example.gitlab.com/commits/develop)` by API failed',
+ title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by API failed',
subtitle: 'in [project_name](http://example.gitlab.com)',
text: 'in 02:00:10',
image: ''
@@ -125,7 +125,7 @@ describe ChatMessage::PipelineMessage do
def build_markdown_message(status_text = status, name = user[:name])
"[project_name](http://example.gitlab.com):" \
" Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
- " of branch `[develop](http://example.gitlab.com/commits/develop)`" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
" by #{name} #{status_text} in 02:00:10"
end
end
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index e38117b75f6..c794f659c41 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -28,7 +28,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed to branch `<http://url.com/commits/master|master>` of '\
+ 'test.user pushed to branch <http://url.com/commits/master|master> of '\
'<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)')
expect(subject.attachments).to eq([{
text: "<http://url1.com|abcdefgh>: message1 - author1\n\n"\
@@ -45,7 +45,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed to branch `[master](http://url.com/commits/master)` of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
+ 'test.user pushed to branch [master](http://url.com/commits/master) of [project_name](http://url.com) ([Compare changes](http://url.com/compare/before...after))')
expect(subject.attachments).to eq(
"[abcdefgh](http://url1.com): message1 - author1\n\n[12345678](http://url2.com): message2 - author2")
expect(subject.activity).to eq({
@@ -74,7 +74,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq('test.user pushed new tag ' \
- '`<http://url.com/commits/new_tag|new_tag>` to ' \
+ '<http://url.com/commits/new_tag|new_tag> to ' \
'<http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
@@ -87,7 +87,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed new tag `[new_tag](http://url.com/commits/new_tag)` to [project_name](http://url.com)')
+ 'test.user pushed new tag [new_tag](http://url.com/commits/new_tag) to [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user created tag',
@@ -107,7 +107,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding a new branch' do
expect(subject.pretext).to eq(
- 'test.user pushed new branch `<http://url.com/commits/master|master>` to '\
+ 'test.user pushed new branch <http://url.com/commits/master|master> to '\
'<http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
@@ -120,7 +120,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding a new branch' do
expect(subject.pretext).to eq(
- 'test.user pushed new branch `[master](http://url.com/commits/master)` to [project_name](http://url.com)')
+ 'test.user pushed new branch [master](http://url.com/commits/master) to [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user created branch',
@@ -140,7 +140,7 @@ describe ChatMessage::PushMessage, models: true do
context 'without markdown' do
it 'returns a message regarding a removed branch' do
expect(subject.pretext).to eq(
- 'test.user removed branch `master` from <http://url.com|project_name>')
+ 'test.user removed branch master from <http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
end
@@ -152,7 +152,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding a removed branch' do
expect(subject.pretext).to eq(
- 'test.user removed branch `master` from [project_name](http://url.com)')
+ 'test.user removed branch master from [project_name](http://url.com)')
expect(subject.attachments).to be_empty
expect(subject.activity).to eq({
title: 'test.user removed branch',
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index bc869ea1108..750682bde52 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -11,7 +11,7 @@ describe API::Users do
let(:not_existing_user_id) { (User.maximum('id') || 0 ) + 10 }
let(:not_existing_pat_id) { (PersonalAccessToken.maximum('id') || 0 ) + 10 }
- describe "GET /users" do
+ describe 'GET /users' do
context "when unauthenticated" do
it "returns authentication error" do
get api("/users")
@@ -76,6 +76,12 @@ describe API::Users do
expect(response).to have_http_status(403)
end
+
+ it 'does not reveal the `is_admin` flag of the user' do
+ get api('/users', user)
+
+ expect(json_response.first.keys).not_to include 'is_admin'
+ end
end
context "when admin" do
@@ -92,6 +98,7 @@ describe API::Users do
expect(json_response.first.keys).to include 'two_factor_enabled'
expect(json_response.first.keys).to include 'last_sign_in_at'
expect(json_response.first.keys).to include 'confirmed_at'
+ expect(json_response.first.keys).to include 'is_admin'
end
it "returns an array of external users" do
diff --git a/spec/requests/api/v3/users_spec.rb b/spec/requests/api/v3/users_spec.rb
index e9c57f7c6c3..6d7401f9764 100644
--- a/spec/requests/api/v3/users_spec.rb
+++ b/spec/requests/api/v3/users_spec.rb
@@ -7,6 +7,38 @@ describe API::V3::Users do
let(:email) { create(:email, user: user) }
let(:ldap_blocked_user) { create(:omniauth_user, provider: 'ldapmain', state: 'ldap_blocked') }
+ describe 'GET /users' do
+ context 'when authenticated' do
+ it 'returns an array of users' do
+ get v3_api('/users', user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ username = user.username
+ expect(json_response.detect do |user|
+ user['username'] == username
+ end['username']).to eq(username)
+ end
+ end
+
+ context 'when authenticated as user' do
+ it 'does not reveal the `is_admin` flag of the user' do
+ get v3_api('/users', user)
+
+ expect(json_response.first.keys).not_to include 'is_admin'
+ end
+ end
+
+ context 'when authenticated as admin' do
+ it 'reveals the `is_admin` flag of the user' do
+ get v3_api('/users', admin)
+
+ expect(json_response.first.keys).to include 'is_admin'
+ end
+ end
+ end
+
describe 'GET /user/:id/keys' do
before { admin }