summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/vue_shared')
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_cd_analytics/constants.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/constants.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/group_select/constants.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/group_select/group_select.vue91
-rw-r--r--app/assets/javascripts/vue_shared/components/group_select/init_group_selects.js48
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/listbox_input/init_listbox_inputs.js42
-rw-r--r--app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue58
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue38
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue21
-rw-r--r--app/assets/javascripts/vue_shared/components/navigation_tabs.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js63
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue114
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/constants.js73
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/graphql/get_runner_platforms.query.graphql (renamed from app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql)0
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/graphql/get_runner_setup.query.graphql (renamed from app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql)0
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue123
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue169
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue35
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue35
-rw-r--r--app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue201
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/constants.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue14
-rw-r--r--app/assets/javascripts/vue_shared/components/web_ide_link.vue15
-rw-r--r--app/assets/javascripts/vue_shared/constants.js2
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue5
-rw-r--r--app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue2
36 files changed, 813 insertions, 425 deletions
diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
index c93057c491c..271cfd210a6 100644
--- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
@@ -66,6 +66,7 @@ export default {
<template>
<gl-link
v-gl-tooltip
+ class="gl-display-inline-flex gl-align-items-center gl-line-height-0 gl-px-3 gl-py-2 gl-rounded-base"
:class="cssClass"
:title="title"
data-qa-selector="status_badge_link"
@@ -75,7 +76,7 @@ export default {
<ci-icon :status="status" :css-classes="iconClasses" />
<template v-if="showText">
- {{ status.text }}
+ <span class="gl-ml-2">{{ status.text }}</span>
</template>
</gl-link>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
index 6a03e38a31d..47b96934420 100644
--- a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue
@@ -2,6 +2,7 @@
import { s__, sprintf } from '~/locale';
import SegmentedControlButtonGroup from '~/vue_shared/components/segmented_control_button_group.vue';
import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
+import { DEFAULT_SELECTED_CHART } from './constants';
export default {
components: {
@@ -20,7 +21,7 @@ export default {
},
data() {
return {
- selectedChart: 0,
+ selectedChart: DEFAULT_SELECTED_CHART,
};
},
computed: {
diff --git a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/constants.js b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/constants.js
index 1561674c0ad..3ac632b4690 100644
--- a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/constants.js
+++ b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/constants.js
@@ -1 +1,2 @@
export const CHART_CONTAINER_HEIGHT = 300;
+export const DEFAULT_SELECTED_CHART = 2;
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index 8bffc2479a1..0d7547d88a1 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -69,7 +69,7 @@ export default {
computed: {
wrapperStyleClasses() {
const status = this.status.group;
- return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status} gl-rounded-full gl-justify-content-center`;
+ return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status} gl-rounded-full gl-justify-content-center gl-line-height-0`;
},
icon() {
return this.isBorderless ? `${this.status.icon}_borderless` : this.status.icon;
diff --git a/app/assets/javascripts/vue_shared/components/constants.js b/app/assets/javascripts/vue_shared/components/constants.js
new file mode 100644
index 00000000000..b7ff715922d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/constants.js
@@ -0,0 +1,4 @@
+export const KEY_EDIT = 'edit';
+export const KEY_WEB_IDE = 'webide';
+export const KEY_GITPOD = 'gitpod';
+export const KEY_PIPELINE_EDITOR = 'pipeline_editor';
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
index 74905dc2ae0..9c30ec67d5a 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue
@@ -67,6 +67,7 @@ export default {
:suggestions="emojis"
:suggestions-loading="loading"
:get-active-token-value="getActiveEmoji"
+ value-identifier="name"
v-bind="$attrs"
@fetch-suggestions="fetchEmojis"
v-on="$listeners"
diff --git a/app/assets/javascripts/vue_shared/components/group_select/constants.js b/app/assets/javascripts/vue_shared/components/group_select/constants.js
index bc70936eb36..06537d682fe 100644
--- a/app/assets/javascripts/vue_shared/components/group_select/constants.js
+++ b/app/assets/javascripts/vue_shared/components/group_select/constants.js
@@ -1,6 +1,7 @@
import { __ } from '~/locale';
export const TOGGLE_TEXT = __('Search for a group');
+export const RESET_LABEL = __('Reset');
export const FETCH_GROUPS_ERROR = __('Unable to fetch groups. Reload the page to try again.');
export const FETCH_GROUP_ERROR = __('Unable to fetch group. Reload the page to try again.');
export const QUERY_TOO_SHORT_MESSAGE = __('Enter at least three characters to search.');
diff --git a/app/assets/javascripts/vue_shared/components/group_select/group_select.vue b/app/assets/javascripts/vue_shared/components/group_select/group_select.vue
index 5db723e1e5a..d295052e2ce 100644
--- a/app/assets/javascripts/vue_shared/components/group_select/group_select.vue
+++ b/app/assets/javascripts/vue_shared/components/group_select/group_select.vue
@@ -1,26 +1,35 @@
<script>
import { debounce } from 'lodash';
-import { GlCollapsibleListbox } from '@gitlab/ui';
+import { GlFormGroup, GlAlert, GlCollapsibleListbox } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
import axios from '~/lib/utils/axios_utils';
+import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
import Api from '~/api';
import { __ } from '~/locale';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import { createAlert } from '~/flash';
import { groupsPath } from './utils';
import {
TOGGLE_TEXT,
+ RESET_LABEL,
FETCH_GROUPS_ERROR,
FETCH_GROUP_ERROR,
QUERY_TOO_SHORT_MESSAGE,
} from './constants';
const MINIMUM_QUERY_LENGTH = 3;
+const GROUPS_PER_PAGE = 20;
export default {
components: {
+ GlFormGroup,
+ GlAlert,
GlCollapsibleListbox,
},
props: {
+ label: {
+ type: String,
+ required: true,
+ },
inputName: {
type: String,
required: true,
@@ -54,10 +63,14 @@ export default {
return {
pristine: true,
searching: false,
+ hasMoreGroups: true,
+ infiniteScrollLoading: false,
searchString: '',
groups: [],
+ page: 1,
selectedValue: null,
selectedText: null,
+ errorMessage: '',
};
},
computed: {
@@ -74,6 +87,9 @@ export default {
toggleText() {
return this.selectedText ?? this.$options.i18n.toggleText;
},
+ resetButtonLabel() {
+ return this.clearable ? RESET_LABEL : '';
+ },
inputValue() {
return this.selectedValue ? this.selectedValue : '';
},
@@ -95,35 +111,48 @@ export default {
if (this.isSearchQueryTooShort) {
this.groups = [];
} else {
- this.fetchGroups(searchString);
+ this.fetchGroups();
}
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
- async fetchGroups(searchString = '') {
- this.searching = true;
+ async fetchGroups(page = 1) {
+ if (page === 1) {
+ this.searching = true;
+ this.groups = [];
+ this.hasMoreGroups = true;
+ } else {
+ this.infiniteScrollLoading = true;
+ }
try {
- const { data } = await axios.get(
+ const { data, headers } = await axios.get(
Api.buildUrl(groupsPath(this.groupsFilter, this.parentGroupID)),
{
params: {
- search: searchString,
+ search: this.searchString,
+ per_page: GROUPS_PER_PAGE,
+ page,
},
},
);
const groups = data.length ? data : data.results || [];
- this.groups = groups.map((group) => ({
- ...group,
- value: String(group.id),
- }));
+ this.groups.push(
+ ...groups.map((group) => ({
+ ...group,
+ value: String(group.id),
+ })),
+ );
+
+ const { totalPages } = parseIntPagination(normalizeHeaders(headers));
+ if (page === totalPages) {
+ this.hasMoreGroups = false;
+ }
+ this.page = page;
this.searching = false;
+ this.infiniteScrollLoading = false;
} catch (error) {
- createAlert({
- message: FETCH_GROUPS_ERROR,
- error,
- parent: this.$el,
- });
+ this.handleError({ message: FETCH_GROUPS_ERROR, error });
}
},
async fetchInitialSelection() {
@@ -139,11 +168,7 @@ export default {
this.pristine = false;
this.searching = false;
} catch (error) {
- createAlert({
- message: FETCH_GROUP_ERROR,
- error,
- parent: this.$el,
- });
+ this.handleError({ message: FETCH_GROUP_ERROR, error });
}
},
onShown() {
@@ -154,11 +179,20 @@ export default {
onReset() {
this.selected = null;
},
+ onBottomReached() {
+ this.fetchGroups(this.page + 1);
+ },
+ handleError({ message, error }) {
+ Sentry.captureException(error);
+ this.errorMessage = message;
+ },
+ dismissError() {
+ this.errorMessage = '';
+ },
},
i18n: {
toggleText: TOGGLE_TEXT,
selectGroup: __('Select a group'),
- reset: __('Reset'),
noResultsText: __('No results found.'),
searchQueryTooShort: QUERY_TOO_SHORT_MESSAGE,
},
@@ -166,21 +200,27 @@ export default {
</script>
<template>
- <div>
+ <gl-form-group :label="label">
+ <gl-alert v-if="errorMessage" class="gl-mb-3" variant="danger" @dismiss="dismissError">{{
+ errorMessage
+ }}</gl-alert>
<gl-collapsible-listbox
ref="listbox"
v-model="selected"
:header-text="$options.i18n.selectGroup"
- :reset-button-label="$options.i18n.reset"
+ :reset-button-label="resetButtonLabel"
:toggle-text="toggleText"
:loading="searching && pristine"
:searching="searching"
:items="groups"
:no-results-text="noResultsText"
+ :infinite-scroll="hasMoreGroups"
+ :infinite-scroll-loading="infiniteScrollLoading"
searchable
@shown="onShown"
@search="search"
@reset="onReset"
+ @bottom-reached="onBottomReached"
>
<template #list-item="{ item }">
<div class="gl-font-weight-bold">
@@ -189,7 +229,6 @@ export default {
<div class="gl-text-gray-300">{{ item.full_path }}</div>
</template>
</gl-collapsible-listbox>
- <div class="flash-container"></div>
<input :id="inputId" data-testid="input" type="hidden" :name="inputName" :value="inputValue" />
- </div>
+ </gl-form-group>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/group_select/init_group_selects.js b/app/assets/javascripts/vue_shared/components/group_select/init_group_selects.js
new file mode 100644
index 00000000000..dbfac8a0339
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/group_select/init_group_selects.js
@@ -0,0 +1,48 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import GroupSelect from './group_select.vue';
+
+const SELECTOR = '.js-vue-group-select';
+
+export const initGroupSelects = () => {
+ if (process.env.NODE_ENV !== 'production' && document.querySelector(SELECTOR) === null) {
+ // eslint-disable-next-line no-console
+ console.warn(`Attempted to initialize GroupSelect but '${SELECTOR}' not found in the page`);
+ }
+
+ [...document.querySelectorAll(SELECTOR)].forEach((el) => {
+ const {
+ parentId: parentGroupID,
+ groupsFilter,
+ label,
+ inputName,
+ inputId,
+ selected: initialSelection,
+ testid,
+ } = el.dataset;
+ const clearable = parseBoolean(el.dataset.clearable);
+
+ return new Vue({
+ el,
+ components: {
+ GroupSelect,
+ },
+ render(createElement) {
+ return createElement(GroupSelect, {
+ props: {
+ label,
+ inputName,
+ initialSelection,
+ parentGroupID,
+ groupsFilter,
+ inputId,
+ clearable,
+ },
+ attrs: {
+ 'data-testid': testid,
+ },
+ });
+ },
+ });
+ });
+};
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 3c4ae08d2f7..8e459cc21ac 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -75,6 +75,10 @@ export default {
// GraphQL returns `avatarUrl` and Rest `avatar_url`
return this.user?.avatarUrl || this.user?.avatar_url;
},
+ webUrl() {
+ // GraphQL returns `webUrl` and Rest `web_url`
+ return this.user?.webUrl || this.user?.web_url;
+ },
statusTooltipHTML() {
// Rest `status_tooltip_html` which is a ready to work
// html for the emoji and the status text inside a tooltip.
@@ -132,7 +136,7 @@ export default {
:data-user-id="userId"
:data-username="user.username"
:data-name="user.name"
- :href="user.webUrl"
+ :href="webUrl"
target="_blank"
class="js-user-link gl-vertical-align-middle gl-mx-2 gl-align-items-center"
>
diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/init_listbox_inputs.js b/app/assets/javascripts/vue_shared/components/listbox_input/init_listbox_inputs.js
new file mode 100644
index 00000000000..ad89b78b521
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/listbox_input/init_listbox_inputs.js
@@ -0,0 +1,42 @@
+import Vue from 'vue';
+import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue';
+
+export const initListboxInputs = () => {
+ const els = [...document.querySelectorAll('.js-listbox-input')];
+
+ els.forEach((el, index) => {
+ const { label, description, name, defaultToggleText, value = null } = el.dataset;
+ const { id } = el;
+ const items = JSON.parse(el.dataset.items);
+
+ return new Vue({
+ el,
+ name: `ListboxInputRoot${index + 1}`,
+ data() {
+ return {
+ selected: value,
+ };
+ },
+ render(createElement) {
+ return createElement(ListboxInput, {
+ on: {
+ select: (newValue) => {
+ this.selected = newValue;
+ },
+ },
+ props: {
+ label,
+ description,
+ name,
+ defaultToggleText,
+ selected: this.selected,
+ items,
+ },
+ attrs: {
+ id,
+ },
+ });
+ },
+ });
+ });
+};
diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue
index b1809e6a9f3..bc6b5d3176f 100644
--- a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue
+++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue
@@ -1,25 +1,37 @@
<script>
-import { GlListbox } from '@gitlab/ui';
+import { GlFormGroup, GlListbox } from '@gitlab/ui';
import { __ } from '~/locale';
-const MIN_ITEMS_COUNT_FOR_SEARCHING = 20;
+const MIN_ITEMS_COUNT_FOR_SEARCHING = 10;
export default {
i18n: {
noResultsText: __('No results found'),
},
components: {
+ GlFormGroup,
GlListbox,
},
model: GlListbox.model,
props: {
+ label: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ description: {
+ type: String,
+ required: false,
+ default: '',
+ },
name: {
type: String,
required: true,
},
defaultToggleText: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
selected: {
type: String,
@@ -30,6 +42,11 @@ export default {
type: GlListbox.props.items.type,
required: true,
},
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -37,6 +54,9 @@ export default {
};
},
computed: {
+ wrapperComponent() {
+ return this.label || this.description ? 'gl-form-group' : 'div';
+ },
allOptions() {
const allOptions = [];
@@ -95,16 +115,17 @@ export default {
</script>
<template>
- <div>
+ <component :is="wrapperComponent" :label="label" :description="description" v-bind="$attrs">
<gl-listbox
:selected="selected"
:toggle-text="toggleText"
:items="filteredItems"
:searchable="isSearchable"
:no-results-text="$options.i18n.noResultsText"
+ :disabled="disabled"
@search="search"
@select="$emit($options.model.event, $event)"
/>
<input ref="input" type="hidden" :name="name" :value="selected" />
- </div>
+ </component>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue
new file mode 100644
index 00000000000..6702a81e747
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/markdown/editor_mode_dropdown.vue
@@ -0,0 +1,58 @@
+<script>
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ props: {
+ size: {
+ type: String,
+ required: false,
+ default: 'medium',
+ },
+ value: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ markdownEditorSelected() {
+ return this.value === 'markdown';
+ },
+ text() {
+ return this.markdownEditorSelected ? __('View rich text') : __('View markdown');
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown
+ category="tertiary"
+ data-qa-selector="editing_mode_switcher"
+ :size="size"
+ :text="text"
+ right
+ >
+ <gl-dropdown-item
+ is-check-item
+ :is-checked="!markdownEditorSelected"
+ @click="$emit('input', 'richText')"
+ ><div class="gl-font-weight-bold">{{ __('Rich text') }}</div>
+ <div class="gl-text-secondary">
+ {{ __('View the formatted output in real-time as you edit.') }}
+ </div>
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ is-check-item
+ :is-checked="markdownEditorSelected"
+ @click="$emit('input', 'markdown')"
+ ><div class="gl-font-weight-bold">{{ __('Markdown') }}</div>
+ <div class="gl-text-secondary">
+ {{ __('View and edit markdown, with the option to preview the formatted output.') }}
+ </div></gl-dropdown-item
+ >
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index b5f2602af5e..7b76fc3fc6d 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -122,6 +122,11 @@ export default {
required: false,
default: () => [],
},
+ showContentEditorSwitcher: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -364,6 +369,8 @@ export default {
:quick-actions-docs-path="quickActionsDocsPath"
:can-attach-file="canAttachFile"
:show-comment-tool-bar="showCommentToolBar"
+ :show-content-editor-switcher="showContentEditorSwitcher"
+ @enableContentEditor="$emit('enableContentEditor')"
/>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 89fffdedbfd..e83441e59a2 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -10,6 +10,7 @@ import {
INDENT_LINE,
OUTDENT_LINE,
} from '~/behaviors/shortcuts/keybindings';
+import { getModifierKey } from '~/constants';
import { getSelectedFragment } from '~/lib/utils/common_utils';
import { s__, __ } from '~/locale';
import { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
@@ -66,6 +67,7 @@ export default {
return {
tag: '> ',
suggestPopoverVisible: false,
+ modifierKey: getModifierKey(),
};
},
computed: {
@@ -90,15 +92,6 @@ export default {
const expandText = s__('MarkdownEditor|Click to expand');
return [`<details><summary>${expandText}</summary>`, `{text}`, '</details>'].join('\n');
},
- isMac() {
- // Accessing properties using ?. to allow tests to use
- // this component without setting up window.gl.client.
- // In production, window.gl.client should always be present.
- return Boolean(window.gl?.client?.isMac);
- },
- modifierKey() {
- return this.isMac ? '⌘' : s__('KeyboardKey|Ctrl+');
- },
},
watch: {
showSuggestPopover() {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
index d01eae0308f..c53118b9f62 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/markdown_editor.vue
@@ -1,16 +1,13 @@
<script>
-import { GlSegmentedControl } from '@gitlab/ui';
-import { __ } from '~/locale';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import axios from '~/lib/utils/axios_utils';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { EDITING_MODE_MARKDOWN_FIELD, EDITING_MODE_CONTENT_EDITOR } from '../../constants';
import MarkdownField from './field.vue';
export default {
components: {
- MarkdownField,
LocalStorageSync,
- GlSegmentedControl,
+ MarkdownField,
ContentEditor: () =>
import(
/* webpackChunkName: 'content_editor' */ '~/content_editor/components/content_editor.vue'
@@ -91,7 +88,6 @@ export default {
data() {
return {
editingMode: EDITING_MODE_MARKDOWN_FIELD,
- switchEditingControlEnabled: true,
autofocused: false,
};
},
@@ -114,19 +110,16 @@ export default {
updateMarkdownFromMarkdownField({ target }) {
this.$emit('input', target.value);
},
- enableSwitchEditingControl() {
- this.switchEditingControlEnabled = true;
- },
- disableSwitchEditingControl() {
- this.switchEditingControlEnabled = false;
- },
renderMarkdown(markdown) {
return axios.post(this.renderMarkdownPath, { text: markdown }).then(({ data }) => data.body);
},
onEditingModeChange(editingMode) {
+ this.editingMode = editingMode;
this.notifyEditingModeChange(editingMode);
},
onEditingModeRestored(editingMode) {
+ this.editingMode = editingMode;
+ this.$emit(editingMode);
this.notifyEditingModeChange(editingMode);
},
notifyEditingModeChange(editingMode) {
@@ -142,25 +135,10 @@ export default {
this.autofocused = true;
},
},
- switchEditingControlOptions: [
- { text: __('Source'), value: EDITING_MODE_MARKDOWN_FIELD },
- { text: __('Rich text'), value: EDITING_MODE_CONTENT_EDITOR },
- ],
};
</script>
<template>
<div>
- <div class="gl-display-flex gl-justify-content-start gl-mb-3">
- <gl-segmented-control
- v-model="editingMode"
- data-testid="toggle-editing-mode-button"
- data-qa-selector="editing_mode_button"
- class="gl-display-flex"
- :options="$options.switchEditingControlOptions"
- :disabled="!enableContentEditor || !switchEditingControlEnabled"
- @change="onEditingModeChange"
- />
- </div>
<local-storage-sync
v-model="editingMode"
storage-key="gl-wiki-content-editor-enabled"
@@ -176,7 +154,9 @@ export default {
:quick-actions-docs-path="quickActionsDocsPath"
:uploads-path="uploadsPath"
:enable-preview="enablePreview"
+ show-content-editor-switcher
class="bordered-box"
+ @enableContentEditor="onEditingModeChange('contentEditor')"
>
<template #textarea>
<textarea
@@ -205,10 +185,8 @@ export default {
:use-bottom-toolbar="useBottomToolbar"
@initialized="setEditorAsAutofocused"
@change="updateMarkdownFromContentEditor"
- @loading="disableSwitchEditingControl"
- @loadingSuccess="enableSwitchEditingControl"
- @loadingError="enableSwitchEditingControl"
@keydown="$emit('keydown', $event)"
+ @enableMarkdownEditor="onEditingModeChange('markdownField')"
/>
<input
:id="formFieldId"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index b5640e12541..e8be242f660 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton, GlLink, GlLoadingIcon, GlSprintf, GlIcon } from '@gitlab/ui';
+import EditorModeDropdown from './editor_mode_dropdown.vue';
export default {
components: {
@@ -8,6 +9,7 @@ export default {
GlLoadingIcon,
GlSprintf,
GlIcon,
+ EditorModeDropdown,
},
props: {
markdownDocsPath: {
@@ -29,12 +31,24 @@ export default {
required: false,
default: true,
},
+ showContentEditorSwitcher: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
hasQuickActionsDocsPath() {
return this.quickActionsDocsPath !== '';
},
},
+ methods: {
+ handleEditorModeChanged(mode) {
+ if (mode === 'richText') {
+ this.$emit('enableContentEditor');
+ }
+ },
+ },
};
</script>
@@ -121,5 +135,12 @@ export default {
{{ __('Cancel') }}
</gl-button>
</span>
+ <editor-mode-dropdown
+ v-if="showContentEditorSwitcher"
+ size="small"
+ class="gl-float-right gl-line-height-28 gl-display-block"
+ value="markdown"
+ @input="handleEditorModeChanged"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index 21212e82de4..c83643ca4de 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -1,6 +1,6 @@
<script>
import { GlBadge, GlTabs, GlTab } from '@gitlab/ui';
-import $ from 'jquery';
+import { initScrollingTabs } from '~/layout_nav';
/**
* Given an array of tabs, renders non linked bootstrap tabs.
@@ -41,7 +41,7 @@ export default {
},
},
mounted() {
- $(document).trigger('init.scrolling-tabs');
+ initScrollingTabs();
},
methods: {
shouldRenderBadge(count) {
diff --git a/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue b/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
index 5f2a66ee0b7..e1f042f78ab 100644
--- a/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/pagination/table_pagination.vue
@@ -64,8 +64,9 @@ export default {
<template>
<gl-pagination
v-if="showPagination"
- class="justify-content-center gl-mt-3"
+ class="gl-mt-3"
v-bind="$attrs"
+ align="center"
:value="pageInfo.page"
:per-page="pageInfo.perPage"
:total-items="pageInfo.total"
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js
deleted file mode 100644
index 88c975b97b9..00000000000
--- a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/constants.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { s__, sprintf } from '~/locale';
-
-export const README_URL =
- 'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/easybuttons.md';
-
-export const CF_BASE_URL =
- 'https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?';
-
-export const TEMPLATES_BASE_URL = 'https://gl-public-templates.s3.amazonaws.com/cfn/experimental/';
-
-export const EASY_BUTTONS = [
- {
- stackName: 'linux-docker-nonspot',
- templateName:
- 'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml',
- description: s__(
- 'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot.',
- ),
- moreDetails1: s__('Runners|No spot. This is the default choice for Linux Docker executor.'),
- moreDetails2: s__(
- 'Runners|A capacity of 1 enables warm HA through Auto Scaling group re-spawn. A capacity of 2 enables hot HA because the service is available even when a node is lost. A capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
- ),
- },
- {
- stackName: 'linux-docker-spotonly',
- templateName: 'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-spotonly.cf.yml',
- description: sprintf(
- s__(
- 'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. %{percentage} spot.',
- ),
- { percentage: '100%' },
- ),
- moreDetails1: sprintf(s__('Runners|%{percentage} spot.'), { percentage: '100%' }),
- moreDetails2: s__(
- 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
- ),
- },
- {
- stackName: 'win2019-shell-non-spot',
- templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml',
- description: s__(
- 'Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot.',
- ),
- moreDetails1: s__('Runners|No spot. Default choice for Windows Shell executor.'),
- moreDetails2: s__(
- 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
- ),
- },
- {
- stackName: 'win2019-shell-spot',
- templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-spotonly.cf.yml',
- description: sprintf(
- s__(
- 'Runners|Windows 2019 Shell with manual scaling and optional scheduling. %{percentage} spot.',
- ),
- { percentage: '100%' },
- ),
- moreDetails1: sprintf(s__('Runners|%{percentage} spot.'), { percentage: '100%' }),
- moreDetails2: s__(
- 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
- ),
- },
-];
diff --git a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
index eee65d90285..08acde1aefc 100644
--- a/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_aws_deployments/runner_aws_deployments_modal.vue
@@ -1,125 +1,29 @@
<script>
-import {
- GlModal,
- GlSprintf,
- GlLink,
- GlFormRadioGroup,
- GlFormRadio,
- GlAccordion,
- GlAccordionItem,
-} from '@gitlab/ui';
-import Tracking from '~/tracking';
-import { getBaseURL, objectToQuery, visitUrl } from '~/lib/utils/url_utility';
-import { __, s__ } from '~/locale';
-import { README_URL, CF_BASE_URL, TEMPLATES_BASE_URL, EASY_BUTTONS } from './constants';
+import { GlModal } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import RunnerAwsInstructions from '~/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue';
export default {
components: {
GlModal,
- GlSprintf,
- GlLink,
- GlFormRadioGroup,
- GlFormRadio,
- GlAccordion,
- GlAccordionItem,
+ RunnerAwsInstructions,
},
- mixins: [Tracking.mixin()],
props: {
modalId: {
type: String,
required: true,
},
},
- data() {
- return {
- selected: this.$options.easyButtons[0],
- };
- },
methods: {
- borderBottom(idx) {
- return idx < this.$options.easyButtons.length - 1;
- },
- easyButtonUrl(easyButton) {
- const params = {
- templateURL: TEMPLATES_BASE_URL + easyButton.templateName,
- stackName: easyButton.stackName,
- param_3GITLABRunnerInstanceURL: getBaseURL(),
- };
- return CF_BASE_URL + objectToQuery(params);
- },
- trackCiRunnerTemplatesClick(stackName) {
- this.track('template_clicked', {
- label: stackName,
- });
- },
- handleModalPrimary() {
- this.trackCiRunnerTemplatesClick(this.selected.stackName);
- visitUrl(this.easyButtonUrl(this.selected), true);
+ onClose() {
+ this.$refs.modal.close();
},
},
- i18n: {
- title: s__('Runners|Deploy GitLab Runner in AWS'),
- instructions: s__(
- 'Runners|Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console.',
- ),
- chooseRunner: s__('Runners|Choose your preferred GitLab Runner'),
- dontSeeWhatYouAreLookingFor: s__(
- "Runners|Don't see what you are looking for? See the full list of options, including a fully customizable option %{linkStart}here%{linkEnd}.",
- ),
- moreDetails: __('More Details'),
- lessDetails: __('Less Details'),
- },
- deployButton: {
- text: s__('Runners|Deploy GitLab Runner in AWS'),
- attributes: [{ variant: 'confirm' }],
- },
- closeButton: {
- text: __('Cancel'),
- attributes: [{ variant: 'default' }],
- },
- readmeUrl: README_URL,
- easyButtons: EASY_BUTTONS,
+ i18n_title: s__('Runners|Deploy GitLab Runner in AWS'),
};
</script>
<template>
- <gl-modal
- :modal-id="modalId"
- :title="$options.i18n.title"
- :action-primary="$options.deployButton"
- :action-secondary="$options.closeButton"
- size="sm"
- @primary="handleModalPrimary"
- >
- <p>{{ $options.i18n.instructions }}</p>
- <gl-form-radio-group v-model="selected" :label="$options.i18n.chooseRunner" label-sr-only>
- <gl-form-radio
- v-for="(easyButton, idx) in $options.easyButtons"
- :key="easyButton.templateName"
- :value="easyButton"
- class="gl-py-5 gl-pl-8"
- :class="{ 'gl-border-b': borderBottom(idx) }"
- >
- <div class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold">
- {{ easyButton.description }}
- <gl-accordion :header-level="3" class="gl-pt-3">
- <gl-accordion-item
- :title="$options.i18n.moreDetails"
- :title-visible="$options.i18n.lessDetails"
- class="gl-font-weight-normal"
- >
- <p class="gl-pt-2">{{ easyButton.moreDetails1 }}</p>
- <p class="gl-m-0">{{ easyButton.moreDetails2 }}</p>
- </gl-accordion-item>
- </gl-accordion>
- </div>
- </gl-form-radio>
- </gl-form-radio-group>
- <p>
- <gl-sprintf :message="$options.i18n.dontSeeWhatYouAreLookingFor">
- <template #link="{ content }">
- <gl-link :href="$options.readmeUrl" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </p>
+ <gl-modal ref="modal" :modal-id="modalId" :title="$options.i18n_title" hide-footer size="sm">
+ <runner-aws-instructions @close="onClose" />
</gl-modal>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
index c97e191b630..3dbc5246c3d 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/constants.js
@@ -1,18 +1,69 @@
-import { s__ } from '~/locale';
+import { s__, sprintf } from '~/locale';
export const REGISTRATION_TOKEN_PLACEHOLDER = '$REGISTRATION_TOKEN';
-export const INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES = {
- docker: {
- instructions: s__(
- 'Runners|To install Runner in a container follow the instructions described in the GitLab documentation',
+export const PLATFORM_DOCKER = 'docker';
+export const PLATFORM_KUBERNETES = 'kubernetes';
+
+export const AWS_README_URL =
+ 'https://gitlab.com/guided-explorations/aws/gitlab-runner-autoscaling-aws-asg/-/blob/main/easybuttons.md';
+
+export const AWS_CF_BASE_URL =
+ 'https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/create/review?';
+
+export const AWS_TEMPLATES_BASE_URL =
+ 'https://gl-public-templates.s3.amazonaws.com/cfn/experimental/';
+
+export const AWS_EASY_BUTTONS = [
+ {
+ stackName: 'linux-docker-nonspot',
+ templateName:
+ 'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-ondemandonly.cf.yml',
+ description: s__(
+ 'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. Non-spot.',
+ ),
+ moreDetails1: s__('Runners|No spot. This is the default choice for Linux Docker executor.'),
+ moreDetails2: s__(
+ 'Runners|A capacity of 1 enables warm HA through Auto Scaling group re-spawn. A capacity of 2 enables hot HA because the service is available even when a node is lost. A capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
+ ),
+ },
+ {
+ stackName: 'linux-docker-spotonly',
+ templateName: 'easybutton-amazon-linux-2-docker-manual-scaling-with-schedule-spotonly.cf.yml',
+ description: sprintf(
+ s__(
+ 'Runners|Amazon Linux 2 Docker HA with manual scaling and optional scheduling. %{percentage} spot.',
+ ),
+ { percentage: '100%' },
+ ),
+ moreDetails1: sprintf(s__('Runners|%{percentage} spot.'), { percentage: '100%' }),
+ moreDetails2: s__(
+ 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
+ ),
+ },
+ {
+ stackName: 'win2019-shell-non-spot',
+ templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-ondemandonly.cf.yml',
+ description: s__(
+ 'Runners|Windows 2019 Shell with manual scaling and optional scheduling. Non-spot.',
+ ),
+ moreDetails1: s__('Runners|No spot. Default choice for Windows Shell executor.'),
+ moreDetails2: s__(
+ 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
),
- link: 'https://docs.gitlab.com/runner/install/docker.html',
},
- kubernetes: {
- instructions: s__(
- 'Runners|To install Runner in Kubernetes follow the instructions described in the GitLab documentation.',
+ {
+ stackName: 'win2019-shell-spot',
+ templateName: 'easybutton-windows2019-shell-manual-scaling-with-scheduling-spotonly.cf.yml',
+ description: sprintf(
+ s__(
+ 'Runners|Windows 2019 Shell with manual scaling and optional scheduling. %{percentage} spot.',
+ ),
+ { percentage: '100%' },
+ ),
+ moreDetails1: sprintf(s__('Runners|%{percentage} spot.'), { percentage: '100%' }),
+ moreDetails2: s__(
+ 'Runners|Capacity of 1 enables warm HA through Auto Scaling group re-spawn. Capacity of 2 enables hot HA because the service is available even when a node is lost. Capacity of 3 or more enables hot HA and manual scaling of runner fleet.',
),
- link: 'https://docs.gitlab.com/runner/install/kubernetes.html',
},
-};
+];
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/get_runner_platforms.query.graphql
index 76f152e5453..76f152e5453 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/get_runner_platforms.query.graphql
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/get_runner_setup.query.graphql
index c0248a35e3f..c0248a35e3f 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/graphql/get_runner_setup.query.graphql
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue
new file mode 100644
index 00000000000..cafebdfe5f4
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_aws_instructions.vue
@@ -0,0 +1,123 @@
+<script>
+import {
+ GlButton,
+ GlSprintf,
+ GlLink,
+ GlFormRadioGroup,
+ GlFormRadio,
+ GlAccordion,
+ GlAccordionItem,
+} from '@gitlab/ui';
+import Tracking from '~/tracking';
+import { getBaseURL, objectToQuery, visitUrl } from '~/lib/utils/url_utility';
+import { __, s__ } from '~/locale';
+import {
+ AWS_README_URL,
+ AWS_CF_BASE_URL,
+ AWS_TEMPLATES_BASE_URL,
+ AWS_EASY_BUTTONS,
+} from '../constants';
+
+export default {
+ components: {
+ GlButton,
+ GlSprintf,
+ GlLink,
+ GlFormRadioGroup,
+ GlFormRadio,
+ GlAccordion,
+ GlAccordionItem,
+ },
+ mixins: [Tracking.mixin()],
+ data() {
+ return {
+ selectedIndex: 0,
+ };
+ },
+ computed: {
+ selected() {
+ return this.$options.easyButtons[this.selectedIndex];
+ },
+ },
+ methods: {
+ borderBottom(idx) {
+ return idx < this.$options.easyButtons.length - 1;
+ },
+ easyButtonUrl(easyButton) {
+ const params = {
+ templateURL: AWS_TEMPLATES_BASE_URL + easyButton.templateName,
+ stackName: easyButton.stackName,
+ param_3GITLABRunnerInstanceURL: getBaseURL(),
+ };
+ return AWS_CF_BASE_URL + objectToQuery(params);
+ },
+ trackCiRunnerTemplatesClick(stackName) {
+ this.track('template_clicked', {
+ label: stackName,
+ });
+ },
+ onOk() {
+ this.trackCiRunnerTemplatesClick(this.selected.stackName);
+ visitUrl(this.easyButtonUrl(this.selected), true);
+ },
+ onClose() {
+ this.$emit('close');
+ },
+ },
+ i18n: {
+ title: s__('Runners|Deploy GitLab Runner in AWS'),
+ instructions: s__(
+ 'Runners|Select your preferred option here. In the next step, you can choose the capacity for your runner in the AWS CloudFormation console.',
+ ),
+ chooseRunner: s__('Runners|Choose your preferred GitLab Runner'),
+ dontSeeWhatYouAreLookingFor: s__(
+ "Runners|Don't see what you are looking for? See the full list of options, including a fully customizable option %{linkStart}here%{linkEnd}.",
+ ),
+ moreDetails: __('More Details'),
+ lessDetails: __('Less Details'),
+ },
+ readmeUrl: AWS_README_URL,
+ easyButtons: AWS_EASY_BUTTONS,
+};
+</script>
+<template>
+ <div>
+ <p>{{ $options.i18n.instructions }}</p>
+ <gl-form-radio-group v-model="selectedIndex" :label="$options.i18n.chooseRunner" label-sr-only>
+ <gl-form-radio
+ v-for="(easyButton, idx) in $options.easyButtons"
+ :key="easyButton.templateName"
+ :value="idx"
+ class="gl-py-5 gl-pl-8"
+ :class="{ 'gl-border-b': borderBottom(idx) }"
+ >
+ <div class="gl-mt-n1 gl-pl-4 gl-pb-2 gl-font-weight-bold">
+ {{ easyButton.description }}
+ <gl-accordion :header-level="3" class="gl-pt-3">
+ <gl-accordion-item
+ :title="$options.i18n.moreDetails"
+ :title-visible="$options.i18n.lessDetails"
+ class="gl-font-weight-normal"
+ >
+ <p class="gl-pt-2">{{ easyButton.moreDetails1 }}</p>
+ <p class="gl-m-0">{{ easyButton.moreDetails2 }}</p>
+ </gl-accordion-item>
+ </gl-accordion>
+ </div>
+ </gl-form-radio>
+ </gl-form-radio-group>
+ <p>
+ <gl-sprintf :message="$options.i18n.dontSeeWhatYouAreLookingFor">
+ <template #link="{ content }">
+ <gl-link :href="$options.readmeUrl" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </p>
+ <footer class="gl-display-flex gl-justify-content-end gl-pt-3 gl-gap-3">
+ <gl-button @click="onClose()">{{ __('Close') }}</gl-button>
+ <gl-button variant="confirm" @click="onOk()">
+ {{ s__('Runners|Deploy GitLab Runner in AWS') }}
+ </gl-button>
+ </footer>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue
new file mode 100644
index 00000000000..36e608a068b
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_cli_instructions.vue
@@ -0,0 +1,169 @@
+<script>
+import { GlButton, GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
+import { REGISTRATION_TOKEN_PLACEHOLDER } from '../constants';
+import getRunnerSetupInstructionsQuery from '../graphql/get_runner_setup.query.graphql';
+
+export default {
+ components: {
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlLoadingIcon,
+ ModalCopyButton,
+ },
+ props: {
+ platform: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ registrationToken: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ selectedArchitecture: this.platform?.architectures[0] || null,
+ instructions: null,
+ };
+ },
+ apollo: {
+ instructions: {
+ query: getRunnerSetupInstructionsQuery,
+ skip() {
+ return !this.platform || !this.selectedArchitecture;
+ },
+ variables() {
+ return {
+ platform: this.platform.name,
+ architecture: this.selectedArchitecture.name,
+ };
+ },
+ update(data) {
+ return data?.runnerSetup;
+ },
+ error() {
+ this.$emit('error');
+ },
+ },
+ },
+ computed: {
+ architectures() {
+ return this.platform?.architectures || [];
+ },
+ binaryUrl() {
+ return this.selectedArchitecture?.downloadLocation;
+ },
+ registerInstructionsWithToken() {
+ const { registerInstructions } = this.instructions || {};
+
+ if (this.registrationToken) {
+ return registerInstructions?.replace(
+ REGISTRATION_TOKEN_PLACEHOLDER,
+ this.registrationToken,
+ );
+ }
+ return registerInstructions;
+ },
+ },
+ watch: {
+ platform() {
+ // reset selection if architecture is not in this list
+ const arch = this.architectures.find(({ name }) => name === this.selectedArchitecture.name);
+ if (!arch) {
+ this.selectArchitecture(this.architectures[0]);
+ }
+ },
+ },
+ methods: {
+ selectArchitecture(architecture) {
+ this.selectedArchitecture = architecture;
+ },
+ onClose() {
+ this.$emit('close');
+ },
+ },
+ i18n: {
+ architecture: s__('Runners|Architecture'),
+ downloadInstallBinary: s__('Runners|Download and install binary'),
+ downloadLatestBinary: s__('Runners|Download latest binary'),
+ registerRunnerCommand: s__('Runners|Command to register runner'),
+ copyInstructions: s__('Runners|Copy instructions'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h5>
+ {{ $options.i18n.architecture }}
+ <gl-loading-icon v-if="$apollo.loading" size="sm" inline />
+ </h5>
+
+ <gl-dropdown class="gl-mb-3" :text="selectedArchitecture.name">
+ <gl-dropdown-item
+ v-for="architecture in architectures"
+ :key="architecture.name"
+ is-check-item
+ :is-checked="selectedArchitecture.name === architecture.name"
+ data-testid="architecture-dropdown-item"
+ @click="selectArchitecture(architecture)"
+ >
+ {{ architecture.name }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ <div class="gl-sm-display-flex gl-align-items-center gl-mb-3">
+ <h5>{{ $options.i18n.downloadInstallBinary }}</h5>
+ <gl-button
+ v-if="binaryUrl"
+ class="gl-ml-auto"
+ :href="binaryUrl"
+ download
+ icon="download"
+ data-testid="binary-download-button"
+ >
+ {{ $options.i18n.downloadLatestBinary }}
+ </gl-button>
+ </div>
+
+ <template v-if="instructions">
+ <div class="gl-display-flex">
+ <pre
+ class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line"
+ data-testid="binary-instructions"
+ >{{ instructions.installInstructions }}</pre
+ >
+ <modal-copy-button
+ :title="$options.i18n.copyInstructions"
+ :text="instructions.installInstructions"
+ :modal-id="$options.modalId"
+ css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
+ category="tertiary"
+ />
+ </div>
+ <h5 class="gl-mb-3">{{ $options.i18n.registerRunnerCommand }}</h5>
+ <div class="gl-display-flex">
+ <pre
+ class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line"
+ data-testid="register-command"
+ >{{ registerInstructionsWithToken }}</pre
+ >
+ <modal-copy-button
+ :title="$options.i18n.copyInstructions"
+ :text="registerInstructionsWithToken"
+ :modal-id="$options.modalId"
+ css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
+ category="tertiary"
+ />
+ </div>
+ </template>
+
+ <footer class="gl-display-flex gl-justify-content-end gl-pt-3">
+ <gl-button @click="onClose()">{{ __('Close') }}</gl-button>
+ </footer>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue
new file mode 100644
index 00000000000..ff7e803af2a
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_docker_instructions.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlButton, GlIcon } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlButton,
+ GlIcon,
+ },
+ methods: {
+ onClose() {
+ this.$emit('close');
+ },
+ },
+ I18N_INSTRUCTIONS_TEXT: s__(
+ 'Runners|To install Runner in a container follow the instructions described in the GitLab documentation',
+ ),
+ I18N_VIEW_INSTRUCTIONS: s__('Runners|View installation instructions'),
+ HELP_URL: 'https://docs.gitlab.com/runner/install/docker.html',
+};
+</script>
+<template>
+ <div>
+ <p>
+ {{ $options.I18N_INSTRUCTIONS_TEXT }}
+ </p>
+ <gl-button :href="$options.HELP_URL">
+ <gl-icon name="external-link" />
+ {{ $options.I18N_VIEW_INSTRUCTIONS }}
+ </gl-button>
+ <footer class="gl-display-flex gl-justify-content-end gl-pt-3">
+ <gl-button @click="onClose()">{{ __('Close') }}</gl-button>
+ </footer>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue
new file mode 100644
index 00000000000..ee41dab0cec
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/instructions/runner_kubernetes_instructions.vue
@@ -0,0 +1,35 @@
+<script>
+import { GlButton, GlIcon } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlButton,
+ GlIcon,
+ },
+ methods: {
+ onClose() {
+ this.$emit('close');
+ },
+ },
+ I18N_INSTRUCTIONS_TEXT: s__(
+ 'Runners|To install Runner in Kubernetes follow the instructions described in the GitLab documentation.',
+ ),
+ I18N_VIEW_INSTRUCTIONS: s__('Runners|View installation instructions'),
+ HELP_URL: 'https://docs.gitlab.com/runner/install/kubernetes.html',
+};
+</script>
+<template>
+ <div>
+ <p>
+ {{ $options.I18N_INSTRUCTIONS_TEXT }}
+ </p>
+ <gl-button :href="$options.HELP_URL">
+ <gl-icon name="external-link" />
+ {{ $options.I18N_VIEW_INSTRUCTIONS }}
+ </gl-button>
+ <footer class="gl-display-flex gl-justify-content-end gl-pt-3">
+ <gl-button @click="onClose()">{{ __('Close') }}</gl-button>
+ </footer>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
index c5d3704ead9..729fe9c462c 100644
--- a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue
@@ -12,15 +12,13 @@ import {
GlResizeObserverDirective,
} from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import { isEmpty } from 'lodash';
import { __, s__ } from '~/locale';
-import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
-import {
- INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES,
- REGISTRATION_TOKEN_PLACEHOLDER,
-} from './constants';
-import getRunnerPlatformsQuery from './graphql/queries/get_runner_platforms.query.graphql';
-import getRunnerSetupInstructionsQuery from './graphql/queries/get_runner_setup.query.graphql';
+import getRunnerPlatformsQuery from './graphql/get_runner_platforms.query.graphql';
+import { PLATFORM_DOCKER, PLATFORM_KUBERNETES } from './constants';
+
+import RunnerCliInstructions from './instructions/runner_cli_instructions.vue';
+import RunnerDockerInstructions from './instructions/runner_docker_instructions.vue';
+import RunnerKubernetesInstructions from './instructions/runner_kubernetes_instructions.vue';
export default {
components: {
@@ -33,7 +31,7 @@ export default {
GlIcon,
GlLoadingIcon,
GlSkeletonLoader,
- ModalCopyButton,
+ RunnerDockerInstructions,
},
directives: {
GlResizeObserver: GlResizeObserverDirective,
@@ -74,27 +72,13 @@ export default {
);
},
result() {
- // If it is set and available, select the defaultSelectedPlatform.
+ // If found, select the defaultSelectedPlatform.
// Otherwise, select the first available platform
- this.selectPlatform(this.defaultPlatformName || this.platforms?.[0].name);
- },
- error() {
- this.toggleAlert(true);
- },
- },
- instructions: {
- query: getRunnerSetupInstructionsQuery,
- skip() {
- return !this.shown || !this.selectedPlatform;
- },
- variables() {
- return {
- platform: this.selectedPlatform,
- architecture: this.selectedArchitecture || '',
- };
- },
- update(data) {
- return data?.runnerSetup;
+ const platform =
+ this.platforms?.find(({ name }) => this.defaultPlatformName === name) ||
+ this.platforms?.[0];
+
+ this.selectPlatform(platform);
},
error() {
this.toggleAlert(true);
@@ -106,39 +90,23 @@ export default {
shown: false,
platforms: [],
selectedPlatform: null,
- selectedArchitecture: null,
showAlert: false,
- instructions: {},
platformsButtonGroupVertical: false,
};
},
computed: {
- instructionsEmpty() {
- return isEmpty(this.instructions);
- },
- architectures() {
- return this.platforms.find(({ name }) => name === this.selectedPlatform)?.architectures || [];
- },
- binaryUrl() {
- return this.architectures.find(({ name }) => name === this.selectedArchitecture)
- ?.downloadLocation;
- },
- instructionsWithoutArchitecture() {
- return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatform]?.instructions;
- },
- runnerInstallationLink() {
- return INSTRUCTIONS_PLATFORMS_WITHOUT_ARCHITECTURES[this.selectedPlatform]?.link;
- },
- registerInstructionsWithToken() {
- const { registerInstructions } = this.instructions || {};
-
- if (this.registrationToken) {
- return registerInstructions?.replace(
- REGISTRATION_TOKEN_PLACEHOLDER,
- this.registrationToken,
- );
+ instructionsComponent() {
+ if (this.selectedPlatform?.architectures?.length) {
+ return RunnerCliInstructions;
+ }
+ switch (this.selectedPlatform?.name) {
+ case PLATFORM_DOCKER:
+ return RunnerDockerInstructions;
+ case PLATFORM_KUBERNETES:
+ return RunnerKubernetesInstructions;
+ default:
+ return null;
}
- return registerInstructions;
},
},
updated() {
@@ -149,6 +117,12 @@ export default {
show() {
this.$refs.modal.show();
},
+ close() {
+ this.$refs.modal.close();
+ },
+ onClose() {
+ this.close();
+ },
onShown() {
this.shown = true;
this.refocusSelectedPlatformButton();
@@ -159,21 +133,13 @@ export default {
// get focused when setting a `defaultPlatformName`.
// This method refocuses the expected button.
// See more about this auto-focus: https://bootstrap-vue.org/docs/components/modal#auto-focus-on-open
- this.$refs[this.selectedPlatform]?.[0].$el.focus();
+ this.$refs[this.selectedPlatform?.name]?.[0].$el.focus();
},
- selectPlatform(platformName) {
- this.selectedPlatform = platformName;
-
- // Update architecture when platform changes
- const arch = this.architectures.find(({ name }) => name === this.selectedArchitecture);
- if (arch) {
- this.selectArchitecture(arch.name);
- } else {
- this.selectArchitecture(this.architectures[0]?.name);
- }
+ selectPlatform(platform) {
+ this.selectedPlatform = platform;
},
- selectArchitecture(architecture) {
- this.selectedArchitecture = architecture;
+ isPlatformSelected(platform) {
+ return this.selectedPlatform.name === platform.name;
},
toggleAlert(state) {
this.showAlert = state;
@@ -189,17 +155,9 @@ export default {
i18n: {
environment: __('Environment'),
installARunner: s__('Runners|Install a runner'),
- architecture: s__('Runners|Architecture'),
downloadInstallBinary: s__('Runners|Download and install binary'),
downloadLatestBinary: s__('Runners|Download latest binary'),
- registerRunnerCommand: s__('Runners|Command to register runner'),
fetchError: s__('Runners|An error has occurred fetching instructions'),
- copyInstructions: s__('Runners|Copy instructions'),
- viewInstallationInstructions: s__('Runners|View installation instructions'),
- },
- closeButton: {
- text: __('Close'),
- attributes: [{ variant: 'default' }],
},
};
</script>
@@ -208,8 +166,8 @@ export default {
ref="modal"
:modal-id="modalId"
:title="$options.i18n.installARunner"
- :action-secondary="$options.closeButton"
v-bind="$attrs"
+ hide-footer
v-on="$listeners"
@shown="onShown"
>
@@ -234,88 +192,23 @@ export default {
v-for="platform in platforms"
:key="platform.name"
:ref="platform.name"
- :selected="selectedPlatform === platform.name"
- @click="selectPlatform(platform.name)"
+ :selected="isPlatformSelected(platform)"
+ @click="selectPlatform(platform)"
>
{{ platform.humanReadableName }}
</gl-button>
</gl-button-group>
</div>
</template>
- <template v-if="architectures.length">
- <template v-if="selectedPlatform">
- <h5>
- {{ $options.i18n.architecture }}
- <gl-loading-icon v-if="$apollo.loading" size="sm" inline />
- </h5>
-
- <gl-dropdown class="gl-mb-3" :text="selectedArchitecture">
- <gl-dropdown-item
- v-for="architecture in architectures"
- :key="architecture.name"
- is-check-item
- :is-checked="selectedArchitecture === architecture.name"
- data-testid="architecture-dropdown-item"
- @click="selectArchitecture(architecture.name)"
- >
- {{ architecture.name }}
- </gl-dropdown-item>
- </gl-dropdown>
- <div class="gl-sm-display-flex gl-align-items-center gl-mb-3">
- <h5>{{ $options.i18n.downloadInstallBinary }}</h5>
- <gl-button
- v-if="binaryUrl"
- class="gl-ml-auto"
- :href="binaryUrl"
- download
- icon="download"
- data-testid="binary-download-button"
- >
- {{ $options.i18n.downloadLatestBinary }}
- </gl-button>
- </div>
- </template>
- <template v-if="!instructionsEmpty">
- <div class="gl-display-flex">
- <pre
- class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line"
- data-testid="binary-instructions"
- >{{ instructions.installInstructions }}</pre
- >
- <modal-copy-button
- :title="$options.i18n.copyInstructions"
- :text="instructions.installInstructions"
- :modal-id="$options.modalId"
- css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
- category="tertiary"
- />
- </div>
- <h5 class="gl-mb-3">{{ $options.i18n.registerRunnerCommand }}</h5>
- <div class="gl-display-flex">
- <pre
- class="gl-bg-gray gl-flex-grow-1 gl-white-space-pre-line"
- data-testid="register-command"
- >{{ registerInstructionsWithToken }}</pre
- >
- <modal-copy-button
- :title="$options.i18n.copyInstructions"
- :text="registerInstructionsWithToken"
- :modal-id="$options.modalId"
- css-classes="gl-align-self-start gl-ml-2 gl-mt-2"
- category="tertiary"
- />
- </div>
- </template>
- </template>
- <template v-else>
- <div>
- <p>{{ instructionsWithoutArchitecture }}</p>
- <gl-button :href="runnerInstallationLink">
- <gl-icon name="external-link" />
- {{ $options.i18n.viewInstallationInstructions }}
- </gl-button>
- </div>
- </template>
+ <keep-alive>
+ <component
+ :is="instructionsComponent"
+ :registration-token="registrationToken"
+ :platform="selectedPlatform"
+ @close="onClose"
+ @error="toggleAlert(true)"
+ />
+ </keep-alive>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
index a28460dd58e..f382ded90d7 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/constants.js
@@ -140,3 +140,7 @@ export const BIDI_CHARS_CLASS_LIST = 'unicode-bidi has-tooltip';
export const BIDI_CHAR_TOOLTIP = 'Potentially unwanted character detected: Unicode BiDi Control';
export const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight';
+
+// We fallback to highlighting these languages with Rouge, see the following issue for more detail:
+// https://gitlab.com/gitlab-org/gitlab/-/issues/384375#note_1212752013
+export const LEGACY_FALLBACKS = ['python'];
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
index 0cfee93ce5d..efafa67a733 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue
@@ -11,6 +11,7 @@ import {
EVENT_LABEL_FALLBACK,
ROUGE_TO_HLJS_LANGUAGE_MAP,
LINES_PER_CHUNK,
+ LEGACY_FALLBACKS,
} from './constants';
import Chunk from './components/chunk.vue';
import { registerPlugins } from './plugins/index';
@@ -57,10 +58,11 @@ export default {
},
unsupportedLanguage() {
const supportedLanguages = Object.keys(languageLoader);
- return (
+ const unsupportedLanguage =
!supportedLanguages.includes(this.language) &&
- !supportedLanguages.includes(this.blob.language?.toLowerCase())
- );
+ !supportedLanguages.includes(this.blob.language?.toLowerCase());
+
+ return LEGACY_FALLBACKS.includes(this.language) || unsupportedLanguage;
},
totalChunks() {
return Object.keys(this.chunks).length;
diff --git a/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue b/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue
index 423501265d7..247f49c1345 100644
--- a/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/timezone_dropdown/timezone_dropdown.vue
@@ -30,6 +30,11 @@ export default {
required: true,
default: () => [],
},
+ additionalClass: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
},
data() {
return {
@@ -96,7 +101,14 @@ export default {
:value="timezoneIdentifier || value"
type="hidden"
/>
- <gl-dropdown :text="selectedTimezoneLabel" block lazy menu-class="gl-w-full!" v-bind="$attrs">
+ <gl-dropdown
+ :text="selectedTimezoneLabel"
+ :class="additionalClass"
+ block
+ lazy
+ menu-class="gl-w-full!"
+ v-bind="$attrs"
+ >
<gl-search-box-by-type v-model.trim="searchTerm" v-autofocusonshow autofocus />
<gl-dropdown-item
v-for="timezone in filteredResults"
diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
index 383dc27ea5e..98630512308 100644
--- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue
+++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
@@ -1,16 +1,13 @@
<script>
import { GlModal, GlSprintf, GlLink, GlPopover } from '@gitlab/ui';
import { s__, __ } from '~/locale';
+import { visitUrl } from '~/lib/utils/url_utility';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import ActionsButton from '~/vue_shared/components/actions_button.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-
-export const KEY_EDIT = 'edit';
-export const KEY_WEB_IDE = 'webide';
-export const KEY_GITPOD = 'gitpod';
-export const KEY_PIPELINE_EDITOR = 'pipeline_editor';
+import { KEY_EDIT, KEY_WEB_IDE, KEY_GITPOD, KEY_PIPELINE_EDITOR } from './constants';
export const i18n = {
modal: {
@@ -221,7 +218,13 @@ export default {
this.showModal('showForkModal');
},
}
- : { href: this.webIdeUrl };
+ : {
+ href: this.webIdeUrl,
+ handle: (evt) => {
+ evt.preventDefault();
+ visitUrl(this.webIdeUrl, true);
+ },
+ };
return {
key: KEY_WEB_IDE,
diff --git a/app/assets/javascripts/vue_shared/constants.js b/app/assets/javascripts/vue_shared/constants.js
index 2f85a29fb84..c93dd95a886 100644
--- a/app/assets/javascripts/vue_shared/constants.js
+++ b/app/assets/javascripts/vue_shared/constants.js
@@ -9,7 +9,7 @@ const INTERVALS = {
export const FILE_SYMLINK_MODE = '120000';
-export const SHORT_DATE_FORMAT = 'd mmm, yyyy';
+export const SHORT_DATE_FORMAT = 'mmm dd, yyyy';
export const ISO_SHORT_FORMAT = 'yyyy-mm-dd';
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue
index 2fc1f935501..387fc5e0d1c 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue
@@ -1,6 +1,5 @@
<script>
import { GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui';
-import $ from 'jquery';
import Autosave from '~/autosave';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
@@ -81,13 +80,13 @@ export default {
if (!titleInput || !descriptionInput) return;
- this.autosaveTitle = new Autosave($(titleInput.$el), [
+ this.autosaveTitle = new Autosave(titleInput.$el, [
document.location.pathname,
document.location.search,
'title',
]);
- this.autosaveDescription = new Autosave($(descriptionInput.$el), [
+ this.autosaveDescription = new Autosave(descriptionInput, [
document.location.pathname,
document.location.search,
'description',
diff --git a/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue b/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue
index b6a459f21e0..26309a25f07 100644
--- a/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue
+++ b/app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue
@@ -34,7 +34,7 @@ export default {
:href="`#${panel.name}`"
data-qa-selector="panel_link"
:data-qa-panel-name="panel.name"
- class="new-namespace-panel gl-display-flex gl-flex-shrink-0 gl-flex-direction-column gl-lg-flex-direction-row gl-align-items-center gl-rounded-base gl-border-gray-100 gl-border-solid gl-border-1 gl-w-full gl-py-6 gl-px-8 gl-hover-text-decoration-none!"
+ class="new-namespace-panel gl-display-flex gl-flex-shrink-0 gl-flex-direction-column gl-lg-flex-direction-row gl-align-items-center gl-rounded-base gl-border-gray-100 gl-border-solid gl-border-1 gl-w-full gl-py-6 gl-px-3 gl-hover-text-decoration-none!"
@click="track('click_tab', { label: panel.name })"
>
<div