diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-31 18:08:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-31 18:08:42 +0300 |
commit | c27acb1d376f7127cd33eadcc8f5683ed55262bc (patch) | |
tree | 685c31391dca71a73782b5c8626f4ef5b582dc21 /app/assets/javascripts | |
parent | 1808454313ed75c92e1384466e8c83bfbc8ae25e (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
10 files changed, 199 insertions, 150 deletions
diff --git a/app/assets/javascripts/blob/components/blob_embeddable.vue b/app/assets/javascripts/blob/components/blob_embeddable.vue new file mode 100644 index 00000000000..26bd0208309 --- /dev/null +++ b/app/assets/javascripts/blob/components/blob_embeddable.vue @@ -0,0 +1,41 @@ +<script> +import { GlFormInputGroup, GlButton, GlIcon } from '@gitlab/ui'; +import { __ } from '~/locale'; + +export default { + components: { + GlFormInputGroup, + GlButton, + GlIcon, + }, + props: { + url: { + type: String, + required: true, + }, + }, + data() { + return { + optionValues: [ + // eslint-disable-next-line no-useless-escape + { name: __('Embed'), value: `<script src='${this.url}.js'><\/script>` }, + { name: __('Share'), value: this.url }, + ], + }; + }, +}; +</script> +<template> + <gl-form-input-group + id="embeddable-text" + :predefined-options="optionValues" + readonly + select-on-click + > + <template #append> + <gl-button new-style data-clipboard-target="#embeddable-text"> + <gl-icon name="copy-to-clipboard" :title="__('Copy')" /> + </gl-button> + </template> + </gl-form-input-group> +</template> diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index c76c039fb3b..5f410c487e9 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -19,10 +19,10 @@ import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; import { s__ } from '~/locale'; import createFlash from '~/flash'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; +import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; import invalidUrl from '~/lib/utils/invalid_url'; +import { convertToFixedRange } from '~/lib/utils/datetime_range'; import Icon from '~/vue_shared/components/icon.vue'; -import { getTimeRange } from '~/vue_shared/components/date_time_picker/date_time_picker_lib'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import GraphGroup from './graph_group.vue'; @@ -31,11 +31,8 @@ import GroupEmptyState from './group_empty_state.vue'; import DashboardsDropdown from './dashboards_dropdown.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; -import { getAddMetricTrackingOptions } from '../utils'; - -import { datePickerTimeWindows, metricStates } from '../constants'; - -const defaultTimeRange = getTimeRange(); +import { getAddMetricTrackingOptions, timeRangeToUrl, timeRangeFromUrl } from '../utils'; +import { defaultTimeRange, timeRanges, metricStates } from '../constants'; export default { components: { @@ -197,10 +194,9 @@ export default { return { state: 'gettingStarted', formIsValid: null, - startDate: getParameterValues('start')[0] || defaultTimeRange.start, - endDate: getParameterValues('end')[0] || defaultTimeRange.end, + selectedTimeRange: timeRangeFromUrl() || defaultTimeRange, hasValidDates: true, - datePickerTimeWindows, + timeRanges, isRearrangingPanels: false, }; }, @@ -260,9 +256,11 @@ export default { if (!this.hasMetrics) { this.setGettingStartedEmptyState(); } else { + const { start, end } = convertToFixedRange(this.selectedTimeRange); + this.fetchData({ - start: this.startDate, - end: this.endDate, + start, + end, }); } }, @@ -287,8 +285,8 @@ export default { }); }, - onDateTimePickerApply(params) { - redirectTo(mergeUrlParams(params, window.location.href)); + onDateTimePickerInput(timeRange) { + redirectTo(timeRangeToUrl(timeRange)); }, onDateTimePickerInvalid() { createFlash( @@ -296,8 +294,8 @@ export default { 'Metrics|Link contains an invalid time window, please verify the link to see the requested time range.', ), ); - this.startDate = defaultTimeRange.start; - this.endDate = defaultTimeRange.end; + // As a fallback, switch to default time range instead + this.selectedTimeRange = defaultTimeRange; }, generateLink(group, title, yLabel) { @@ -447,10 +445,9 @@ export default { > <date-time-picker ref="dateTimePicker" - :start="startDate" - :end="endDate" - :time-windows="datePickerTimeWindows" - @apply="onDateTimePickerApply" + :value="selectedTimeRange" + :options="timeRanges" + @input="onDateTimePickerInput" @invalid="onDateTimePickerInvalid" /> </gl-form-group> diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue index c79e43c7c29..e55de1c0105 100644 --- a/app/assets/javascripts/monitoring/components/embed.vue +++ b/app/assets/javascripts/monitoring/components/embed.vue @@ -1,9 +1,9 @@ <script> import { mapActions, mapState, mapGetters } from 'vuex'; import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; -import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; -import { sidebarAnimationDuration } from '../constants'; -import { getTimeRange } from '~/vue_shared/components/date_time_picker/date_time_picker_lib'; +import { convertToFixedRange } from '~/lib/utils/datetime_range'; +import { timeRangeFromUrl, removeTimeRangeParams } from '../utils'; +import { sidebarAnimationDuration, defaultTimeRange } from '../constants'; let sidebarMutationObserver; @@ -18,10 +18,8 @@ export default { }, }, data() { - const defaultRange = getTimeRange(); - const start = getParameterValues('start', this.dashboardUrl)[0] || defaultRange.start; - const end = getParameterValues('end', this.dashboardUrl)[0] || defaultRange.end; - + const timeRange = timeRangeFromUrl(this.dashboardUrl) || defaultTimeRange; + const { start, end } = convertToFixedRange(timeRange); const params = { start, end, @@ -81,7 +79,7 @@ export default { }, setInitialState() { this.setEndpoints({ - dashboardEndpoint: removeParams(['start', 'end'], this.dashboardUrl), + dashboardEndpoint: removeTimeRangeParams(this.dashboardUrl), }); this.setShowErrorBanner(false); }, diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index 2d5361a5029..789b3131d11 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -83,34 +83,36 @@ export const dateFormats = { default: 'dd mmm yyyy, h:MMTT', }; -export const datePickerTimeWindows = { - thirtyMinutes: { +export const timeRanges = [ + { label: __('30 minutes'), - seconds: 60 * 30, + duration: { seconds: 60 * 30 }, }, - threeHours: { + { label: __('3 hours'), - seconds: 60 * 60 * 3, + duration: { seconds: 60 * 60 * 3 }, }, - eightHours: { + { label: __('8 hours'), - seconds: 60 * 60 * 8, + duration: { seconds: 60 * 60 * 8 }, default: true, }, - oneDay: { + { label: __('1 day'), - seconds: 60 * 60 * 24 * 1, + duration: { seconds: 60 * 60 * 24 * 1 }, }, - threeDays: { + { label: __('3 days'), - seconds: 60 * 60 * 24 * 3, + duration: { seconds: 60 * 60 * 24 * 3 }, }, - oneWeek: { + { label: __('1 week'), - seconds: 60 * 60 * 24 * 7 * 1, + duration: { seconds: 60 * 60 * 24 * 7 * 1 }, }, - twoWeeks: { + { label: __('2 weeks'), - seconds: 60 * 60 * 24 * 7 * 2, + duration: { seconds: 60 * 60 * 24 * 7 * 2 }, }, -}; +]; + +export const defaultTimeRange = timeRanges.find(tr => tr.default); diff --git a/app/assets/javascripts/snippets/components/app.vue b/app/assets/javascripts/snippets/components/app.vue index 7a2145a800c..e98f56d87f5 100644 --- a/app/assets/javascripts/snippets/components/app.vue +++ b/app/assets/javascripts/snippets/components/app.vue @@ -2,6 +2,7 @@ import GetSnippetQuery from '../queries/snippet.query.graphql'; import SnippetHeader from './snippet_header.vue'; import SnippetTitle from './snippet_title.vue'; +import SnippetBlob from './snippet_blob_view.vue'; import { GlLoadingIcon } from '@gitlab/ui'; export default { @@ -9,6 +10,7 @@ export default { SnippetHeader, SnippetTitle, GlLoadingIcon, + SnippetBlob, }, apollo: { snippet: { @@ -50,6 +52,7 @@ export default { <template v-else> <snippet-header :snippet="snippet" /> <snippet-title :snippet="snippet" /> + <snippet-blob :snippet="snippet" /> </template> </div> </template> diff --git a/app/assets/javascripts/snippets/components/snippet_blob_view.vue b/app/assets/javascripts/snippets/components/snippet_blob_view.vue new file mode 100644 index 00000000000..b91e08a4251 --- /dev/null +++ b/app/assets/javascripts/snippets/components/snippet_blob_view.vue @@ -0,0 +1,26 @@ +<script> +import BlobEmbeddable from '~/blob/components/blob_embeddable.vue'; +import { SNIPPET_VISIBILITY_PUBLIC } from '../constants'; + +export default { + components: { + BlobEmbeddable, + }, + props: { + snippet: { + type: Object, + required: true, + }, + }, + computed: { + embeddable() { + return this.snippet.visibilityLevel === SNIPPET_VISIBILITY_PUBLIC; + }, + }, +}; +</script> +<template> + <div> + <blob-embeddable v-if="embeddable" class="mb-3" :url="snippet.webUrl" /> + </div> +</template> diff --git a/app/assets/javascripts/snippets/constants.js b/app/assets/javascripts/snippets/constants.js new file mode 100644 index 00000000000..87e3fe360a3 --- /dev/null +++ b/app/assets/javascripts/snippets/constants.js @@ -0,0 +1,3 @@ +export const SNIPPET_VISIBILITY_PRIVATE = 'private'; +export const SNIPPET_VISIBILITY_INTERNAL = 'internal'; +export const SNIPPET_VISIBILITY_PUBLIC = 'public'; diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index 9f498037185..3ff1d9cf48a 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -12,8 +12,7 @@ * css-class="btn-transparent" * /> */ -import { GlButton, GlTooltipDirective } from '@gitlab/ui'; -import Icon from '../components/icon.vue'; +import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'; export default { name: 'ClipboardButton', @@ -22,7 +21,7 @@ export default { }, components: { GlButton, - Icon, + GlIcon, }, props: { text: { @@ -72,6 +71,6 @@ export default { :title="title" :data-clipboard-text="clipboardText" > - <icon name="duplicate" /> + <gl-icon name="copy-to-clipboard" /> </gl-button> </template> diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue index 7d4c162473f..eedcafe2b42 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue @@ -1,13 +1,15 @@ <script> import { GlButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; + +import { convertToFixedRange, isEqualTimeRanges, findTimeRange } from '~/lib/utils/datetime_range'; + import Icon from '~/vue_shared/components/icon.vue'; import DateTimePickerInput from './date_time_picker_input.vue'; import { - defaultTimeWindows, + defaultTimeRanges, + defaultTimeRange, isValidDate, - getTimeRange, - getTimeWindowKey, stringToISODate, ISODateToString, truncateZerosInDateTime, @@ -15,7 +17,7 @@ import { } from './date_time_picker_lib'; const events = { - apply: 'apply', + input: 'input', invalid: 'invalid', }; @@ -29,24 +31,22 @@ export default { GlDropdownItem, }, props: { - start: { - type: String, - required: true, - }, - end: { - type: String, - required: true, - }, - timeWindows: { + value: { type: Object, required: false, - default: () => defaultTimeWindows, + default: () => defaultTimeRange, + }, + options: { + type: Array, + required: false, + default: () => defaultTimeRanges, }, }, data() { return { - startDate: this.start, - endDate: this.end, + timeRange: this.value, + startDate: '', + endDate: '', }; }, computed: { @@ -67,6 +67,7 @@ export default { set(val) { // Attempt to set a formatted date if possible this.startDate = isDateTimePickerInputValid(val) ? stringToISODate(val) : val; + this.timeRange = null; }, }, endInput: { @@ -76,23 +77,48 @@ export default { set(val) { // Attempt to set a formatted date if possible this.endDate = isDateTimePickerInputValid(val) ? stringToISODate(val) : val; + this.timeRange = null; }, }, timeWindowText() { - const timeWindow = getTimeWindowKey({ start: this.start, end: this.end }, this.timeWindows); - if (timeWindow) { - return this.timeWindows[timeWindow].label; - } else if (isValidDate(this.start) && isValidDate(this.end)) { - return sprintf(__('%{start} to %{end}'), { - start: this.formatDate(this.start), - end: this.formatDate(this.end), - }); + try { + const timeRange = findTimeRange(this.value, this.options); + if (timeRange) { + return timeRange.label; + } + + const { start, end } = convertToFixedRange(this.value); + if (isValidDate(start) && isValidDate(end)) { + return sprintf(__('%{start} to %{end}'), { + start: this.formatDate(start), + end: this.formatDate(end), + }); + } + } catch { + return __('Invalid date range'); } return ''; }, }, + watch: { + value(newValue) { + const { start, end } = convertToFixedRange(newValue); + this.timeRange = this.value; + this.startDate = start; + this.endDate = end; + }, + }, mounted() { + try { + const { start, end } = convertToFixedRange(this.timeRange); + this.startDate = start; + this.endDate = end; + } catch { + // when dates cannot be parsed, emit error. + this.$emit(events.invalid); + } + // Validate on mounted, and trigger an update if needed if (!this.isValid) { this.$emit(events.invalid); @@ -102,21 +128,22 @@ export default { formatDate(date) { return truncateZerosInDateTime(ISODateToString(date)); }, - setTimeWindow(key) { - const { start, end } = getTimeRange(key, this.timeWindows); - this.startDate = start; - this.endDate = end; - - this.apply(); - }, closeDropdown() { this.$refs.dropdown.hide(); }, - apply() { - this.$emit(events.apply, { + isOptionActive(option) { + return isEqualTimeRanges(option, this.timeRange); + }, + setQuickRange(option) { + this.timeRange = option; + this.$emit(events.input, this.timeRange); + }, + setFixedRange() { + this.timeRange = convertToFixedRange({ start: this.startDate, end: this.endDate, }); + this.$emit(events.input, this.timeRange); }, }, }; @@ -146,7 +173,7 @@ export default { </div> <gl-form-group> <gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button> - <gl-button variant="success" :disabled="!isValid" @click="apply()"> + <gl-button variant="success" :disabled="!isValid" @click="setFixedRange()"> {{ __('Apply') }} </gl-button> </gl-form-group> @@ -155,19 +182,20 @@ export default { <template #label> <span class="gl-pl-5">{{ __('Quick range') }}</span> </template> + <gl-dropdown-item - v-for="(timeWindow, key) in timeWindows" - :key="key" - :active="timeWindow.label === timeWindowText" + v-for="(option, index) in options" + :key="index" + :active="isOptionActive(option)" active-class="active" - @click="setTimeWindow(key)" + @click="setQuickRange(option)" > <icon name="mobile-issue-close" class="align-bottom" - :class="{ invisible: timeWindow.label !== timeWindowText }" + :class="{ invisible: !isOptionActive(option) }" /> - {{ timeWindow.label }} + {{ option.label }} </gl-dropdown-item> </gl-form-group> </div> diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js index 685115b92dd..673d981cf07 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js @@ -1,6 +1,5 @@ import dateformat from 'dateformat'; import { __ } from '~/locale'; -import { secondsToMilliseconds } from '~/lib/utils/datetime_utility'; /** * Valid strings for this regex are @@ -9,37 +8,30 @@ import { secondsToMilliseconds } from '~/lib/utils/datetime_utility'; const dateTimePickerRegex = /^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])(?: (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/; /** - * A key-value pair of "time windows". - * - * A time window is a representation of period of time that starts - * some time in past until now. Keys are only used for easy reference. - * - * It is represented as user friendly `label` and number of `seconds` - * to be substracted from now. + * Default time ranges for the date picker. + * @see app/assets/javascripts/lib/utils/datetime_range.js */ -export const defaultTimeWindows = { - thirtyMinutes: { +export const defaultTimeRanges = [ + { + duration: { seconds: 60 * 30 }, label: __('30 minutes'), - seconds: 60 * 30, }, - threeHours: { + { + duration: { seconds: 60 * 60 * 3 }, label: __('3 hours'), - seconds: 60 * 60 * 3, }, - eightHours: { + { + duration: { seconds: 60 * 60 * 8 }, label: __('8 hours'), - seconds: 60 * 60 * 8, default: true, }, - oneDay: { + { + duration: { seconds: 60 * 60 * 24 * 1 }, label: __('1 day'), - seconds: 60 * 60 * 24 * 1, }, - threeDays: { - label: __('3 days'), - seconds: 60 * 60 * 24 * 3, - }, -}; +]; + +export const defaultTimeRange = defaultTimeRanges.find(tr => tr.default); export const dateFormats = { ISODate: "yyyy-mm-dd'T'HH:MM:ss'Z'", @@ -68,46 +60,6 @@ export const isValidDate = dateString => { }; /** - * For a given time window key (e.g. `threeHours`) and key-value pair - * object of time windows. - * - * Returns a date time range with start and end. - * - * @param {String} timeWindowKey - A key in the object of time windows. - * @param {Object} timeWindows - A key-value pair of time windows, - * with a second duration and a label. - * @returns An object with time range, start and end dates, in ISO format. - */ -export const getTimeRange = (timeWindowKey, timeWindows = defaultTimeWindows) => { - let difference; - if (timeWindows[timeWindowKey]) { - difference = timeWindows[timeWindowKey].seconds; - } else { - const [defaultEntry] = Object.entries(timeWindows).filter( - ([, timeWindow]) => timeWindow.default, - ); - // find default time window - difference = defaultEntry[1].seconds; - } - - const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds - const start = end - difference; - - return { - start: new Date(secondsToMilliseconds(start)).toISOString(), - end: new Date(secondsToMilliseconds(end)).toISOString(), - }; -}; - -export const getTimeWindowKey = ({ start, end }, timeWindows = defaultTimeWindows) => - Object.entries(timeWindows).reduce((acc, [timeWindowKey, timeWindow]) => { - if (new Date(end) - new Date(start) === secondsToMilliseconds(timeWindow.seconds)) { - return timeWindowKey; - } - return acc; - }, null); - -/** * Convert the input in Time picker component to ISO date. * * @param {string} val |