summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/keybindings.js68
-rw-r--r--app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue12
-rw-r--r--app/assets/javascripts/ci/runner/project_register_runner/index.js36
-rw-r--r--app/assets/javascripts/ci/runner/project_register_runner/project_register_runner_app.vue69
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_overview.vue6
-rw-r--r--app/assets/javascripts/environments/components/kubernetes_tabs.vue158
-rw-r--r--app/assets/javascripts/environments/constants.js2
-rw-r--r--app/assets/javascripts/environments/graphql/client.js19
-rw-r--r--app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql2
-rw-r--r--app/assets/javascripts/environments/graphql/queries/k8s_services.query.graphql15
-rw-r--r--app/assets/javascripts/environments/graphql/resolvers.js24
-rw-r--r--app/assets/javascripts/environments/graphql/typedefs.graphql19
-rw-r--r--app/assets/javascripts/environments/helpers/k8s_integration_helper.js36
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_trigger.vue19
-rw-r--r--app/assets/javascripts/invite_members/constants.js1
-rw-r--r--app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue6
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue6
-rw-r--r--app/assets/javascripts/ml/experiment_tracking/components/delete_button.vue72
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue57
-rw-r--r--app/assets/javascripts/monitoring/constants.js13
-rw-r--r--app/assets/javascripts/pages/projects/runners/register/index.js3
-rw-r--r--app/assets/javascripts/super_sidebar/components/create_menu.vue47
-rw-r--r--app/assets/javascripts/super_sidebar/components/help_center.vue10
-rw-r--r--app/assets/javascripts/super_sidebar/constants.js4
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/constants.js4
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue70
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue38
-rw-r--r--app/assets/javascripts/work_items/pages/create_work_item.vue61
28 files changed, 634 insertions, 243 deletions
diff --git a/app/assets/javascripts/behaviors/shortcuts/keybindings.js b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
index cc8a9baf69e..f4facc39f4a 100644
--- a/app/assets/javascripts/behaviors/shortcuts/keybindings.js
+++ b/app/assets/javascripts/behaviors/shortcuts/keybindings.js
@@ -2,8 +2,11 @@ import { memoize } from 'lodash';
import AccessorUtilities from '~/lib/utils/accessor';
import { __ } from '~/locale';
-const isCustomizable = (command) =>
- 'customizable' in command ? Boolean(command.customizable) : true;
+/**
+ * @param {object} command
+ * @param {boolean} [command.customizable]
+ */
+const isCustomizable = ({ customizable }) => Boolean(customizable ?? true);
export const LOCAL_STORAGE_KEY = 'gl-keyboard-shortcuts-customizations';
@@ -183,7 +186,10 @@ export const TOGGLE_MARKDOWN_PREVIEW = {
defaultKeys: ['ctrl+shift+p', 'command+shift+p'],
};
-export const EDIT_RECENT_COMMENT = {
+/**
+ * @keydown.up event is handled here: https://gitlab.com/gitlab-org/gitlab/-/blob/f3e807cdff5cf25765894163b4e92f8b2bcf8a68/app/assets/javascripts/notes/components/comment_form.vue#L379
+ */
+const EDIT_RECENT_COMMENT = {
id: 'editing.editRecentComment',
description: __('Edit your most recent comment in a thread (from an empty textarea)'),
defaultKeys: ['up'],
@@ -472,13 +478,22 @@ export const ISSUE_CLOSE_DESIGN = {
defaultKeys: ['esc'],
};
-export const WEB_IDE_GO_TO_FILE = {
+/**
+ * Legacy Web IDE uses the same shortcuts as MR_GO_TO_FILE, from this shared component:
+ * https://gitlab.com/gitlab-org/gitlab/-/blob/f3e807cdff5cf25765894163b4e92f8b2bcf8a68/app/assets/javascripts/vue_shared/components/file_finder/index.vue#L6
+ */
+const WEB_IDE_GO_TO_FILE = {
id: 'webIDE.goToFile',
description: __('Go to file'),
- defaultKeys: ['mod+p'],
+ defaultKeys: ['mod+p', 't'],
+ customizable: false /* customize MR_GO_TO_FILE instead */,
};
-export const WEB_IDE_COMMIT = {
+/**
+ * Legacy Web IDE uses @keydown.ctrl.enter and @keydown.meta.enter events here:
+ * https://gitlab.com/gitlab-org/gitlab/-/blob/f3e807cdff5cf25765894163b4e92f8b2bcf8a68/app/assets/javascripts/ide/components/shared/commit_message_field.vue#L131-132
+ */
+const WEB_IDE_COMMIT = {
id: 'webIDE.commit',
description: __('Commit (when editing commit message)'),
defaultKeys: ['mod+enter'],
@@ -489,39 +504,28 @@ export const METRICS_EXPAND_PANEL = {
id: 'metrics.expandPanel',
description: __('Expand panel'),
defaultKeys: ['e'],
- customizable: false,
-};
-
-export const METRICS_VIEW_LOGS = {
- id: 'metrics.viewLogs',
- description: __('View logs'),
- defaultKeys: ['l'],
- customizable: false,
};
export const METRICS_DOWNLOAD_CSV = {
id: 'metrics.downloadCSV',
description: __('Download CSV'),
defaultKeys: ['d'],
- customizable: false,
};
export const METRICS_COPY_LINK_TO_CHART = {
id: 'metrics.copyLinkToChart',
description: __('Copy link to chart'),
defaultKeys: ['c'],
- customizable: false,
};
export const METRICS_SHOW_ALERTS = {
id: 'metrics.showAlerts',
description: __('Alerts'),
defaultKeys: ['a'],
- customizable: false,
};
// All keybinding groups
-export const GLOBAL_SHORTCUTS_GROUP = {
+const GLOBAL_SHORTCUTS_GROUP = {
id: 'globalShortcuts',
name: __('Global Shortcuts'),
keybindings: [
@@ -559,13 +563,13 @@ export const EDITING_SHORTCUTS_GROUP = {
],
};
-export const WIKI_SHORTCUTS_GROUP = {
+const WIKI_SHORTCUTS_GROUP = {
id: 'wiki',
name: __('Wiki'),
keybindings: [EDIT_WIKI_PAGE],
};
-export const REPOSITORY_GRAPH_SHORTCUTS_GROUP = {
+const REPOSITORY_GRAPH_SHORTCUTS_GROUP = {
id: 'repositoryGraph',
name: __('Repository Graph'),
keybindings: [
@@ -578,7 +582,7 @@ export const REPOSITORY_GRAPH_SHORTCUTS_GROUP = {
],
};
-export const PROJECT_SHORTCUTS_GROUP = {
+const PROJECT_SHORTCUTS_GROUP = {
id: 'project',
name: __('Project'),
keybindings: [
@@ -604,7 +608,7 @@ export const PROJECT_SHORTCUTS_GROUP = {
],
};
-export const PROJECT_FILES_SHORTCUTS_GROUP = {
+const PROJECT_FILES_SHORTCUTS_GROUP = {
id: 'projectFiles',
name: __('Project Files'),
keybindings: [
@@ -616,19 +620,19 @@ export const PROJECT_FILES_SHORTCUTS_GROUP = {
],
};
-export const ISSUABLE_SHORTCUTS_GROUP = {
+const ISSUABLE_SHORTCUTS_GROUP = {
id: 'issuables',
name: __('Epics, issues, and merge requests'),
keybindings: [ISSUABLE_COMMENT_OR_REPLY, ISSUABLE_EDIT_DESCRIPTION, ISSUABLE_CHANGE_LABEL],
};
-export const ISSUE_MR_SHORTCUTS_GROUP = {
+const ISSUE_MR_SHORTCUTS_GROUP = {
id: 'issuesMRs',
name: __('Issues and merge requests'),
keybindings: [ISSUE_MR_CHANGE_ASSIGNEE, ISSUE_MR_CHANGE_MILESTONE],
};
-export const MR_SHORTCUTS_GROUP = {
+const MR_SHORTCUTS_GROUP = {
id: 'mergeRequests',
name: __('Merge requests'),
keybindings: [
@@ -641,30 +645,29 @@ export const MR_SHORTCUTS_GROUP = {
],
};
-export const MR_COMMITS_SHORTCUTS_GROUP = {
+const MR_COMMITS_SHORTCUTS_GROUP = {
id: 'mergeRequestCommits',
name: __('Merge request commits'),
keybindings: [MR_COMMITS_NEXT_COMMIT, MR_COMMITS_PREVIOUS_COMMIT],
};
-export const ISSUES_SHORTCUTS_GROUP = {
+const ISSUES_SHORTCUTS_GROUP = {
id: 'issues',
name: __('Issues'),
keybindings: [ISSUE_NEXT_DESIGN, ISSUE_PREVIOUS_DESIGN, ISSUE_CLOSE_DESIGN],
};
-export const WEB_IDE_SHORTCUTS_GROUP = {
+const WEB_IDE_SHORTCUTS_GROUP = {
id: 'webIDE',
- name: __('Web IDE'),
+ name: __('Legacy Web IDE'),
keybindings: [WEB_IDE_GO_TO_FILE, WEB_IDE_COMMIT],
};
-export const METRICS_SHORTCUTS_GROUP = {
+const METRICS_SHORTCUTS_GROUP = {
id: 'metrics',
name: __('Metrics'),
keybindings: [
METRICS_EXPAND_PANEL,
- METRICS_VIEW_LOGS,
METRICS_DOWNLOAD_CSV,
METRICS_COPY_LINK_TO_CHART,
METRICS_SHOW_ALERTS,
@@ -700,6 +703,9 @@ export const keybindingGroups = [
*
* @param {Object} command The command object. All command objects are
* available as imports from this file.
+ * @param {string} command.id
+ * @param {string[]} command.defaultKeys
+ * @param {boolean} [command.customizable]
*
* @returns {string[]} An array of keyboard shortcut strings bound to the command
*
diff --git a/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue b/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue
index abc2eca38f4..821e002581c 100644
--- a/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue
+++ b/app/assets/javascripts/ci/runner/project_new_runner/project_new_runner_app.vue
@@ -1,9 +1,11 @@
<script>
import { createAlert, VARIANT_SUCCESS } from '~/alert';
+import { redirectTo, setUrlParams } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
-import { DEFAULT_PLATFORM, PROJECT_TYPE } from '../constants';
+import { DEFAULT_PLATFORM, PARAM_KEY_PLATFORM, PROJECT_TYPE } from '../constants';
+import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage';
export default {
name: 'ProjectNewRunnerApp',
@@ -23,11 +25,15 @@ export default {
};
},
methods: {
- onSaved() {
- createAlert({
+ onSaved(runner) {
+ const params = { [PARAM_KEY_PLATFORM]: this.platform };
+ const ephemeralRegisterUrl = setUrlParams(params, runner.ephemeralRegisterUrl);
+
+ saveAlertToLocalStorage({
message: s__('Runners|Runner created.'),
variant: VARIANT_SUCCESS,
});
+ redirectTo(ephemeralRegisterUrl);
},
onError(error) {
createAlert({ message: error.message });
diff --git a/app/assets/javascripts/ci/runner/project_register_runner/index.js b/app/assets/javascripts/ci/runner/project_register_runner/index.js
new file mode 100644
index 00000000000..63e88359ee7
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/project_register_runner/index.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage';
+import ProjectRegisterRunnerApp from './project_register_runner_app.vue';
+
+Vue.use(VueApollo);
+
+export const initProjectRegisterRunner = (selector = '#js-project-register-runner') => {
+ showAlertFromLocalStorage();
+
+ const el = document.querySelector(selector);
+
+ if (!el) {
+ return null;
+ }
+
+ const { runnerId, runnersPath } = el.dataset;
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ return new Vue({
+ el,
+ apolloProvider,
+ render(h) {
+ return h(ProjectRegisterRunnerApp, {
+ props: {
+ runnerId,
+ runnersPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/ci/runner/project_register_runner/project_register_runner_app.vue b/app/assets/javascripts/ci/runner/project_register_runner/project_register_runner_app.vue
new file mode 100644
index 00000000000..b3fad595c7e
--- /dev/null
+++ b/app/assets/javascripts/ci/runner/project_register_runner/project_register_runner_app.vue
@@ -0,0 +1,69 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { getParameterByName, updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
+import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM } from '../constants';
+import RegistrationInstructions from '../components/registration/registration_instructions.vue';
+import PlatformsDrawer from '../components/registration/platforms_drawer.vue';
+
+export default {
+ name: 'ProjectRegisterRunnerApp',
+ components: {
+ GlButton,
+ RegistrationInstructions,
+ PlatformsDrawer,
+ },
+ props: {
+ runnerId: {
+ type: String,
+ required: true,
+ },
+ runnersPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ platform: getParameterByName(PARAM_KEY_PLATFORM) || DEFAULT_PLATFORM,
+ isDrawerOpen: false,
+ };
+ },
+ watch: {
+ platform(platform) {
+ updateHistory({
+ url: mergeUrlParams({ [PARAM_KEY_PLATFORM]: platform }, window.location.href),
+ });
+ },
+ },
+ methods: {
+ onSelectPlatform(platform) {
+ this.platform = platform;
+ },
+ onToggleDrawer(val = !this.isDrawerOpen) {
+ this.isDrawerOpen = val;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <registration-instructions
+ :runner-id="runnerId"
+ :platform="platform"
+ @toggleDrawer="onToggleDrawer"
+ >
+ <template #runner-list-name>{{ s__('Runners|Project › CI/CD Settings › Runners') }}</template>
+ </registration-instructions>
+
+ <platforms-drawer
+ :platform="platform"
+ :open="isDrawerOpen"
+ @selectPlatform="onSelectPlatform"
+ @close="onToggleDrawer(false)"
+ />
+
+ <gl-button :href="runnersPath" variant="confirm">{{
+ s__('Runners|Go to runners page')
+ }}</gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue
index 736eaa7062d..41abfcf6dc8 100644
--- a/app/assets/javascripts/environments/components/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue
@@ -5,6 +5,7 @@ import csrf from '~/lib/utils/csrf';
import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
import KubernetesAgentInfo from './kubernetes_agent_info.vue';
import KubernetesPods from './kubernetes_pods.vue';
+import KubernetesTabs from './kubernetes_tabs.vue';
export default {
components: {
@@ -13,6 +14,7 @@ export default {
GlAlert,
KubernetesAgentInfo,
KubernetesPods,
+ KubernetesTabs,
},
inject: ['kasTunnelUrl'],
props: {
@@ -103,6 +105,10 @@ export default {
:configuration="k8sAccessConfiguration"
:namespace="namespace"
class="gl-mb-5"
+ @cluster-error="onClusterError" />
+ <kubernetes-tabs
+ :configuration="k8sAccessConfiguration"
+ class="gl-mb-5"
@cluster-error="onClusterError"
/></template>
</gl-collapse>
diff --git a/app/assets/javascripts/environments/components/kubernetes_tabs.vue b/app/assets/javascripts/environments/components/kubernetes_tabs.vue
new file mode 100644
index 00000000000..b1eb92a4049
--- /dev/null
+++ b/app/assets/javascripts/environments/components/kubernetes_tabs.vue
@@ -0,0 +1,158 @@
+<script>
+import { GlTabs, GlTab, GlLoadingIcon, GlBadge, GlTable, GlPagination } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import k8sServicesQuery from '../graphql/queries/k8s_services.query.graphql';
+import { generateServicePortsString, getServiceAge } from '../helpers/k8s_integration_helper';
+import { SERVICES_LIMIT_PER_PAGE } from '../constants';
+
+const tableHeadingClasses = 'gl-bg-gray-50! gl-font-weight-bold gl-white-space-nowrap';
+
+export default {
+ components: {
+ GlTabs,
+ GlTab,
+ GlBadge,
+ GlTable,
+ GlPagination,
+ GlLoadingIcon,
+ },
+ apollo: {
+ k8sServices: {
+ query: k8sServicesQuery,
+ variables() {
+ return {
+ configuration: this.configuration,
+ };
+ },
+ update(data) {
+ return data?.k8sServices || [];
+ },
+ error(error) {
+ this.$emit('cluster-error', error);
+ },
+ },
+ },
+ props: {
+ configuration: {
+ required: true,
+ type: Object,
+ },
+ },
+ data() {
+ return {
+ currentPage: 1,
+ };
+ },
+ computed: {
+ servicesItems() {
+ if (!this.k8sServices?.length) return [];
+
+ return this.k8sServices.map((service) => {
+ return {
+ name: service?.metadata?.name,
+ namespace: service?.metadata?.namespace,
+ type: service?.spec?.type,
+ clusterIP: service?.spec?.clusterIP,
+ externalIP: service?.spec?.externalIP,
+ ports: generateServicePortsString(service?.spec?.ports),
+ age: getServiceAge(service?.metadata?.creationTimestamp),
+ };
+ });
+ },
+ servicesLoading() {
+ return this.$apollo.queries.k8sServices.loading;
+ },
+ showPagination() {
+ return this.servicesItems.length > SERVICES_LIMIT_PER_PAGE;
+ },
+ prevPage() {
+ return Math.max(this.currentPage - 1, 0);
+ },
+ nextPage() {
+ const nextPage = this.currentPage + 1;
+ return nextPage > Math.ceil(this.servicesItems.length / SERVICES_LIMIT_PER_PAGE)
+ ? null
+ : nextPage;
+ },
+ },
+ i18n: {
+ servicesTitle: s__('Environment|Services'),
+ name: __('Name'),
+ namespace: __('Namespace'),
+ status: __('Status'),
+ type: __('Type'),
+ clusterIP: s__('Environment|Cluster IP'),
+ externalIP: s__('Environment|External IP'),
+ ports: s__('Environment|Ports'),
+ age: s__('Environment|Age'),
+ },
+ servicesFields: [
+ {
+ key: 'name',
+ label: __('Name'),
+ thClass: tableHeadingClasses,
+ },
+ {
+ key: 'namespace',
+ label: __('Namespace'),
+ thClass: tableHeadingClasses,
+ },
+ {
+ key: 'type',
+ label: __('Type'),
+ thClass: tableHeadingClasses,
+ },
+ {
+ key: 'clusterIP',
+ label: s__('Environment|Cluster IP'),
+ thClass: tableHeadingClasses,
+ },
+ {
+ key: 'externalIP',
+ label: s__('Environment|External IP'),
+ thClass: tableHeadingClasses,
+ },
+ {
+ key: 'ports',
+ label: s__('Environment|Ports'),
+ thClass: tableHeadingClasses,
+ },
+ {
+ key: 'age',
+ label: s__('Environment|Age'),
+ thClass: tableHeadingClasses,
+ },
+ ],
+ SERVICES_LIMIT_PER_PAGE,
+};
+</script>
+<template>
+ <gl-tabs>
+ <gl-tab>
+ <template #title>
+ {{ $options.i18n.servicesTitle }}
+ <gl-badge size="sm" class="gl-tab-counter-badge">{{ servicesItems.length }}</gl-badge>
+ </template>
+
+ <gl-loading-icon v-if="servicesLoading" />
+
+ <gl-table
+ v-else
+ :fields="$options.servicesFields"
+ :items="servicesItems"
+ :per-page="$options.SERVICES_LIMIT_PER_PAGE"
+ :current-page="currentPage"
+ stacked="lg"
+ class="gl-bg-white! gl-mt-3"
+ />
+ <gl-pagination
+ v-if="showPagination"
+ v-model="currentPage"
+ :prev-page="prevPage"
+ :next-page="nextPage"
+ align="center"
+ class="gl-mt-6"
+ />
+ </gl-tab>
+ </gl-tabs>
+</template>
diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js
index 28424322dd2..e675a73ba7d 100644
--- a/app/assets/javascripts/environments/constants.js
+++ b/app/assets/javascripts/environments/constants.js
@@ -87,3 +87,5 @@ export const ENVIRONMENT_NEW_HELP_TEXT = __(
);
export const ENVIRONMENT_EDIT_HELP_TEXT = ENVIRONMENT_NEW_HELP_TEXT;
+
+export const SERVICES_LIMIT_PER_PAGE = 10;
diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js
index 0482741979b..bb6f57e7e80 100644
--- a/app/assets/javascripts/environments/graphql/client.js
+++ b/app/assets/javascripts/environments/graphql/client.js
@@ -6,6 +6,7 @@ import environmentToDeleteQuery from './queries/environment_to_delete.query.grap
import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql';
import environmentToStopQuery from './queries/environment_to_stop.query.graphql';
import k8sPodsQuery from './queries/k8s_pods.query.graphql';
+import k8sServicesQuery from './queries/k8s_services.query.graphql';
import { resolvers } from './resolvers';
import typeDefs from './typedefs.graphql';
@@ -87,7 +88,23 @@ export const apolloProvider = (endpoint) => {
query: k8sPodsQuery,
data: {
status: {
- phase: '',
+ phase: null,
+ },
+ },
+ });
+ cache.writeQuery({
+ query: k8sServicesQuery,
+ data: {
+ metadata: {
+ name: null,
+ namespace: null,
+ creationTimestamp: null,
+ },
+ spec: {
+ type: null,
+ clusterIP: null,
+ externalIP: null,
+ ports: [],
},
},
});
diff --git a/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql b/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql
index 818bca24d51..2d57ede8c15 100644
--- a/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql
+++ b/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql
@@ -1,4 +1,4 @@
-query getK8sPods($configuration: Object, $namespace: String) {
+query getK8sPods($configuration: LocalConfiguration, $namespace: String) {
k8sPods(configuration: $configuration, namespace: $namespace) @client {
status {
phase
diff --git a/app/assets/javascripts/environments/graphql/queries/k8s_services.query.graphql b/app/assets/javascripts/environments/graphql/queries/k8s_services.query.graphql
new file mode 100644
index 00000000000..d97849eecc1
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/k8s_services.query.graphql
@@ -0,0 +1,15 @@
+query getK8sServices($configuration: LocalConfiguration) {
+ k8sServices(configuration: $configuration) @client {
+ metadata {
+ name
+ namespace
+ creationTimestamp
+ }
+ spec {
+ type
+ clusterIP
+ externalIP
+ ports
+ }
+ }
+}
diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js
index 013467e34be..8ebeeb92a53 100644
--- a/app/assets/javascripts/environments/graphql/resolvers.js
+++ b/app/assets/javascripts/environments/graphql/resolvers.js
@@ -85,6 +85,30 @@ export const resolvers = (endpoint) => ({
throw error;
});
},
+ k8sServices(_, { configuration }) {
+ const coreV1Api = new CoreV1Api(new Configuration(configuration));
+ return coreV1Api
+ .listCoreV1ServiceForAllNamespaces()
+ .then((res) => {
+ const items = res?.data?.items || [];
+ return items.map((item) => {
+ const { type, clusterIP, externalIP, ports } = item.spec;
+ return {
+ metadata: item.metadata,
+ spec: {
+ type,
+ clusterIP: clusterIP || '-',
+ externalIP: externalIP || '-',
+ ports,
+ },
+ };
+ });
+ })
+ .catch((err) => {
+ const error = err?.response?.data?.message ? new Error(err.response.data.message) : err;
+ throw error;
+ });
+ },
},
Mutation: {
stopEnvironmentREST(_, { environment }, { client }) {
diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql
index 4de271935d6..b85bf1e10d3 100644
--- a/app/assets/javascripts/environments/graphql/typedefs.graphql
+++ b/app/assets/javascripts/environments/graphql/typedefs.graphql
@@ -75,6 +75,24 @@ input LocalConfiguration {
baseOptions: JSON
}
+type k8sServiceMetadata {
+ name: String
+ namespace: String
+ creationTimestamp: String
+}
+
+type k8sServiceSpec {
+ type: String
+ clusterIP: String
+ externalIP: String
+ ports: JSON
+}
+
+type LocalK8sServices {
+ metadata: k8sServiceMetadata
+ spec: k8sServiceSpec
+}
+
extend type Query {
environmentApp(page: Int, scope: String): LocalEnvironmentApp
folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder
@@ -85,6 +103,7 @@ extend type Query {
isEnvironmentStopping(environment: LocalEnvironmentInput): Boolean
isLastDeployment(environment: LocalEnvironmentInput): Boolean
k8sPods(configuration: LocalConfiguration, namespace: String): [LocalK8sPods]
+ k8sServices(configuration: LocalConfiguration): [LocalK8sServices]
}
extend type Mutation {
diff --git a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js
new file mode 100644
index 00000000000..6a94ac31fa1
--- /dev/null
+++ b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js
@@ -0,0 +1,36 @@
+import { differenceInSeconds } from '~/lib/utils/datetime_utility';
+
+export function generateServicePortsString(ports) {
+ if (!ports?.length) return '';
+
+ return ports
+ .map((port) => {
+ const nodePort = port.nodePort ? `:${port.nodePort}` : '';
+ return `${port.port}${nodePort}/${port.protocol}`;
+ })
+ .join(', ');
+}
+
+export function getServiceAge(creationTimestamp) {
+ if (!creationTimestamp) return '';
+
+ const timeDifference = differenceInSeconds(new Date(creationTimestamp), new Date());
+
+ const seconds = Math.floor(timeDifference);
+ const minutes = Math.floor(seconds / 60) % 60;
+ const hours = Math.floor(seconds / 60 / 60) % 24;
+ const days = Math.floor(seconds / 60 / 60 / 24);
+
+ let ageString;
+ if (days > 0) {
+ ageString = `${days}d`;
+ } else if (hours > 0) {
+ ageString = `${hours}h`;
+ } else if (minutes > 0) {
+ ageString = `${minutes}m`;
+ } else {
+ ageString = `${seconds}s`;
+ }
+
+ return ageString;
+}
diff --git a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
index 6d1a3ceba16..fadce6457bc 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlLink, GlDropdownItem } from '@gitlab/ui';
+import { GlButton, GlLink, GlDropdownItem, GlDisclosureDropdownItem } from '@gitlab/ui';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
import {
@@ -7,10 +7,11 @@ import {
TRIGGER_DEFAULT_QA_SELECTOR,
TRIGGER_ELEMENT_WITH_EMOJI,
TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI,
+ TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN,
} from '../constants';
export default {
- components: { GlButton, GlLink, GlDropdownItem },
+ components: { GlButton, GlLink, GlDropdownItem, GlDisclosureDropdownItem },
props: {
displayText: {
type: String,
@@ -55,6 +56,9 @@ export default {
'data-test-id': 'invite-members-button',
};
},
+ item() {
+ return { text: this.displayText };
+ },
},
methods: {
checkTrigger(targetTriggerElement) {
@@ -63,10 +67,15 @@ export default {
openModal() {
eventHub.$emit('openModal', { source: this.triggerSource });
},
+ handleDisclosureDropdownAction() {
+ this.openModal();
+ this.$emit('modal-opened');
+ },
},
TRIGGER_ELEMENT_BUTTON,
TRIGGER_ELEMENT_WITH_EMOJI,
TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI,
+ TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN,
};
</script>
@@ -97,6 +106,12 @@ export default {
{{ displayText }}
<gl-emoji class="gl-vertical-align-baseline gl-reset-font-size gl-mr-1" :data-name="icon" />
</gl-dropdown-item>
+ <gl-disclosure-dropdown-item
+ v-else-if="checkTrigger($options.TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN)"
+ v-bind="componentAttributes"
+ :item="item"
+ @action="handleDisclosureDropdownAction"
+ />
<gl-link v-else v-bind="componentAttributes" data-is-link="true" @click="openModal">
{{ displayText }}
</gl-link>
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index d5e9e498c6b..58457c80524 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -21,6 +21,7 @@ export const TRIGGER_ELEMENT_BUTTON = 'button';
export const TOP_NAV_INVITE_MEMBERS_COMPONENT = 'invite_members';
export const TRIGGER_ELEMENT_WITH_EMOJI = 'text-emoji';
export const TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI = 'dropdown-text-emoji';
+export const TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN = 'dropdown-text';
export const INVITE_MEMBER_MODAL_TRACKING_CATEGORY = 'invite_members_modal';
export const TRIGGER_DEFAULT_QA_SELECTOR = 'invite_members_button';
export const MEMBERS_MODAL_DEFAULT_TITLE = s__('InviteMembersModal|Invite members');
diff --git a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
index 970ab12cc85..b4a9b37d487 100644
--- a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
+++ b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
@@ -53,7 +53,7 @@ import {
TOKEN_TYPE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
-import { DEFAULT_PAGE_SIZE, IssuableListTabs } from '~/vue_shared/issuable/list/constants';
+import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants';
import getIssuesCountsQuery from '../queries/get_issues_counts.query.graphql';
import { AutocompleteCache } from '../utils';
@@ -67,7 +67,7 @@ const MilestoneToken = () =>
export default {
i18n,
- IssuableListTabs,
+ issuableListTabs,
components: {
GlDisclosureDropdown,
GlEmptyState,
@@ -457,7 +457,7 @@ export default {
show-work-item-type-icon
:sort-options="sortOptions"
:tab-counts="tabCounts"
- :tabs="$options.IssuableListTabs"
+ :tabs="$options.issuableListTabs"
truncate-counts
:url-params="urlParams"
use-keyset-pagination
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index 5c4bf8f19e4..525daab41d5 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -52,7 +52,7 @@ import {
TOKEN_TYPE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
-import { DEFAULT_PAGE_SIZE, IssuableListTabs } from '~/vue_shared/issuable/list/constants';
+import { DEFAULT_PAGE_SIZE, issuableListTabs } from '~/vue_shared/issuable/list/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue';
import {
@@ -108,7 +108,7 @@ const CrmOrganizationToken = () =>
export default {
i18n,
- IssuableListTabs,
+ issuableListTabs,
components: {
CsvImportExportButtons,
EmptyStateWithAnyIssues,
@@ -774,7 +774,7 @@ export default {
:issuables="issues"
:error="issuesError"
label-filter-param="label_name"
- :tabs="$options.IssuableListTabs"
+ :tabs="$options.issuableListTabs"
:current-tab="state"
:tab-counts="tabCounts"
:truncate-counts="!isProject"
diff --git a/app/assets/javascripts/ml/experiment_tracking/components/delete_button.vue b/app/assets/javascripts/ml/experiment_tracking/components/delete_button.vue
index 4c0f99cf62c..c10b0cb52b9 100644
--- a/app/assets/javascripts/ml/experiment_tracking/components/delete_button.vue
+++ b/app/assets/javascripts/ml/experiment_tracking/components/delete_button.vue
@@ -1,9 +1,9 @@
<script>
import {
GlModal,
- GlDropdown,
+ GlDisclosureDropdown,
GlTooltipDirective,
- GlDropdownItem,
+ GlDisclosureDropdownItem,
GlModalDirective,
} from '@gitlab/ui';
import { __ } from '~/locale';
@@ -12,8 +12,8 @@ import csrf from '~/lib/utils/csrf';
export default {
components: {
GlModal,
- GlDropdown,
- GlDropdownItem,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -63,36 +63,42 @@ export default {
</script>
<template>
- <gl-dropdown
- right
- category="tertiary"
- :aria-label="__('More actions')"
- icon="ellipsis_v"
- no-caret
- >
- <gl-dropdown-item
- v-gl-modal-directive="modal.id"
- :aria-label="actionPrimaryText"
- variant="danger"
+ <div>
+ <gl-disclosure-dropdown
+ placement="right"
+ category="tertiary"
+ :aria-label="__('More actions')"
+ icon="ellipsis_v"
+ no-caret
>
- {{ actionPrimaryText }}
+ <gl-disclosure-dropdown-item
+ v-gl-modal-directive="modal.id"
+ :aria-label="actionPrimaryText"
+ variant="danger"
+ >
+ <template #list-item>
+ <span class="gl-text-red-500">
+ {{ actionPrimaryText }}
+ </span>
+ </template>
+ </gl-disclosure-dropdown-item>
+ </gl-disclosure-dropdown>
- <form ref="deleteForm" method="post" :action="deletePath">
- <input type="hidden" name="_method" value="delete" />
- <input type="hidden" name="authenticity_token" :value="$options.csrf.token" />
- </form>
+ <form ref="deleteForm" method="post" :action="deletePath">
+ <input type="hidden" name="_method" value="delete" />
+ <input type="hidden" name="authenticity_token" :value="$options.csrf.token" />
+ </form>
- <gl-modal
- :modal-id="modal.id"
- :title="modalTitle"
- :action-primary="modal.actionPrimary"
- :action-cancel="modal.actionCancel"
- @primary="confirmDelete"
- >
- <p>
- {{ deleteConfirmationText }}
- </p>
- </gl-modal>
- </gl-dropdown-item>
- </gl-dropdown>
+ <gl-modal
+ :modal-id="modal.id"
+ :title="modalTitle"
+ :action-primary="modal.actionPrimary"
+ :action-cancel="modal.actionCancel"
+ @primary="confirmDelete"
+ >
+ <p>
+ {{ deleteConfirmationText }}
+ </p>
+ </gl-modal>
+ </div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 100ef11409f..752ba4241d8 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -11,6 +11,13 @@ import {
import VueDraggable from 'vuedraggable';
import { mapActions, mapState, mapGetters } from 'vuex';
import { createAlert } from '~/alert';
+import {
+ keysFor,
+ METRICS_COPY_LINK_TO_CHART,
+ METRICS_DOWNLOAD_CSV,
+ METRICS_EXPAND_PANEL,
+ METRICS_SHOW_ALERTS,
+} from '~/behaviors/shortcuts/keybindings';
import invalidUrl from '~/lib/utils/invalid_url';
import { ESC_KEY } from '~/lib/utils/keys';
import { Mousetrap } from '~/lib/mousetrap';
@@ -18,7 +25,7 @@ import { mergeUrlParams, updateHistory } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { defaultTimeRange } from '~/vue_shared/constants';
import TrackEventDirective from '~/vue_shared/directives/track_event';
-import { metricStates, keyboardShortcutKeys } from '../constants';
+import { metricStates } from '../constants';
import {
timeRangeFromUrl,
panelToUrl,
@@ -214,12 +221,28 @@ export default {
created() {
window.addEventListener('keyup', this.onKeyup);
- Mousetrap.bind(Object.values(keyboardShortcutKeys), this.runShortcut);
+ Mousetrap.bind(keysFor(METRICS_EXPAND_PANEL), () =>
+ this.runShortcut('onExpandFromKeyboardShortcut'),
+ );
+ Mousetrap.bind(keysFor(METRICS_SHOW_ALERTS), () =>
+ this.runShortcut('showAlertModalFromKeyboardShortcut'),
+ );
+ Mousetrap.bind(keysFor(METRICS_DOWNLOAD_CSV), () =>
+ this.runShortcut('downloadCsvFromKeyboardShortcut'),
+ );
+ Mousetrap.bind(keysFor(METRICS_COPY_LINK_TO_CHART), () =>
+ this.runShortcut('copyChartLinkFromKeyboardShotcut'),
+ );
},
destroyed() {
window.removeEventListener('keyup', this.onKeyup);
- Mousetrap.unbind(Object.values(keyboardShortcutKeys));
+ [
+ METRICS_COPY_LINK_TO_CHART,
+ METRICS_DOWNLOAD_CSV,
+ METRICS_EXPAND_PANEL,
+ METRICS_SHOW_ALERTS,
+ ].forEach((command) => Mousetrap.unbind(keysFor(command)));
},
mounted() {
if (!this.hasMetrics) {
@@ -339,42 +362,18 @@ export default {
},
/**
* TODO: Investigate this to utilize the eventBus from Vue
- * The intentation behind this cleanup is to allow for better tests
+ * The intention behind this cleanup is to allow for better tests
* as well as use the correct eventBus facilities that are compatible
* with Vue 3
* https://gitlab.com/gitlab-org/gitlab/-/issues/225583
*/
//
- runShortcut(e) {
+ runShortcut(actionToRun) {
const panel = this.$refs[this.hoveredPanel];
if (!panel) return;
const [panelInstance] = panel;
- let actionToRun = '';
-
- switch (e.key) {
- case keyboardShortcutKeys.EXPAND:
- actionToRun = 'onExpandFromKeyboardShortcut';
- break;
-
- case keyboardShortcutKeys.SHOW_ALERT:
- actionToRun = 'showAlertModalFromKeyboardShortcut';
- break;
-
- case keyboardShortcutKeys.DOWNLOAD_CSV:
- actionToRun = 'downloadCsvFromKeyboardShortcut';
- break;
-
- case keyboardShortcutKeys.CHART_COPY:
- actionToRun = 'copyChartLinkFromKeyboardShotcut';
- break;
-
- default:
- actionToRun = 'onExpandFromKeyboardShortcut';
- break;
- }
-
panelInstance[actionToRun]();
},
setHoveredPanel(groupKey, graphIndex) {
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index faef4b01c27..e35dcc350f2 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -256,19 +256,6 @@ export const VARIABLE_TYPES = {
*/
export const VARIABLE_PREFIX = 'var-';
-/**
- * All of the actions inside each panel dropdown can be accessed
- * via keyboard shortcuts than can be activated via mouse hovers
- * and or focus via tabs.
- */
-
-export const keyboardShortcutKeys = {
- EXPAND: 'e',
- SHOW_ALERT: 'a',
- DOWNLOAD_CSV: 'd',
- CHART_COPY: 'c',
-};
-
export const thresholdModeTypes = {
ABSOLUTE: 'absolute',
PERCENTAGE: 'percentage',
diff --git a/app/assets/javascripts/pages/projects/runners/register/index.js b/app/assets/javascripts/pages/projects/runners/register/index.js
new file mode 100644
index 00000000000..a55ff95f84d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/runners/register/index.js
@@ -0,0 +1,3 @@
+import { initProjectRegisterRunner } from '~/ci/runner/project_register_runner';
+
+initProjectRegisterRunner();
diff --git a/app/assets/javascripts/super_sidebar/components/create_menu.vue b/app/assets/javascripts/super_sidebar/components/create_menu.vue
index 4cff4642cf7..fa6056aff5e 100644
--- a/app/assets/javascripts/super_sidebar/components/create_menu.vue
+++ b/app/assets/javascripts/super_sidebar/components/create_menu.vue
@@ -1,6 +1,16 @@
<script>
-import { GlDisclosureDropdown, GlTooltip } from '@gitlab/ui';
+import {
+ GlDisclosureDropdown,
+ GlTooltip,
+ GlDisclosureDropdownGroup,
+ GlDisclosureDropdownItem,
+} from '@gitlab/ui';
+import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
import { __ } from '~/locale';
+import {
+ TOP_NAV_INVITE_MEMBERS_COMPONENT,
+ TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN,
+} from '~/invite_members/constants';
import { DROPDOWN_Y_OFFSET } from '../constants';
// Left offset required for the dropdown to be aligned with the super sidebar
@@ -9,7 +19,10 @@ const DROPDOWN_X_OFFSET = -147;
export default {
components: {
GlDisclosureDropdown,
+ GlDisclosureDropdownGroup,
+ GlDisclosureDropdownItem,
GlTooltip,
+ InviteMembersTrigger,
},
i18n: {
createNew: __('Create new...'),
@@ -25,6 +38,14 @@ export default {
dropdownOpen: false,
};
},
+ methods: {
+ isInvitedMembers(groupItem) {
+ return groupItem.component === TOP_NAV_INVITE_MEMBERS_COMPONENT;
+ },
+ closeAndFocus() {
+ this.$refs.dropdown.closeAndFocus();
+ },
+ },
toggleId: 'create-menu-toggle',
popperOptions: {
modifiers: [
@@ -36,24 +57,44 @@ export default {
},
],
},
+ TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN,
};
</script>
<template>
<div>
<gl-disclosure-dropdown
+ ref="dropdown"
category="tertiary"
icon="plus"
- :items="groups"
no-caret
text-sr-only
:toggle-text="$options.i18n.createNew"
:toggle-id="$options.toggleId"
:popper-options="$options.popperOptions"
data-qa-selector="new_menu_toggle"
+ data-testid="new-menu-toggle"
@shown="dropdownOpen = true"
@hidden="dropdownOpen = false"
- />
+ >
+ <gl-disclosure-dropdown-group
+ v-for="(group, index) in groups"
+ :key="group.name"
+ :bordered="index !== 0"
+ :group="group"
+ >
+ <template v-for="groupItem in group.items">
+ <invite-members-trigger
+ v-if="isInvitedMembers(groupItem)"
+ :key="`${groupItem.text}-trigger`"
+ trigger-source="top-nav"
+ :trigger-element="$options.TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN"
+ @modal-opened="closeAndFocus"
+ />
+ <gl-disclosure-dropdown-item v-else :key="groupItem.text" :item="groupItem" />
+ </template>
+ </gl-disclosure-dropdown-group>
+ </gl-disclosure-dropdown>
<gl-tooltip
v-if="!dropdownOpen"
:target="`#${$options.toggleId}`"
diff --git a/app/assets/javascripts/super_sidebar/components/help_center.vue b/app/assets/javascripts/super_sidebar/components/help_center.vue
index a75d1fd6bff..70e1780aae1 100644
--- a/app/assets/javascripts/super_sidebar/components/help_center.vue
+++ b/app/assets/javascripts/super_sidebar/components/help_center.vue
@@ -12,7 +12,7 @@ import { PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
import { __ } from '~/locale';
import { STORAGE_KEY } from '~/whats_new/utils/notification';
import Tracking from '~/tracking';
-import { DROPDOWN_Y_OFFSET, HELP_MENU_TRACKING_DEFAULTS } from '../constants';
+import { DROPDOWN_Y_OFFSET, HELP_MENU_TRACKING_DEFAULTS, helpCenterState } from '../constants';
// Left offset required for the dropdown to be aligned with the super sidebar
const DROPDOWN_X_OFFSET = -4;
@@ -49,6 +49,7 @@ export default {
data() {
return {
showWhatsNewNotification: this.shouldShowWhatsNewNotification(),
+ helpCenterState,
};
},
computed: {
@@ -175,9 +176,10 @@ export default {
this.$refs.dropdown.close();
},
- async showTanukiBotChat() {
- // This will be implemented in the following MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/117930
- return true;
+ showTanukiBotChat() {
+ this.$refs.dropdown.close();
+
+ this.helpCenterState.showTanukiBotChatDrawer = true;
},
async showWhatsNew() {
diff --git a/app/assets/javascripts/super_sidebar/constants.js b/app/assets/javascripts/super_sidebar/constants.js
index 4f5b027c138..f3f71feaa8a 100644
--- a/app/assets/javascripts/super_sidebar/constants.js
+++ b/app/assets/javascripts/super_sidebar/constants.js
@@ -20,6 +20,10 @@ export const sidebarState = Vue.observable({
closePeekTimer: null,
});
+export const helpCenterState = Vue.observable({
+ showTanukiBotChatDrawer: false,
+});
+
export const MAX_FREQUENT_PROJECTS_COUNT = 5;
export const MAX_FREQUENT_GROUPS_COUNT = 3;
diff --git a/app/assets/javascripts/vue_shared/issuable/list/constants.js b/app/assets/javascripts/vue_shared/issuable/list/constants.js
index 1b71819bdc2..7ece3b60bd5 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/constants.js
+++ b/app/assets/javascripts/vue_shared/issuable/list/constants.js
@@ -1,7 +1,7 @@
import { STATUS_ALL, STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
import { __ } from '~/locale';
-export const IssuableListTabs = [
+export const issuableListTabs = [
{
id: 'state-opened',
name: STATUS_OPEN,
@@ -22,7 +22,7 @@ export const IssuableListTabs = [
},
];
-export const AvailableSortOptions = [
+export const availableSortOptions = [
{
id: 1,
title: __('Created date'),
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
index 3c999d166dc..c7ac0b71510 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
@@ -5,7 +5,6 @@ import { s__ } from '~/locale';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { TYPENAME_ISSUE, TYPENAME_WORK_ITEM } from '~/graphql_shared/constants';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getIssueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql';
import { isMetaKey } from '~/lib/utils/common_utils';
import { getParameterByName, setUrlParams, updateHistory } from '~/lib/utils/url_utility';
@@ -21,7 +20,6 @@ import getWorkItemLinksQuery from '../../graphql/work_item_links.query.graphql';
import addHierarchyChildMutation from '../../graphql/add_hierarchy_child.mutation.graphql';
import removeHierarchyChildMutation from '../../graphql/remove_hierarchy_child.mutation.graphql';
import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql';
-import workItemQuery from '../../graphql/work_item.query.graphql';
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
import WidgetWrapper from '../widget_wrapper.vue';
import WorkItemDetailModal from '../work_item_detail_modal.vue';
@@ -43,14 +41,8 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [glFeatureFlagMixin()],
inject: ['projectPath', 'reportAbusePath'],
props: {
- workItemId: {
- type: String,
- required: false,
- default: null,
- },
issuableId: {
type: Number,
required: false,
@@ -75,10 +67,8 @@ export default {
this.error = e.message || this.$options.i18n.fetchError;
},
async result() {
- const { id, iid } = this.childUrlParams();
- this.activeChild = this.fetchByIid
- ? this.children.find((child) => child.iid === iid) ?? {}
- : this.children.find((child) => child.id === id) ?? {};
+ const iid = getParameterByName('work_item_iid');
+ this.activeChild = this.children.find((child) => child.iid === iid) ?? {};
await this.$nextTick();
if (!isEmpty(this.activeChild)) {
this.$refs.modal.show();
@@ -147,31 +137,11 @@ export default {
childrenCountLabel() {
return this.isLoading && this.children.length === 0 ? '...' : this.children.length;
},
- fetchByIid() {
- return true;
- },
},
mounted() {
- if (!isEmpty(this.childUrlParams())) {
- this.addWorkItemQuery(this.childUrlParams());
- }
+ this.addWorkItemQuery(getParameterByName('work_item_iid'));
},
methods: {
- childUrlParams() {
- const params = {};
- if (this.fetchByIid) {
- const iid = getParameterByName('work_item_iid');
- if (iid) {
- params.iid = iid;
- }
- } else {
- const workItemId = getParameterByName('work_item_id');
- if (workItemId) {
- params.id = convertToGraphQLId(TYPENAME_WORK_ITEM, workItemId);
- }
- }
- return params;
- },
showAddForm(formType) {
this.$refs.wrapper.show();
this.isShownAddForm = true;
@@ -200,11 +170,8 @@ export default {
this.removeHierarchyChild(child);
this.activeToast = this.$toast.show(s__('WorkItem|Task deleted'));
},
- updateWorkItemIdUrlQuery({ id, iid } = {}) {
- const params = this.fetchByIid
- ? { work_item_iid: iid }
- : { work_item_id: getIdFromGraphQLId(id) };
- updateHistory({ url: setUrlParams(params), replace: true });
+ updateWorkItemIdUrlQuery({ iid } = {}) {
+ updateHistory({ url: setUrlParams({ work_item_iid: iid }), replace: true });
},
async addHierarchyChild(workItem) {
return this.$apollo.mutate({
@@ -251,31 +218,28 @@ export default {
});
}
},
- addWorkItemQuery({ id, iid }) {
- const variables = this.fetchByIid
- ? {
- fullPath: this.projectPath,
- iid,
- }
- : {
- id,
- };
+ addWorkItemQuery(iid) {
+ if (!iid) {
+ return;
+ }
+
this.$apollo.addSmartQuery('prefetchedWorkItem', {
- query() {
- return this.fetchByIid ? workItemByIidQuery : workItemQuery;
+ query: workItemByIidQuery,
+ variables: {
+ fullPath: this.projectPath,
+ iid,
},
- variables,
update(data) {
- return this.fetchByIid ? data.workspace.workItems.nodes[0] : data.workItem;
+ return data.workspace.workItems.nodes[0];
},
context: {
isSingleRequest: true,
},
});
},
- prefetchWorkItem({ id, iid }) {
+ prefetchWorkItem({ iid }) {
this.prefetch = setTimeout(
- () => this.addWorkItemQuery({ id, iid }),
+ () => this.addWorkItemQuery(iid),
DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
);
},
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
index dd0d50861e4..06d6d62e603 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
@@ -1,7 +1,6 @@
<script>
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-
+import { getParameterByName } from '~/lib/utils/url_utility';
import {
FORM_TYPES,
WIDGET_TYPE_HIERARCHY,
@@ -10,7 +9,6 @@ import {
WORK_ITEM_TYPE_ENUM_KEY_RESULT,
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
} from '../../constants';
-import workItemQuery from '../../graphql/work_item.query.graphql';
import workItemByIidQuery from '../../graphql/work_item_by_iid.query.graphql';
import WidgetWrapper from '../widget_wrapper.vue';
import OkrActionsSplitButton from './okr_actions_split_button.vue';
@@ -28,7 +26,6 @@ export default {
WorkItemLinksForm,
WorkItemLinkChild,
},
- mixins: [glFeatureFlagMixin()],
props: {
workItemType: {
type: String,
@@ -72,9 +69,6 @@ export default {
};
},
computed: {
- fetchByIid() {
- return true;
- },
childrenIds() {
return this.children.map((c) => c.id);
},
@@ -86,6 +80,9 @@ export default {
.some((hierarchy) => hierarchy.hasChildren);
},
},
+ mounted() {
+ this.addWorkItemQuery(getParameterByName('work_item_iid'));
+ },
methods: {
showAddForm(formType, childType) {
this.$refs.wrapper.show();
@@ -99,10 +96,10 @@ export default {
hideAddForm() {
this.isShownAddForm = false;
},
- prefetchWorkItem({ id, iid }) {
+ prefetchWorkItem({ iid }) {
if (this.workItemType !== WORK_ITEM_TYPE_VALUE_OBJECTIVE) {
this.prefetch = setTimeout(
- () => this.addWorkItemQuery({ id, iid }),
+ () => this.addWorkItemQuery(iid),
DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
);
}
@@ -112,22 +109,19 @@ export default {
clearTimeout(this.prefetch);
}
},
- addWorkItemQuery({ id, iid }) {
- const variables = this.fetchByIid
- ? {
- fullPath: this.projectPath,
- iid,
- }
- : {
- id,
- };
+ addWorkItemQuery(iid) {
+ if (!iid) {
+ return;
+ }
+
this.$apollo.addSmartQuery('prefetchedWorkItem', {
- query() {
- return this.fetchByIid ? workItemByIidQuery : workItemQuery;
+ query: workItemByIidQuery,
+ variables: {
+ fullPath: this.projectPath,
+ iid,
},
- variables,
update(data) {
- return this.fetchByIid ? data.workspace.workItems.nodes[0] : data.workItem;
+ return data.workspace.workItems.nodes[0];
},
context: {
isSingleRequest: true,
diff --git a/app/assets/javascripts/work_items/pages/create_work_item.vue b/app/assets/javascripts/work_items/pages/create_work_item.vue
index 9f653fc8f71..49ec12db4e1 100644
--- a/app/assets/javascripts/work_items/pages/create_work_item.vue
+++ b/app/assets/javascripts/work_items/pages/create_work_item.vue
@@ -1,13 +1,12 @@
<script>
import { GlButton, GlAlert, GlLoadingIcon, GlFormSelect } from '@gitlab/ui';
+import { TYPENAME_PROJECT } from '~/graphql_shared/constants';
import { getPreferredLocales, s__ } from '~/locale';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { sprintfWorkItem, I18N_WORK_ITEM_ERROR_CREATING } from '../constants';
import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql';
import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql';
-import { getWorkItemQuery } from '../utils';
+import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
import ItemTitle from '../components/item_title.vue';
@@ -22,7 +21,6 @@ export default {
ItemTitle,
GlFormSelect,
},
- mixins: [glFeatureFlagMixin()],
inject: ['fullPath'],
props: {
initialTitle: {
@@ -73,9 +71,6 @@ export default {
return sprintfWorkItem(I18N_WORK_ITEM_ERROR_CREATING, workItemType);
},
- fetchByIid() {
- return true;
- },
},
methods: {
async createWorkItem() {
@@ -96,45 +91,31 @@ export default {
},
update: (store, { data: { workItemCreate } }) => {
const { workItem } = workItemCreate;
- const data = this.fetchByIid
- ? {
- workspace: {
- // eslint-disable-next-line @gitlab/require-i18n-strings
- __typename: 'Project',
- id: workItem.project.id,
- workItems: {
- __typename: 'WorkItemConnection',
- nodes: [workItem],
- },
- },
- }
- : { workItem };
store.writeQuery({
- query: getWorkItemQuery(this.fetchByIid),
- variables: this.fetchByIid
- ? {
- fullPath: this.fullPath,
- iid: workItem.iid,
- }
- : {
- id: workItem.id,
+ query: workItemByIidQuery,
+ variables: {
+ fullPath: this.fullPath,
+ iid: workItem.iid,
+ },
+ data: {
+ workspace: {
+ __typename: TYPENAME_PROJECT,
+ id: workItem.project.id,
+ workItems: {
+ __typename: 'WorkItemConnection',
+ nodes: [workItem],
},
- data,
+ },
+ },
});
},
});
- const {
- data: {
- workItemCreate: {
- workItem: { id, iid },
- },
- },
- } = response;
- const routerParams = this.fetchByIid
- ? { name: 'workItem', params: { id: iid } }
- : { name: 'workItem', params: { id: `${getIdFromGraphQLId(id)}` } };
- this.$router.push(routerParams);
+
+ this.$router.push({
+ name: 'workItem',
+ params: { id: response.data.workItemCreate.workItem.iid },
+ });
} catch {
this.error = this.createErrorText;
}