diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-07 18:09:19 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-07 18:09:19 +0000 |
commit | 3290d46655f07d7ae3dca788d6df9f326972ffd8 (patch) | |
tree | 0d24713e1592cdd3583257f14a52d46a22539ed1 /app/assets | |
parent | c6b3ec3f56fa32a0e0ed3de0d0878d25f1adaddf (diff) | |
download | gitlab-ce-3290d46655f07d7ae3dca788d6df9f326972ffd8.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
8 files changed, 194 insertions, 88 deletions
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index 2e4987b7349..acc09fa6305 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -12,6 +12,20 @@ import { s__, sprintf } from '../../locale'; import { PROMETHEUS_TIMEOUT } from '../constants'; +function prometheusMetricQueryParams(timeRange) { + const { start, end } = convertToFixedRange(timeRange); + + const timeDiff = (new Date(end) - new Date(start)) / 1000; + const minStep = 60; + const queryDataPoints = 600; + + return { + start_time: start, + end_time: end, + step: Math.max(minStep, Math.ceil(timeDiff / queryDataPoints)), + }; +} + function backOffRequest(makeRequestCallback) { return backOff((next, stop) => { makeRequestCallback() @@ -26,6 +40,20 @@ function backOffRequest(makeRequestCallback) { }, PROMETHEUS_TIMEOUT); } +function getPrometheusMetricResult(prometheusEndpoint, params) { + return backOffRequest(() => axios.get(prometheusEndpoint, { params })) + .then(res => res.data) + .then(response => { + if (response.status === 'error') { + throw new Error(response.error); + } + + return response.data.result; + }); +} + +// Setup + export const setGettingStartedEmptyState = ({ commit }) => { commit(types.SET_GETTING_STARTED_EMPTY_STATE); }; @@ -47,56 +75,26 @@ export const setShowErrorBanner = ({ commit }, enabled) => { commit(types.SET_SHOW_ERROR_BANNER, enabled); }; -export const requestMetricsDashboard = ({ commit }) => { - commit(types.REQUEST_METRICS_DATA); -}; -export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response, params }) => { - const { all_dashboards, dashboard, metrics_data } = response; - - commit(types.SET_ALL_DASHBOARDS, all_dashboards); - commit(types.RECEIVE_METRICS_DATA_SUCCESS, dashboard); - commit(types.SET_ENDPOINTS, convertObjectPropsToCamelCase(metrics_data)); - - return dispatch('fetchPrometheusMetrics', params); -}; -export const receiveMetricsDashboardFailure = ({ commit }, error) => { - commit(types.RECEIVE_METRICS_DATA_FAILURE, error); -}; - -export const receiveDeploymentsDataSuccess = ({ commit }, data) => - commit(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, data); -export const receiveDeploymentsDataFailure = ({ commit }) => - commit(types.RECEIVE_DEPLOYMENTS_DATA_FAILURE); -export const requestEnvironmentsData = ({ commit }) => commit(types.REQUEST_ENVIRONMENTS_DATA); -export const receiveEnvironmentsDataSuccess = ({ commit }, data) => - commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data); -export const receiveEnvironmentsDataFailure = ({ commit }) => - commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE); +// All Data export const fetchData = ({ dispatch }) => { - dispatch('fetchDashboard'); - dispatch('fetchDeploymentsData'); dispatch('fetchEnvironmentsData'); + dispatch('fetchDashboard'); }; +// Metrics dashboard + export const fetchDashboard = ({ state, commit, dispatch }) => { dispatch('requestMetricsDashboard'); const params = {}; - - if (state.timeRange) { - const { start, end } = convertToFixedRange(state.timeRange); - params.start_time = start; - params.end_time = end; - } - if (state.currentDashboard) { params.dashboard = state.currentDashboard; } return backOffRequest(() => axios.get(state.dashboardEndpoint, { params })) .then(resp => resp.data) - .then(response => dispatch('receiveMetricsDashboardSuccess', { response, params })) + .then(response => dispatch('receiveMetricsDashboardSuccess', { response })) .catch(error => { Sentry.captureException(error); @@ -120,61 +118,43 @@ export const fetchDashboard = ({ state, commit, dispatch }) => { }); }; -function fetchPrometheusResult(prometheusEndpoint, params) { - return backOffRequest(() => axios.get(prometheusEndpoint, { params })) - .then(res => res.data) - .then(response => { - if (response.status === 'error') { - throw new Error(response.error); - } - - return response.data.result; - }); -} - -/** - * Returns list of metrics in data.result - * {"status":"success", "data":{"resultType":"matrix","result":[]}} - * - * @param {metric} metric - */ -export const fetchPrometheusMetric = ({ commit }, { metric, params }) => { - const { start_time, end_time } = params; - const timeDiff = (new Date(end_time) - new Date(start_time)) / 1000; +export const requestMetricsDashboard = ({ commit }) => { + commit(types.REQUEST_METRICS_DASHBOARD); +}; +export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response }) => { + const { all_dashboards, dashboard, metrics_data } = response; - const minStep = 60; - const queryDataPoints = 600; - const step = metric.step ? metric.step : Math.max(minStep, Math.ceil(timeDiff / queryDataPoints)); + commit(types.SET_ALL_DASHBOARDS, all_dashboards); + commit(types.RECEIVE_METRICS_DASHBOARD_SUCCESS, dashboard); + commit(types.SET_ENDPOINTS, convertObjectPropsToCamelCase(metrics_data)); - const queryParams = { - start_time, - end_time, - step, - }; + return dispatch('fetchPrometheusMetrics'); +}; +export const receiveMetricsDashboardFailure = ({ commit }, error) => { + commit(types.RECEIVE_METRICS_DASHBOARD_FAILURE, error); +}; - commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId }); +// Metrics - return fetchPrometheusResult(metric.prometheusEndpointPath, queryParams) - .then(result => { - commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, result }); - }) - .catch(error => { - Sentry.captureException(error); +/** + * Loads timeseries data: Prometheus data points and deployment data from the project + * @param {Object} Vuex store + */ +export const fetchPrometheusMetrics = ({ state, dispatch, getters }) => { + dispatch('fetchDeploymentsData'); - commit(types.RECEIVE_METRIC_RESULT_FAILURE, { metricId: metric.metricId, error }); - // Continue to throw error so the dashboard can notify using createFlash - throw error; - }); -}; + if (!state.timeRange) { + createFlash(s__(`Metrics|Invalid time range, please verify.`), 'warning'); + return Promise.reject(); + } -export const fetchPrometheusMetrics = ({ state, commit, dispatch, getters }, params) => { - commit(types.REQUEST_METRICS_DATA); + const defaultQueryParams = prometheusMetricQueryParams(state.timeRange); const promises = []; state.dashboard.panelGroups.forEach(group => { group.panels.forEach(panel => { panel.metrics.forEach(metric => { - promises.push(dispatch('fetchPrometheusMetric', { metric, params })); + promises.push(dispatch('fetchPrometheusMetric', { metric, defaultQueryParams })); }); }); }); @@ -192,6 +172,35 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch, getters }, par }); }; +/** + * Returns list of metrics in data.result + * {"status":"success", "data":{"resultType":"matrix","result":[]}} + * + * @param {metric} metric + */ +export const fetchPrometheusMetric = ({ commit }, { metric, defaultQueryParams }) => { + const queryParams = { ...defaultQueryParams }; + if (metric.step) { + queryParams.step = metric.step; + } + + commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId }); + + return getPrometheusMetricResult(metric.prometheusEndpointPath, queryParams) + .then(result => { + commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metricId, result }); + }) + .catch(error => { + Sentry.captureException(error); + + commit(types.RECEIVE_METRIC_RESULT_FAILURE, { metricId: metric.metricId, error }); + // Continue to throw error so the dashboard can notify using createFlash + throw error; + }); +}; + +// Deployments + export const fetchDeploymentsData = ({ state, dispatch }) => { if (!state.deploymentsEndpoint) { return Promise.resolve([]); @@ -212,6 +221,14 @@ export const fetchDeploymentsData = ({ state, dispatch }) => { createFlash(s__('Metrics|There was an error getting deployment information.')); }); }; +export const receiveDeploymentsDataSuccess = ({ commit }, data) => { + commit(types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS, data); +}; +export const receiveDeploymentsDataFailure = ({ commit }) => { + commit(types.RECEIVE_DEPLOYMENTS_DATA_FAILURE); +}; + +// Environments export const fetchEnvironmentsData = ({ state, dispatch }) => { dispatch('requestEnvironmentsData'); @@ -241,6 +258,17 @@ export const fetchEnvironmentsData = ({ state, dispatch }) => { createFlash(s__('Metrics|There was an error getting environments information.')); }); }; +export const requestEnvironmentsData = ({ commit }) => { + commit(types.REQUEST_ENVIRONMENTS_DATA); +}; +export const receiveEnvironmentsDataSuccess = ({ commit }, data) => { + commit(types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS, data); +}; +export const receiveEnvironmentsDataFailure = ({ commit }) => { + commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE); +}; + +// Dashboard manipulation /** * Set a new array of metrics to a panel group diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js index 09eb7dc1673..9a3489d53d7 100644 --- a/app/assets/javascripts/monitoring/stores/mutation_types.js +++ b/app/assets/javascripts/monitoring/stores/mutation_types.js @@ -1,19 +1,24 @@ -export const REQUEST_METRICS_DATA = 'REQUEST_METRICS_DATA'; -export const RECEIVE_METRICS_DATA_SUCCESS = 'RECEIVE_METRICS_DATA_SUCCESS'; -export const RECEIVE_METRICS_DATA_FAILURE = 'RECEIVE_METRICS_DATA_FAILURE'; +// Dashboard "skeleton", groups, panels and metrics +export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD'; +export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS'; +export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE'; +// Git project deployments export const REQUEST_DEPLOYMENTS_DATA = 'REQUEST_DEPLOYMENTS_DATA'; export const RECEIVE_DEPLOYMENTS_DATA_SUCCESS = 'RECEIVE_DEPLOYMENTS_DATA_SUCCESS'; export const RECEIVE_DEPLOYMENTS_DATA_FAILURE = 'RECEIVE_DEPLOYMENTS_DATA_FAILURE'; +// Environments export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA'; export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS'; export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAILURE'; +// Metric data points export const REQUEST_METRIC_RESULT = 'REQUEST_METRIC_RESULT'; export const RECEIVE_METRIC_RESULT_SUCCESS = 'RECEIVE_METRIC_RESULT_SUCCESS'; export const RECEIVE_METRIC_RESULT_FAILURE = 'RECEIVE_METRIC_RESULT_FAILURE'; +// Parameters and other information export const SET_TIME_RANGE = 'SET_TIME_RANGE'; export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS'; export const SET_ENDPOINTS = 'SET_ENDPOINTS'; diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js index 2e10d189087..0a7bb47d533 100644 --- a/app/assets/javascripts/monitoring/stores/mutations.js +++ b/app/assets/javascripts/monitoring/stores/mutations.js @@ -74,18 +74,18 @@ export default { /** * Dashboard panels structure and global state */ - [types.REQUEST_METRICS_DATA](state) { + [types.REQUEST_METRICS_DASHBOARD](state) { state.emptyState = 'loading'; state.showEmptyState = true; }, - [types.RECEIVE_METRICS_DATA_SUCCESS](state, dashboard) { + [types.RECEIVE_METRICS_DASHBOARD_SUCCESS](state, dashboard) { state.dashboard = mapToDashboardViewModel(dashboard); if (!state.dashboard.panelGroups.length) { state.emptyState = 'noData'; } }, - [types.RECEIVE_METRICS_DATA_FAILURE](state, error) { + [types.RECEIVE_METRICS_DASHBOARD_FAILURE](state, error) { state.emptyState = error ? 'unableToConnect' : 'noData'; state.showEmptyState = true; }, diff --git a/app/assets/javascripts/notes/components/sort_discussion.vue b/app/assets/javascripts/notes/components/sort_discussion.vue index 3f82ddde3ef..4a7543819eb 100644 --- a/app/assets/javascripts/notes/components/sort_discussion.vue +++ b/app/assets/javascripts/notes/components/sort_discussion.vue @@ -1,7 +1,9 @@ +gs <script> import { GlIcon } from '@gitlab/ui'; import { mapActions, mapGetters } from 'vuex'; import { __ } from '~/locale'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import Tracking from '~/tracking'; import { ASC, DESC } from '../constants'; @@ -14,16 +16,20 @@ export default { SORT_OPTIONS, components: { GlIcon, + LocalStorageSync, }, mixins: [Tracking.mixin()], computed: { - ...mapGetters(['sortDirection']), + ...mapGetters(['sortDirection', 'noteableType']), selectedOption() { return SORT_OPTIONS.find(({ key }) => this.sortDirection === key); }, dropdownText() { return this.selectedOption.text; }, + storageKey() { + return `sort_direction_${this.noteableType.toLowerCase()}`; + }, }, methods: { ...mapActions(['setDiscussionSortDirection']), @@ -44,6 +50,11 @@ export default { <template> <div class="mr-2 d-inline-block align-bottom full-width-mobile"> + <local-storage-sync + :value="sortDirection" + :storage-key="storageKey" + @input="setDiscussionSortDirection" + /> <button class="btn btn-sm js-dropdown-text" data-toggle="dropdown" aria-expanded="false"> {{ dropdownText }} <gl-icon name="chevron-down" /> diff --git a/app/assets/javascripts/snippets/components/snippet_blob_view.vue b/app/assets/javascripts/snippets/components/snippet_blob_view.vue index 4703a940e08..3e3dcab70c0 100644 --- a/app/assets/javascripts/snippets/components/snippet_blob_view.vue +++ b/app/assets/javascripts/snippets/components/snippet_blob_view.vue @@ -4,6 +4,7 @@ import { SNIPPET_VISIBILITY_PUBLIC } from '../constants'; import BlobHeader from '~/blob/components/blob_header.vue'; import BlobContent from '~/blob/components/blob_content.vue'; import { GlLoadingIcon } from '@gitlab/ui'; +import CloneDropdownButton from '~/vue_shared/components/clone_dropdown.vue'; import GetSnippetBlobQuery from '../queries/snippet.blob.query.graphql'; import GetBlobContent from '../queries/snippet.blob.content.query.graphql'; @@ -16,6 +17,7 @@ export default { BlobHeader, BlobContent, GlLoadingIcon, + CloneDropdownButton, }, apollo: { blob: { @@ -72,6 +74,9 @@ export default { const { richViewer, simpleViewer } = this.blob; return this.activeViewerType === RICH_BLOB_VIEWER ? richViewer : simpleViewer; }, + canBeCloned() { + return this.snippet.sshUrlToRepo || this.snippet.httpUrlToRepo; + }, }, methods: { switchViewer(newViewer, respectHash = false) { @@ -90,7 +95,15 @@ export default { class="prepend-top-20 append-bottom-20" /> <article v-else class="file-holder snippet-file-content"> - <blob-header :blob="blob" :active-viewer-type="viewer.type" @viewer-changed="switchViewer" /> + <blob-header :blob="blob" :active-viewer-type="viewer.type" @viewer-changed="switchViewer"> + <template #actions> + <clone-dropdown-button + v-if="canBeCloned" + :ssh-link="snippet.sshUrlToRepo" + :http-link="snippet.httpUrlToRepo" + /> + </template> + </blob-header> <blob-content :loading="isContentLoading" :content="blobContent" :active-viewer="viewer" /> </article> </div> diff --git a/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql b/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql index e0cc6cc2dda..22aab7c7795 100644 --- a/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql +++ b/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql @@ -7,8 +7,10 @@ fragment SnippetBase on Snippet { updatedAt visibilityLevel webUrl + httpUrlToRepo + sshUrlToRepo userPermissions { adminSnippet updateSnippet } -}
\ No newline at end of file +} diff --git a/app/assets/javascripts/vue_shared/components/local_storage_sync.vue b/app/assets/javascripts/vue_shared/components/local_storage_sync.vue new file mode 100644 index 00000000000..b5d6b872547 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/local_storage_sync.vue @@ -0,0 +1,39 @@ +<script> +export default { + props: { + storageKey: { + type: String, + required: true, + }, + value: { + type: String, + required: false, + default: '', + }, + }, + watch: { + value(newVal) { + this.saveValue(newVal); + }, + }, + mounted() { + // On mount, trigger update if we actually have a localStorageValue + const value = this.getValue(); + + if (value && this.value !== value) { + this.$emit('input', value); + } + }, + methods: { + getValue() { + return localStorage.getItem(this.storageKey); + }, + saveValue(val) { + localStorage.setItem(this.storageKey, val); + }, + }, + render() { + return this.$slots.default; + }, +}; +</script> diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index a56505ee6e2..b6edadb05a9 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -1,6 +1,14 @@ .dropdown { position: relative; + // Once the new design (https://gitlab.com/gitlab-org/gitlab-foss/-/issues/63499/designs) + // for Snippets is introduced and Clone button is relocated, we won't + // need this style. + // Issue for the refactoring: https://gitlab.com/gitlab-org/gitlab/-/issues/213327 + &.gl-new-dropdown button.dropdown-toggle { + @include gl-display-inline-flex; + } + .btn-link { &:hover { cursor: pointer; |