From afdd52bbd497f33eb05b3993462718c734fd8ece Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Thu, 27 Feb 2025 13:40:00 +0530 Subject: [PATCH 01/19] Quick Start in React wip --- .../src/about/templates/admin_wizard.mako | 297 +----------------- apps/about/src/about/views.py | 1 + desktop/core/src/desktop/api2.py | 3 + desktop/core/src/desktop/api_public.py | 2 +- .../about/components/ko.connectorsConfig.js | 38 +-- .../js/apps/admin/Components/utils.tsx | 1 + .../js/apps/admin/Overview/Analytics.tsx | 56 ++++ .../js/apps/admin/Overview/ConfigStatus.tsx | 115 +++++++ .../js/apps/admin/Overview/Examples.tsx | 89 ++++++ .../js/apps/admin/Overview/Overview.scss | 55 ++++ .../apps/admin/Overview/OverviewTab.test.tsx | 15 + .../js/apps/admin/Overview/OverviewTab.tsx | 55 ++++ desktop/core/src/desktop/js/config/types.ts | 1 + .../src/desktop/js/reactComponents/imports.js | 3 + .../src/desktop/js/reactComponents/utils.ts | 22 ++ .../static/desktop/js/admin-wizard-inline.js | 182 +---------- .../src/desktop/templates/check_config.mako | 65 ---- desktop/core/src/desktop/templates/logs.mako | 5 - .../core/src/desktop/templates/metrics.mako | 14 +- 19 files changed, 442 insertions(+), 577 deletions(-) create mode 100644 desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx create mode 100644 desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx create mode 100644 desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx create mode 100644 desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss create mode 100644 desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx create mode 100644 desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx create mode 100644 desktop/core/src/desktop/js/reactComponents/utils.ts delete mode 100644 desktop/core/src/desktop/templates/check_config.mako diff --git a/apps/about/src/about/templates/admin_wizard.mako b/apps/about/src/about/templates/admin_wizard.mako index 6244cc4c9f4..c426485e7a0 100644 --- a/apps/about/src/about/templates/admin_wizard.mako +++ b/apps/about/src/about/templates/admin_wizard.mako @@ -14,306 +14,13 @@ ## See the License for the specific language governing permissions and ## limitations under the License. -<%! -import sys - -from django.urls import reverse - -from metadata.conf import OPTIMIZER, has_optimizer - -from desktop.auth.backend import is_admin -from desktop.conf import has_connectors -from desktop.views import commonheader, commonfooter - -if sys.version_info[0] > 2: - from django.utils.translation import gettext as _ -else: - from django.utils.translation import ugettext as _ -%> <%namespace name="layout" file="/about_layout.mako" /> -% if not is_embeddable: - ${ commonheader(_('Quick Start'), "quickstart", user, request, "70px") | n,unicode } -% endif - ${ layout.menubar(section='quick_start') } -
-
-
-

- % if is_admin(user): - ${ _('Quick Start') } - - % endif - - Hue™ - ${ version } - - - - Query. Explore. Repeat. -

- - % if is_admin(user): -
-
- - - -
-
-
-

${ _('Checking current configuration') }

- -
-
- -
-
-
-
-
- -
-

${ _('Add connectors to data services') }

- - % if has_connectors(): - ... ${ _('connectors installed') } - - - % else: - ${ _('Configuration') } -
- ${ _('Documentation') } - % endif -
- -
-
-

${ _('Install some data examples') }

- -
-
- -
-
-

${ _('Create or import users') }

- ${ _('User Admin') } -
- -
-

${ _('Anonymous usage analytics') }

- -
- - % if not is_embeddable: -
-

${ _('Skip wizard next time') }

- -
- % endif - -
-
- -
-
-
-
- ${ _('Back') } - ${ _('Next') } - ${ _('Done') } - ${ _('Hue and the Hue logo are trademarks of Cloudera, Inc.') } -
-
- % else: -
-

- ${ _('Learn more about Hue and SQL Querying on') } https://gethue.com. -
-
- ${ _('Hue and the Hue logo are trademarks of Cloudera, Inc.') } - % if not user.is_authenticated: -
- - ${ _('Sign in now!') } - - % endif -

-
- % endif - -
-
- +
+
-% if is_admin(user): - - - -% endif - -% if not is_embeddable: - ${ commonfooter(request, messages) | n,unicode } -% endif diff --git a/apps/about/src/about/views.py b/apps/about/src/about/views.py index 2fdc83aeb58..6e4b6064b87 100644 --- a/apps/about/src/about/views.py +++ b/apps/about/src/about/views.py @@ -31,6 +31,7 @@ def admin_wizard(request): else: apps = [] app_names = [app.name for app in sorted(apps, key=lambda app: app.menu_index)] + # instead of here, move this to config return render('admin_wizard.mako', request, { 'version': hue_version(), diff --git a/desktop/core/src/desktop/api2.py b/desktop/core/src/desktop/api2.py index 742e65f5ade..fddcd37b274 100644 --- a/desktop/core/src/desktop/api2.py +++ b/desktop/core/src/desktop/api2.py @@ -147,6 +147,9 @@ def get_config(request): config['storage_browser']['enable_extract_uploaded_archive'] = ENABLE_EXTRACT_UPLOADED_ARCHIVE.get() config['clusters'] = list(get_clusters(request.user).values()) config['documents'] = {'types': list(Document2.objects.documents(user=request.user).order_by().values_list('type', flat=True).distinct())} + apps = appmanager.get_apps(request.user) + app_names = [app.name for app in sorted(apps, key=lambda app: app.menu_index)] + config['app_names'] = app_names config['status'] = 0 return JsonResponse(config) diff --git a/desktop/core/src/desktop/api_public.py b/desktop/core/src/desktop/api_public.py index 53c3c9e1c8a..a65f1ead2e7 100644 --- a/desktop/core/src/desktop/api_public.py +++ b/desktop/core/src/desktop/api_public.py @@ -72,7 +72,7 @@ def download_hue_logs(request): return logs_api.download_hue_logs(django_request) -@api_view(["POST"]) +@api_view(["GET"]) def check_config(request): django_request = get_django_request(request) return desktop_api.check_config(django_request) diff --git a/desktop/core/src/desktop/js/apps/about/components/ko.connectorsConfig.js b/desktop/core/src/desktop/js/apps/about/components/ko.connectorsConfig.js index 4ebd3c11c1c..0984c5a8a9b 100644 --- a/desktop/core/src/desktop/js/apps/about/components/ko.connectorsConfig.js +++ b/desktop/core/src/desktop/js/apps/about/components/ko.connectorsConfig.js @@ -58,17 +58,17 @@ const TEMPLATE = ` value: $parent.selectedConnectorCategory, labelAttribute: 'name', entries: $parent.filterCategories, - linkTitle: '${ I18n('Category') }' + linkTitle: '${I18n('Category')}' } } ">
- - ${ I18n('Help') } + ${I18n('Help')} @@ -84,8 +84,8 @@ const TEMPLATE = ` - - + + @@ -100,10 +100,10 @@ const TEMPLATE = ` @@ -117,17 +117,17 @@ const TEMPLATE = `
${ I18n('Name') }${ I18n('Description') }${I18n('Name')}${I18n('Description')}
- ${ I18n('No connectors') } + ${I18n('No connectors')} - ${ I18n('Add one ?') } + ${I18n('Add one ?')}
- - + + @@ -150,23 +150,23 @@ const TEMPLATE = ` () - ${ I18n('Update') } + ${I18n('Update')} - ${ I18n('Delete') } + ${I18n('Delete')} - ${ I18n('Save') } + ${I18n('Save')} - ${ I18n('Cancel') } + ${I18n('Cancel')} - ${ I18n('Test connection') } + ${I18n('Test connection')} @@ -179,8 +179,8 @@ const TEMPLATE = `
${ I18n('Name') }${ I18n('Instances') }${I18n('Name')}${I18n('Instances')}
- ${ I18n('No connectors') } + ${I18n('No connectors')} - ${ I18n('Add one ?') } + ${I18n('Add one ?')}
- - + + @@ -195,7 +195,7 @@ const TEMPLATE = `
- ${ I18n('Connectors') } + ${I18n('Connectors')} diff --git a/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx b/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx index f821fd52198..7b12312a95c 100644 --- a/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx @@ -15,3 +15,4 @@ // limitations under the License. export const SERVER_LOGS_API_URL = '/api/v1/logs'; +export const INSTALL_APP_EXAMPLES_API_URL = '/api/v1/check_config'; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx new file mode 100644 index 00000000000..e128266781a --- /dev/null +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx @@ -0,0 +1,56 @@ +// Licensed to Cloudera, Inc. under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. Cloudera, Inc. licenses this file +// to you under the Apache License, Version 2.0 (the +// 'License'); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React, { useState } from 'react'; +import huePubSub from '../../../utils/huePubSub'; + +const saveCollectUsagePreference = async collectUsage => { + $.post('/about/update_preferences', { collect_usage: collectUsage ? 'on' : null }, data => { + if (data.status == 0) { + huePubSub.publish('hue.global.info', { message: 'Configuration updated' }); + } else { + huePubSub.publish('hue.global.error', { message: data.data }); + } + }); +}; + +const Analytics = (): JSX.Element => { + const [collectUsage, setCollectUsage] = useState(false); + + const handleCheckboxChange = async event => { + const newPreference = event.target.checked; + setCollectUsage(newPreference); + await saveCollectUsagePreference(newPreference); + }; + + return ( +
+

Anonymous usage analytics

+ +
+ ); +}; + +export default Analytics; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx new file mode 100644 index 00000000000..9da2f3a0839 --- /dev/null +++ b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx @@ -0,0 +1,115 @@ +// Licensed to Cloudera, Inc. under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. Cloudera, Inc. licenses this file +// to you under the Apache License, Version 2.0 (the +// 'License'); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assuming you have an API endpoint for `install_app_examples` + +import React from 'react'; +import { Spin, Alert, Table } from 'antd'; +import useLoadData from '../../../utils/hooks/useLoadData/useLoadData'; +import { INSTALL_APP_EXAMPLES_API_URL } from '../Components/utils'; +import { i18nReact } from '../../../utils/i18nReact'; +import './Overview.scss'; + +function ConfigStatus(): JSX.Element { + const { t } = i18nReact.useTranslation(); + const { data: installData, loading, error } = useLoadData(INSTALL_APP_EXAMPLES_API_URL); + + const columns = [ + { + dataIndex: 'name', + render: name => {name} + }, + { + key: 'details', + render: record => ( +
+ {record.value && ( +

+ {t('Current value')}: {record.value} +

+ )} +

{record.message}

+
+ ) + } + ]; + + if (error) { + return ( +
+ +
+ ); + } + + if (loading) { + return {t('Installing app examples...')}; + } + + const openConfigDocs = () => { + window.open('https://docs.gethue.com/administrator/configuration/', '_blank'); + }; + + const configErrorsExist = + installData && installData['config_errors'] && installData['config_errors'].length > 0; + + return ( + <> +
+

{t('Checking current configuration')}

+ {installData && installData['hue_config_dir'] && ( +
+ {t('Configuration files located in:')} + {installData['hue_config_dir']} +
+ )} + + {configErrorsExist ? ( + <> + + + {t('Potential misconfiguration detected.')} + {' '} + {t('Fix and restart Hue.')} + + } + type="warning" + className="config__alert-margin" + /> + +
${ I18n('Name') }${ I18n('Value') }${I18n('Name')}${I18n('Value')}
`${record.name}-${record.message.slice(1,50)}`} + pagination={false} + showHeader={false} + /> + + ) : ( + {t('All OK. Configuration check passed.')} + )} + + + ); +} + +export default ConfigStatus; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx new file mode 100644 index 00000000000..1e9c8ce89d0 --- /dev/null +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -0,0 +1,89 @@ +// Licensed to Cloudera, Inc. under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. Cloudera, Inc. licenses this file +// to you under the Apache License, Version 2.0 (the +// 'License'); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React, { useState } from 'react'; +import { Button } from 'antd'; +import { DownloadOutlined } from '@ant-design/icons'; +import { fetchWithCsrf } from '../../../reactComponents/utils'; +import huePubSub from '../../../utils/huePubSub'; + +const exampleApps = [ + { id: 'hive', name: 'Hive', old_name: 'beeswax' }, + { id: 'impala', name: 'Impala' }, + { id: 'search', name: 'Solr Search', data: ['log_analytics_demo', 'twitter_demo', 'yelp_demo'] }, + { id: 'spark', name: 'Spark', old_name: 'notebook' }, + { id: 'oozie', name: 'Oozie Editor/Dashboard' }, + { id: 'hbase', name: 'Hbase Browser' }, + { id: 'pig', name: 'Pig Editor' } +]; + +const Examples = (): JSX.Element => { + const [installingAppId, setInstallingAppId] = useState(null); + + const handleInstall = async appData => { + try { + setInstallingAppId(appData.id); + const appIdOrOldName = appData.old_name || appData.id; + const url = `/${appIdOrOldName}/install_examples`; + + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }; + + if (appData.data) { + options.body = JSON.stringify({ data: appData.data }); + } + + const response = await fetchWithCsrf(url, options); + + const responseBody = await response.json(); + if (responseBody.status == 0) { + if (responseBody.message) { + huePubSub.publish('hue.global.info', { message: responseBody.message }); + } else { + huePubSub.publish('hue.global.info', { message: 'Examples refreshed' }); + } + } else { + huePubSub.publish('hue.global.error', { message: responseBody.message }); + } + } finally { + setInstallingAppId(null); + } + }; + + return ( +
+

Install some data examples

+ {exampleApps.map(appData => ( +
+ +
+ ))} +
+ ); +}; + +export default Examples; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss new file mode 100644 index 00000000000..e7460d47c2b --- /dev/null +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss @@ -0,0 +1,55 @@ +// Licensed to Cloudera, Inc. under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. Cloudera, Inc. licenses this file +// to you under the Apache License, Version 2.0 (the +// 'License'); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@import '../../../components/styles/variables'; + +.antd.cuix { + .hue-overview-component { + background-color: $fluidx-gray-100; + padding: 24px; + + .config__address-value { + color: $fluidx-blue-600; + padding: 2px 4px; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + } + + .config-link { + color: $fluidx-blue-600; + cursor: pointer; + } + + .config__alert-margin { + margin: 10px; + } + + .config-table-name { + color: #1b2329; + font-size: 12px; + border-radius: 5px; + margin-left: 8px; + padding: 2px 4px; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + } + + .config__trademark-text{ + text-align: right; + padding: 10px; + } + } +} diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx new file mode 100644 index 00000000000..4f25869a01d --- /dev/null +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -0,0 +1,15 @@ +// Licensed to Cloudera, Inc. under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. Cloudera, Inc. licenses this file +// to you under the Apache License, Version 2.0 (the +// 'License'); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx new file mode 100644 index 00000000000..d8f5731abf1 --- /dev/null +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx @@ -0,0 +1,55 @@ +// Licensed to Cloudera, Inc. under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. Cloudera, Inc. licenses this file +// to you under the Apache License, Version 2.0 (the +// 'License'); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { Tabs } from 'antd'; +import Examples from './Examples'; +import ConfigStatus from './ConfigStatus'; +import Analytics from './Analytics'; +import { i18nReact } from '../../../utils/i18nReact'; +import './Overview.scss'; + +const Overview: React.FC = (): JSX.Element => { + const { t } = i18nReact.useTranslation(); + + const items = [ + { + label: t('ConfigStatus'), + key: '1', + children: + }, + { + label: t('Examples'), + key: '2', + children: + }, + { + label: t('Analytics'), + key: '3', + children: + } + ]; + + return ( + //if admin(user) +
+ +
Hue and the Hue logo are trademarks of Cloudera, Inc.
+
+ ); +}; + +export default Overview; diff --git a/desktop/core/src/desktop/js/config/types.ts b/desktop/core/src/desktop/js/config/types.ts index fbe2f180fba..3ef2c409499 100644 --- a/desktop/core/src/desktop/js/config/types.ts +++ b/desktop/core/src/desktop/js/config/types.ts @@ -90,6 +90,7 @@ export interface HueConfig extends GenericApiResponse { has_computes: boolean; main_button_action: AppConfig; status: number; + app_names: string[]; hue_config: { enable_sharing: boolean; collect_usage: boolean; diff --git a/desktop/core/src/desktop/js/reactComponents/imports.js b/desktop/core/src/desktop/js/reactComponents/imports.js index 23b250ecb6d..c54da492738 100644 --- a/desktop/core/src/desktop/js/reactComponents/imports.js +++ b/desktop/core/src/desktop/js/reactComponents/imports.js @@ -10,6 +10,9 @@ export async function loadComponent(name) { case 'StorageBrowserPage': return (await import('../apps/storageBrowser/StorageBrowserPage')).default; + case 'Overview': + return (await import('../apps/admin/Overview/OverviewTab')).default; + case 'Metrics': return (await import('../apps/admin/Metrics/MetricsTab')).default; diff --git a/desktop/core/src/desktop/js/reactComponents/utils.ts b/desktop/core/src/desktop/js/reactComponents/utils.ts new file mode 100644 index 00000000000..3410960f122 --- /dev/null +++ b/desktop/core/src/desktop/js/reactComponents/utils.ts @@ -0,0 +1,22 @@ +// Extend the Window interface to include the CSRF_TOKEN property +declare global { + interface Window { + CSRF_TOKEN: string; + } +} + +interface FetchWithCsrfOptions extends RequestInit { + headers?: HeadersInit; +} + +async function fetchWithCsrf(url: string, options: FetchWithCsrfOptions = {}): Promise { + const headers = new Headers(options.headers || {}); + headers.append('X-CSRFToken', window.CSRF_TOKEN); + + return fetch(url, { + ...options, + headers: headers + }); +} + +export { fetchWithCsrf }; diff --git a/desktop/core/src/desktop/static/desktop/js/admin-wizard-inline.js b/desktop/core/src/desktop/static/desktop/js/admin-wizard-inline.js index 4f55611ad6c..8c02214656e 100644 --- a/desktop/core/src/desktop/static/desktop/js/admin-wizard-inline.js +++ b/desktop/core/src/desktop/static/desktop/js/admin-wizard-inline.js @@ -1,179 +1,3 @@ -routie.setPathname('/about'); -var AdminWizardViewModel = function () { - var self = this; - - self.connectors = ko.observableArray(); - self.isInstallingSample = ko.observable(false); - - self.installConnectorDataExample = function (connector, event) { - self.isInstallingSample(true); - $.post("/notebook/install_examples", { - connector: connector.id - }, function (data) { - if (data.message) { - huePubSub.publish('hue.global.info', { message: data.message }); - } - if (data.errorMessage) { - huePubSub.publish('hue.global.error', { message: data.errorMessage }); - } - if (data.status == 0 && $(event.target).data("is-connector")) { - huePubSub.publish('cluster.config.refresh.config'); - } - }).always(function (data) { - self.isInstallingSample(false); - }); - } -}; - -function installConnectorExample() { - var button = $(this); - $(button).button('loading'); - $.post(button.data("sample-url"), function (data) { - if (data.status == 0) { - if (data.message) { - huePubSub.publish('hue.global.info', { message: data.message }); - } else { - huePubSub.publish('hue.global.info', { message: 'Examples refreshed' }); - } - if ($(button).data("is-connector")) { - huePubSub.publish('cluster.config.refresh.config'); - } - } else { - huePubSub.publish('hue.global.error', { message: data.message }); - } - }) - .always(function (data) { - $(button).button('reset'); - }); -} - -$(document).ready(function () { - - var adminWizardViewModel = new AdminWizardViewModel(); - ko.applyBindings(adminWizardViewModel, $('#adminWizardComponents')[0]); - - function checkConfig() { - $.get("/desktop/debug/check_config", function (response) { - $("#check-config-section .spinner").css({ - 'position': 'absolute', - 'top': '-100px' - }); - $("#check-config-section .info").html(response); - $("#check-config-section .info").removeClass('hide'); - }) - .fail(function () { - huePubSub.publish('hue.global.error', { message: 'Check config failed: ' }); - }); - } - - $("[rel='popover']").popover(); - - - $(".installBtn").click(installConnectorExample); - - $(".installAllBtn").click(function () { - var button = $(this); - $(button).button('loading'); - var calls = jQuery.map($(button).data("sample-data"), function (app) { - return $.post($(button).data("sample-url"), { data: app }, function (data) { - if (data.status != 0) { - huePubSub.publish('hue.global.error', { message: data.message }); - } - }); - }); - $.when.apply(this, calls) - .then(function () { - huePubSub.publish('hue.global.info', { message: 'Examples refreshed' }); - }) - .always(function (data) { - $(button).button('reset'); - }); - }); - - var currentStep = "step1"; - - routie({ - "step1": function () { - showStep("step1"); - }, - "step2": function () { - showStep("step2"); - }, - "step3": function () { - showStep("step3"); - }, - "step4": function () { - showStep("step4"); - } - }); - - if (window.location.hash === '') { - checkConfig(); - } - - function showStep(step) { - if (window.location.hash === '#step1') { - checkConfig(); - } - - currentStep = step; - if (step != "step1") { - $("#backBtn").removeClass("disabled"); - } else { - $("#backBtn").addClass("disabled"); - } - - if (step != $(".stepDetails:last").attr("id")) { - $("#nextBtn").removeClass("hide"); - $("#doneBtn").addClass("hide"); - } else { - $("#nextBtn").addClass("hide"); - $("#doneBtn").removeClass("hide"); - } - - $("a.step").parent().removeClass("active"); - $("a.step[href='#" + step + "']").parent().addClass("active"); - if (step == "step4") { - $("#lastStep").parent().addClass("active"); - } - $(".stepDetails").hide(); - $("#" + step).show(); - } - - $("#backBtn").click(function () { - var nextStep = (currentStep.substr(4) * 1 - 1); - if (nextStep >= 1) { - routie("step" + nextStep); - } - }); - - $("#nextBtn").click(function () { - var nextStep = (currentStep.substr(4) * 1 + 1); - if (nextStep <= $(".step").length) { - routie("step" + nextStep); - } - }); - - $("#doneBtn").click(function () { - huePubSub.publish('open.link', "/"); - }); - - $(".updatePreferences").click(function () { - $.post("/about/update_preferences", $("input").serialize(), function (data) { - if (data.status == 0) { - huePubSub.publish('hue.global.info', { message: 'Configuration updated' }); - } else { - huePubSub.publish('hue.global.error', { message: data.data }); - } - }); - }); - - $("#updateSkipWizard").prop('checked', $.cookie("hueLandingPage", { path: "/" }) == "home"); - - $("#updateSkipWizard").change(function () { - $.cookie("hueLandingPage", this.checked ? "home" : "wizard", { - path: "/", - secure: window.location.protocol.indexOf('https') > -1 - }); - }); -}); \ No newline at end of file +(function () { + window.createReactComponents('#Overview'); +})(); diff --git a/desktop/core/src/desktop/templates/check_config.mako b/desktop/core/src/desktop/templates/check_config.mako deleted file mode 100644 index 367671a9179..00000000000 --- a/desktop/core/src/desktop/templates/check_config.mako +++ /dev/null @@ -1,65 +0,0 @@ -## Licensed to Cloudera, Inc. under one -## or more contributor license agreements. See the NOTICE file -## distributed with this work for additional information -## regarding copyright ownership. Cloudera, Inc. licenses this file -## to you under the Apache License, Version 2.0 (the -## "License"); you may not use this file except in compliance -## with the License. You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. - -<%! -import sys - -from desktop.conf import has_connectors -from desktop.auth.backend import is_hue_admin - -if sys.version_info[0] > 2: - from django.utils.translation import gettext as _ -else: - from django.utils.translation import ugettext as _ -%> - -% if is_hue_admin(user): - ${ _('Configuration files located in') } ${ conf_dir } -% endif - -

- -% if error_list: -
- - ${ _('Potential misconfiguration detected.') } - - % if not has_connectors(): - ${ _('Fix and restart Hue.') } - % endif -
-
-
- % for error in error_list: - - - - - % endfor -
- - ${ error['name'] | n } - - - ## Doesn't make sense to print the value of a BoundContainer - % if 'value' in error: - ${ _('Current value:') } ${ error['value'] }
- % endif - ${ error['message'] | n } -
-% else: -
${ _('All OK. Configuration check passed.') }
-% endif diff --git a/desktop/core/src/desktop/templates/logs.mako b/desktop/core/src/desktop/templates/logs.mako index 97c1c38d0ec..3321a5b600d 100644 --- a/desktop/core/src/desktop/templates/logs.mako +++ b/desktop/core/src/desktop/templates/logs.mako @@ -13,11 +13,6 @@ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. -<%! -import re -import sys -from desktop.views import commonheader, commonfooter -%> <%namespace name="layout" file="about_layout.mako" /> diff --git a/desktop/core/src/desktop/templates/metrics.mako b/desktop/core/src/desktop/templates/metrics.mako index eebe10f54cb..b625a695384 100644 --- a/desktop/core/src/desktop/templates/metrics.mako +++ b/desktop/core/src/desktop/templates/metrics.mako @@ -13,18 +13,9 @@ ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ## See the License for the specific language governing permissions and ## limitations under the License. -<%! -import sys -from desktop.views import commonheader, commonfooter -from desktop import conf -%> <%namespace name="layout" file="about_layout.mako" /> -%if not is_embeddable: -${ commonheader(_('Metrics'), "about", user, request) | n,unicode } -%endif - ${layout.menubar(section='metrics')} @@ -32,7 +23,4 @@ ${layout.menubar(section='metrics')}
- -%if not is_embeddable: -${ commonfooter(request, messages) | n,unicode } -%endif + From c45c1882ea3eece3cdd73a7f95b0b50f9d5c53db Mon Sep 17 00:00:00 2001 From: Ram Prasad Agarwal Date: Sat, 8 Mar 2025 14:12:45 +0530 Subject: [PATCH 02/19] fixes the api interface error --- .../js/apps/admin/Overview/ConfigStatus.tsx | 32 +++++++++++++------ .../js/apps/admin/Overview/Overview.scss | 4 +-- .../js/apps/admin/Overview/OverviewTab.tsx | 4 ++- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx index 9da2f3a0839..c6028e28aa5 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx @@ -23,9 +23,18 @@ import { INSTALL_APP_EXAMPLES_API_URL } from '../Components/utils'; import { i18nReact } from '../../../utils/i18nReact'; import './Overview.scss'; +interface ConfigError { + name: string; + message: string; +} +interface CheckConfigResponse { + hue_config_dir: string; + config_errors: ConfigError[]; +} + function ConfigStatus(): JSX.Element { const { t } = i18nReact.useTranslation(); - const { data: installData, loading, error } = useLoadData(INSTALL_APP_EXAMPLES_API_URL); + const { data, loading, error } = useLoadData(INSTALL_APP_EXAMPLES_API_URL); const columns = [ { @@ -60,28 +69,27 @@ function ConfigStatus(): JSX.Element { } if (loading) { - return {t('Installing app examples...')}; + return ; } const openConfigDocs = () => { window.open('https://docs.gethue.com/administrator/configuration/', '_blank'); }; - const configErrorsExist = - installData && installData['config_errors'] && installData['config_errors'].length > 0; + const configErrorsExist = data && data['config_errors'] && data['config_errors'].length > 0; return ( <>

{t('Checking current configuration')}

- {installData && installData['hue_config_dir'] && ( + {data && data['hue_config_dir'] && (
{t('Configuration files located in:')} - {installData['hue_config_dir']} + {data['hue_config_dir']}
)} - {configErrorsExist ? ( + {!configErrorsExist && data ? ( <> `${record.name}-${record.message.slice(1,50)}`} + rowKey={record => `${record.name}-${record.message.slice(1, 50)}`} pagination={false} showHeader={false} /> ) : ( - {t('All OK. Configuration check passed.')} + )} diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss index e7460d47c2b..5b531c75ca9 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss @@ -34,7 +34,7 @@ } .config__alert-margin { - margin: 10px; + margin: 10px 0; } .config-table-name { @@ -47,7 +47,7 @@ border: 1px solid #e1e1e8; } - .config__trademark-text{ + .config__trademark-text { text-align: right; padding: 10px; } diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx index d8f5731abf1..c31900c0194 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx @@ -47,7 +47,9 @@ const Overview: React.FC = (): JSX.Element => { //if admin(user)
-
Hue and the Hue logo are trademarks of Cloudera, Inc.
+
+ Hue and the Hue logo are trademarks of Cloudera, Inc. +
); }; From d9fc5ef7bf7a972764a900d23e24c9fb7080a87e Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Sun, 16 Mar 2025 13:51:41 +0530 Subject: [PATCH 03/19] Fixes based on review comments --- .gitignore | 1 + desktop/core/src/desktop/api2_tests.py | 2 +- .../js/apps/admin/Overview/Analytics.tsx | 67 ++++++++---- .../js/apps/admin/Overview/ConfigStatus.tsx | 86 +++++++-------- .../js/apps/admin/Overview/Examples.tsx | 103 +++++++++++++----- .../js/apps/admin/Overview/Overview.scss | 27 ++++- .../apps/admin/Overview/OverviewTab.test.tsx | 76 +++++++++++++ .../js/apps/admin/Overview/OverviewTab.tsx | 17 ++- .../src/desktop/js/reactComponents/utils.ts | 40 ++++--- 9 files changed, 298 insertions(+), 121 deletions(-) diff --git a/.gitignore b/.gitignore index 851e7cf4a38..71483606871 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,7 @@ desktop/core/ext-py/lxml/src/lxml/lxml-version.h # Coverage reports .coverage* +coverage* coverage.xml reports diff --git a/desktop/core/src/desktop/api2_tests.py b/desktop/core/src/desktop/api2_tests.py index d801781b6ff..47e883aa4b5 100644 --- a/desktop/core/src/desktop/api2_tests.py +++ b/desktop/core/src/desktop/api2_tests.py @@ -931,7 +931,7 @@ class TestCheckConfigAPI: def test_check_config_success(self): with patch('desktop.api2.os.path.realpath') as mock_hue_conf_dir: with patch('desktop.api2._get_config_errors') as mock_get_config_errors: - request = Mock(method='POST') + request = Mock(method='GET') mock_hue_conf_dir.return_value = '/test/hue/conf' mock_get_config_errors.return_value = [ {"name": "Hive", "message": "The application won't work without a running HiveServer2."}, diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx index e128266781a..907ee739260 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx @@ -16,19 +16,46 @@ import React, { useState } from 'react'; import huePubSub from '../../../utils/huePubSub'; +import { i18nReact } from '../../../utils/i18nReact'; +import { post } from '../../../api/utils'; +import './Overview.scss'; -const saveCollectUsagePreference = async collectUsage => { - $.post('/about/update_preferences', { collect_usage: collectUsage ? 'on' : null }, data => { - if (data.status == 0) { - huePubSub.publish('hue.global.info', { message: 'Configuration updated' }); - } else { - huePubSub.publish('hue.global.error', { message: data.data }); - } - }); -}; +interface PostResponse { + status: number; + message?: string; +} const Analytics = (): JSX.Element => { const [collectUsage, setCollectUsage] = useState(false); + const { t } = i18nReact.useTranslation(); + + // const saveCollectUsagePreference = async collectUsage => { + // $.post('/about/update_preferences', { collect_usage: collectUsage ? 'on' : null }, data => { + // if (data.status == 0) { + // huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); + // } else { + // huePubSub.publish('hue.global.error', { message: t(data.data) }); + // } + // }); + // }; + +const saveCollectUsagePreference = async (collectUsage: boolean) => { + try { + const response = await post('/about/update_preferences', { + collect_usage: collectUsage ? 'on' : null + }); + + if (response.status === 0) { + huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); + } else { + huePubSub.publish('hue.global.error', { + message: t(response.message || 'Error updating configuration') + }); + } + } catch (err) { + huePubSub.publish('hue.global.error', { message: t(String(err)) }); + } +}; const handleCheckboxChange = async event => { const newPreference = event.target.checked; @@ -37,17 +64,17 @@ const Analytics = (): JSX.Element => { }; return ( -
-

Anonymous usage analytics

-
`${record.name}-${record.message.slice(1, 50)}`} - pagination={false} - showHeader={false} - /> - - ) : ( + {configErrorsExist && data ? ( + <> + + {t('Potential misconfiguration detected.')} + {' '} + {t('Fix and restart Hue.')} + + } + type="warning" className="config__alert-margin" /> - )} - - + +
`${record.name}-${record.message.slice(1, 50)}`} + pagination={false} + showHeader={false} + /> + + ) : ( + + )} + ); } diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 1e9c8ce89d0..4990864b830 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -17,8 +17,11 @@ import React, { useState } from 'react'; import { Button } from 'antd'; import { DownloadOutlined } from '@ant-design/icons'; -import { fetchWithCsrf } from '../../../reactComponents/utils'; +// import { fetchWithCsrf } from '../../../reactComponents/utils'; +import { get } from '../../../api/utils'; import huePubSub from '../../../utils/huePubSub'; +import { i18nReact } from '../../../utils/i18nReact'; +import './Overview.scss' const exampleApps = [ { id: 'hive', name: 'Hive', old_name: 'beeswax' }, @@ -30,46 +33,86 @@ const exampleApps = [ { id: 'pig', name: 'Pig Editor' } ]; +type InstallExamplesResponse = { + status: number; + message?: string; +}; + const Examples = (): JSX.Element => { + const { t } = i18nReact.useTranslation(); const [installingAppId, setInstallingAppId] = useState(null); const handleInstall = async appData => { - try { - setInstallingAppId(appData.id); - const appIdOrOldName = appData.old_name || appData.id; - const url = `/${appIdOrOldName}/install_examples`; + setInstallingAppId(appData.id); + const appIdOrOldName = appData.old_name || appData.id; + const url = `/${appIdOrOldName}/install_examples`; + const data = appData.data ? { data: appData.data } : null; - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' + get(url, data, { + method: 'POST', + silenceErrors: true + }) + .then(response => { + if (response.status === 0) { + const message = response.message ? t(response.message) : t('Examples refreshed'); + huePubSub.publish('hue.global.info', { message }); + } else { + const errorMessage = response.message + ? t(response.message) + : t('An error occurred while installing examples.'); + huePubSub.publish('hue.global.error', { message: errorMessage }); } - }; + }) + .catch(error => { + const errorMessage = + error && typeof error === 'object' && error.message + ? t(error.message) + : t('An unexpected error occurred'); + huePubSub.publish('hue.global.error', { message: errorMessage }); + }) + .finally(() => { + setInstallingAppId(null); + }); + }; - if (appData.data) { - options.body = JSON.stringify({ data: appData.data }); - } - const response = await fetchWithCsrf(url, options); + // const handleInstall = async (appData) => { + // try { + // setInstallingAppId(appData.id); + // const appIdOrOldName = appData.old_name || appData.id; + // const url = `/${appIdOrOldName}/install_examples`; - const responseBody = await response.json(); - if (responseBody.status == 0) { - if (responseBody.message) { - huePubSub.publish('hue.global.info', { message: responseBody.message }); - } else { - huePubSub.publish('hue.global.info', { message: 'Examples refreshed' }); - } - } else { - huePubSub.publish('hue.global.error', { message: responseBody.message }); - } - } finally { - setInstallingAppId(null); - } - }; + // const data = appData.data ? { data: appData.data } : {}; + + // const options = { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // } + // }; + // if (appData.data) { + // options.body = JSON.stringify({ data: appData.data }); + // } + // const response = await fetchWithCsrf(url, options); + + // const responseBody = await response.json(); + // if (responseBody.status == 0) { + // if (responseBody.message) { + // huePubSub.publish('hue.global.info', { message: t(responseBody.message) }); + // } else { + // huePubSub.publish('hue.global.info', { message: t('Examples refreshed') }); + // } + // } else { + // huePubSub.publish('hue.global.error', { message: t(responseBody.message) }); + // } + // } finally { + // setInstallingAppId(null); + // } + // }; return ( -
-

Install some data examples

+
+

{t('Install some data examples')}

{exampleApps.map(appData => (
- - + + @@ -100,10 +100,10 @@ const TEMPLATE = ` @@ -117,17 +117,17 @@ const TEMPLATE = `
${I18n('Name')}${I18n('Description')}${ I18n('Name') }${ I18n('Description') }
- ${I18n('No connectors')} + ${ I18n('No connectors') } - ${I18n('Add one ?')} + ${ I18n('Add one ?') }
- - + + @@ -150,23 +150,23 @@ const TEMPLATE = ` () - ${I18n('Update')} + ${ I18n('Update') } - ${I18n('Delete')} + ${ I18n('Delete') } - ${I18n('Save')} + ${ I18n('Save') } - ${I18n('Cancel')} + ${ I18n('Cancel') } - ${I18n('Test connection')} + ${ I18n('Test connection') } @@ -179,8 +179,8 @@ const TEMPLATE = `
${I18n('Name')}${I18n('Instances')}${ I18n('Name') }${ I18n('Instances') }
- ${I18n('No connectors')} + ${ I18n('No connectors') } - ${I18n('Add one ?')} + ${ I18n('Add one ?') }
- - + + @@ -195,7 +195,7 @@ const TEMPLATE = `
- ${I18n('Connectors')} + ${ I18n('Connectors') } diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx index 907ee739260..4b297c34379 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx @@ -39,23 +39,23 @@ const Analytics = (): JSX.Element => { // }); // }; -const saveCollectUsagePreference = async (collectUsage: boolean) => { - try { - const response = await post('/about/update_preferences', { - collect_usage: collectUsage ? 'on' : null - }); - - if (response.status === 0) { - huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); - } else { - huePubSub.publish('hue.global.error', { - message: t(response.message || 'Error updating configuration') + const saveCollectUsagePreference = async (collectUsage: boolean) => { + try { + const response = await post('/about/update_preferences', { + collect_usage: collectUsage ? 'on' : null }); + + if (response.status === 0) { + huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); + } else { + huePubSub.publish('hue.global.error', { + message: t(response.message || 'Error updating configuration') + }); + } + } catch (err) { + huePubSub.publish('hue.global.error', { message: t(String(err)) }); } - } catch (err) { - huePubSub.publish('hue.global.error', { message: t(String(err)) }); - } -}; + }; const handleCheckboxChange = async event => { const newPreference = event.target.checked; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx index 46afffe7b05..37439d3cd0d 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx @@ -69,10 +69,10 @@ function ConfigStatus(): JSX.Element { } if (loading) { - return ; + return ; } -const configErrorsExist = Boolean(data?.config_errors?.length); + const configErrorsExist = Boolean(data?.config_errors?.length); return (
diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 4990864b830..334fa43d7d5 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -21,7 +21,7 @@ import { DownloadOutlined } from '@ant-design/icons'; import { get } from '../../../api/utils'; import huePubSub from '../../../utils/huePubSub'; import { i18nReact } from '../../../utils/i18nReact'; -import './Overview.scss' +import './Overview.scss'; const exampleApps = [ { id: 'hive', name: 'Hive', old_name: 'beeswax' }, @@ -39,7 +39,7 @@ type InstallExamplesResponse = { }; const Examples = (): JSX.Element => { - const { t } = i18nReact.useTranslation(); + const { t } = i18nReact.useTranslation(); const [installingAppId, setInstallingAppId] = useState(null); const handleInstall = async appData => { @@ -75,7 +75,6 @@ const Examples = (): JSX.Element => { }); }; - // const handleInstall = async (appData) => { // try { // setInstallingAppId(appData.id); @@ -84,34 +83,34 @@ const Examples = (): JSX.Element => { // const data = appData.data ? { data: appData.data } : {}; - // const options = { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // } - // }; - // if (appData.data) { - // options.body = JSON.stringify({ data: appData.data }); - // } - // const response = await fetchWithCsrf(url, options); + // const options = { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json' + // } + // }; + // if (appData.data) { + // options.body = JSON.stringify({ data: appData.data }); + // } + // const response = await fetchWithCsrf(url, options); - // const responseBody = await response.json(); - // if (responseBody.status == 0) { - // if (responseBody.message) { - // huePubSub.publish('hue.global.info', { message: t(responseBody.message) }); - // } else { - // huePubSub.publish('hue.global.info', { message: t('Examples refreshed') }); - // } - // } else { - // huePubSub.publish('hue.global.error', { message: t(responseBody.message) }); - // } - // } finally { - // setInstallingAppId(null); - // } - // }; + // const responseBody = await response.json(); + // if (responseBody.status == 0) { + // if (responseBody.message) { + // huePubSub.publish('hue.global.info', { message: t(responseBody.message) }); + // } else { + // huePubSub.publish('hue.global.info', { message: t('Examples refreshed') }); + // } + // } else { + // huePubSub.publish('hue.global.error', { message: t(responseBody.message) }); + // } + // } finally { + // setInstallingAppId(null); + // } + // }; return ( -
+

{t('Install some data examples')}

{exampleApps.map(appData => (
diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index 742f5845404..a568fb483f8 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -21,16 +21,16 @@ import userEvent from '@testing-library/user-event'; import Overview from './OverviewTab'; import Analytics from './Analytics'; // import ApiHelper from '../../../api/apiHelper'; -// import saveCollectUsagePreference from +// import saveCollectUsagePreference from jest.mock('./ConfigStatus', () => () =>
MockedConfigStatusComponent
); jest.mock('./Examples', () => () =>
MockedExamplesComponent
); jest.mock('./Analytics', () => () =>
MockedAnalyticsComponent
); - jest.mock('../../../api/apiHelper', () => ({ - ...jest.requireActual('../../../api/apiHelper'), - updatePreferences: jest.fn(() => Promise.resolve({ status: 0, data: 'Success message' })) - })); +jest.mock('../../../api/apiHelper', () => ({ + ...jest.requireActual('../../../api/apiHelper'), + updatePreferences: jest.fn(() => Promise.resolve({ status: 0, data: 'Success message' })) +})); describe('OverviewTab', () => { beforeEach(() => { jest.clearAllMocks(); @@ -51,7 +51,8 @@ describe('OverviewTab', () => { test('shows the trademark text', () => { render(); - expect(screen.getByText('Hue and the Hue logo are trademarks of Cloudera, Inc.') + expect( + screen.getByText('Hue and the Hue logo are trademarks of Cloudera, Inc.') ).toBeInTheDocument(); }); @@ -66,26 +67,25 @@ describe('OverviewTab', () => { expect(overviewComponent).toBeNull(); }); - //verify table contents - // describe('Analytics Component', () => { - // test('renders Analytics tab and can interact with the checkbox', async () => { - // render(); - // const analyticsTabButton = screen.getByText('Analytics'); - // userEvent.click(analyticsTabButton); + // describe('Analytics Component', () => { + // test('renders Analytics tab and can interact with the checkbox', async () => { + // render(); + // const analyticsTabButton = screen.getByText('Analytics'); + // userEvent.click(analyticsTabButton); - // const checkbox = await screen.findByTitle('Check to enable usage analytics'); + // const checkbox = await screen.findByTitle('Check to enable usage analytics'); - // expect(checkbox).not.toBeChecked(); - // userEvent.click(checkbox); - // await waitFor(() => expect(checkbox).toBeChecked()); - // expect(ApiHelper.updatePreferences).toHaveBeenCalledWith({ collect_usage: 'on' }); - // userEvent.click(checkbox); - // await waitFor(() => expect(checkbox).not.toBeChecked()); - // expect(ApiHelper.updatePreferences).toHaveBeenCalledWith({ collect_usage: null }); - // }); - // }); + // expect(checkbox).not.toBeChecked(); + // userEvent.click(checkbox); + // await waitFor(() => expect(checkbox).toBeChecked()); + // expect(ApiHelper.updatePreferences).toHaveBeenCalledWith({ collect_usage: 'on' }); + // userEvent.click(checkbox); + // await waitFor(() => expect(checkbox).not.toBeChecked()); + // expect(ApiHelper.updatePreferences).toHaveBeenCalledWith({ collect_usage: null }); + // }); + // }); }); -//click on hive app and api of hive is called (toHaveBeenCalled()) \ No newline at end of file +//click on hive app and api of hive is called (toHaveBeenCalled()) diff --git a/desktop/core/src/desktop/js/reactComponents/utils.ts b/desktop/core/src/desktop/js/reactComponents/utils.ts index d24b842ffe4..a3d43d70a30 100644 --- a/desktop/core/src/desktop/js/reactComponents/utils.ts +++ b/desktop/core/src/desktop/js/reactComponents/utils.ts @@ -21,6 +21,4 @@ // export { fetchWithCsrf }; - - -// remove file \ No newline at end of file +// remove file From 5f47d4446ece44fff99cbef8322f68aa332b9eb3 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Wed, 19 Mar 2025 14:50:39 +0530 Subject: [PATCH 05/19] Fixing isAdmin check --- .../apps/admin/Overview/OverviewTab.test.tsx | 91 ++++++++++++++----- .../js/apps/admin/Overview/OverviewTab.tsx | 19 +--- 2 files changed, 72 insertions(+), 38 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index a568fb483f8..2a9c071f52c 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -16,25 +16,33 @@ import '@testing-library/jest-dom'; import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { queryByText, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Overview from './OverviewTab'; import Analytics from './Analytics'; -// import ApiHelper from '../../../api/apiHelper'; -// import saveCollectUsagePreference from +import saveCollectUsagePreference from '../Overview/Analytics'; +import * as hueConfigModule from '../../../config/hueConfig'; + +import { post } from '../../../api/utils'; jest.mock('./ConfigStatus', () => () =>
MockedConfigStatusComponent
); jest.mock('./Examples', () => () =>
MockedExamplesComponent
); jest.mock('./Analytics', () => () =>
MockedAnalyticsComponent
); -jest.mock('../../../api/apiHelper', () => ({ - ...jest.requireActual('../../../api/apiHelper'), - updatePreferences: jest.fn(() => Promise.resolve({ status: 0, data: 'Success message' })) +jest.mock('../../../config/hueConfig', () => ({ + getLastKnownConfig: jest.fn() })); + describe('OverviewTab', () => { - beforeEach(() => { - jest.clearAllMocks(); +beforeEach(() => { + (hueConfigModule.getLastKnownConfig as jest.Mock).mockReturnValue({ + hue_config: { is_admin: true } }); +}); + afterEach(() => { + jest.clearAllMocks(); + }); + test('renders the Tabs with the correct tab labels', () => { render(); expect(screen.getByText('ConfigStatus')).toBeInTheDocument(); @@ -56,34 +64,69 @@ describe('OverviewTab', () => { ).toBeInTheDocument(); }); - jest.mock('../../../config/hueConfig', () => ({ - getLastKnownConfig: () => ({ hue_config: { is_admin: false } }) - })); test('it should not render Overview for non-admin users', () => { - const { queryByTestId } = render(); - const overviewComponent = queryByTestId('overview-component'); + (hueConfigModule.getLastKnownConfig as jest.Mock).mockReturnValue({ + hue_config: { is_admin: false } + }); - expect(overviewComponent).toBeNull(); + const { queryByText } = render(); + const trademarkText = queryByText('Hue and the Hue logo are trademarks of Cloudera, Inc.'); + expect(trademarkText).toBeNull(); }); - //verify table contents + // jest.mock('../Overview/Analytics', () => ({ + // __esModule: true, + // saveCollectUsagePreference: jest.fn(() => Promise.resolve({ status: 0 })) + // })); + // jest.mock('../../../api/utils', () => ({ + // post: jest.fn(() => Promise.resolve({ status: 0 })) + // })); + + // describe('Analytics Component', () => { + // beforeEach(() => { + // jest.clearAllMocks(); + // }); + // test('renders Analytics tab and can interact with the checkbox', async () => { + // render(); + // const checkbox = screen.getByRole('checkbox', { + // name: /help improve hue with anonymous usage analytics\./i + // }); + + // expect(checkbox).not.toBeChecked(); + // await userEvent.click(checkbox); + // await waitFor(() => expect(checkbox).toBeChecked()); + // expect(saveCollectUsagePreference).toHaveBeenCalledWith(true); + + // await userEvent.click(checkbox); + // await waitFor(() => expect(checkbox).not.toBeChecked()); + // expect(saveCollectUsagePreference).toHaveBeenCalledWith(false); + // }); + // }); // describe('Analytics Component', () => { - // test('renders Analytics tab and can interact with the checkbox', async () => { - // render(); - // const analyticsTabButton = screen.getByText('Analytics'); - // userEvent.click(analyticsTabButton); + // beforeEach(() => { + // jest.clearAllMocks(); + // }); - // const checkbox = await screen.findByTitle('Check to enable usage analytics'); + // test('renders Analytics tab and can interact with the checkbox', async () => { + // render(); + // const checkbox = screen.getByRole('checkbox', { + // name: /help improve hue with anonymous usage analytics\./i + // }); // expect(checkbox).not.toBeChecked(); - // userEvent.click(checkbox); + // await userEvent.click(checkbox); // await waitFor(() => expect(checkbox).toBeChecked()); - // expect(ApiHelper.updatePreferences).toHaveBeenCalledWith({ collect_usage: 'on' }); - // userEvent.click(checkbox); + + // // Now that we are mocking 'post', we assert that the post API call was made + // expect(post).toHaveBeenCalledWith('/about/update_preferences', { collect_usage: 'on' }); + + // await userEvent.click(checkbox); // await waitFor(() => expect(checkbox).not.toBeChecked()); - // expect(ApiHelper.updatePreferences).toHaveBeenCalledWith({ collect_usage: null }); + + // // Again we check that the 'post' was called with the new state ('null' for off) + // expect(post).toHaveBeenCalledWith('/about/update_preferences', { collect_usage: null }); // }); // }); }); diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx index 48a62474c23..830100f1a67 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx @@ -20,18 +20,14 @@ import Examples from './Examples'; import ConfigStatus from './ConfigStatus'; import Analytics from './Analytics'; import { i18nReact } from '../../../utils/i18nReact'; -// import { getLastKnownConfig } from '../../../config/hueConfig'; +import { getLastKnownConfig } from '../../../config/hueConfig'; import './Overview.scss'; const Overview = (): JSX.Element | null => { const { t } = i18nReact.useTranslation(); - // const [isAdmin, setIsAdmin] = useState(false); - - // useEffect(() => { - // const config = getLastKnownConfig(); - // setIsAdmin(config?.hue_config.is_admin ?? false); - // }, []); + const config = getLastKnownConfig(); + const isAdmin = config?.hue_config.is_admin ?? false; const items = [ { label: t('ConfigStatus'), @@ -50,19 +46,14 @@ const Overview = (): JSX.Element | null => { } ]; - // if (!isAdmin) { - // // console.log('User is not admin, rendering null.'); - // return null; - // } - - return ( + return isAdmin ? (
Hue and the Hue logo are trademarks of Cloudera, Inc.
- ); + ) : null; }; export default Overview; From 7cd05a6d66d83a8f2d2126dd785088545891363b Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Thu, 20 Mar 2025 09:17:13 +0530 Subject: [PATCH 06/19] Changes after the review comments --- apps/about/src/about/views.py | 1 - .../js/apps/admin/Overview/Analytics.tsx | 12 +-- .../js/apps/admin/Overview/ConfigStatus.tsx | 10 +- .../js/apps/admin/Overview/Examples.tsx | 39 +------- .../js/apps/admin/Overview/Overview.scss | 13 +-- .../apps/admin/Overview/OverviewTab.test.tsx | 99 ++++++++++--------- .../js/apps/admin/Overview/OverviewTab.tsx | 4 +- .../src/desktop/js/reactComponents/utils.ts | 24 ----- 8 files changed, 68 insertions(+), 134 deletions(-) delete mode 100644 desktop/core/src/desktop/js/reactComponents/utils.ts diff --git a/apps/about/src/about/views.py b/apps/about/src/about/views.py index 6e4b6064b87..2fdc83aeb58 100644 --- a/apps/about/src/about/views.py +++ b/apps/about/src/about/views.py @@ -31,7 +31,6 @@ def admin_wizard(request): else: apps = [] app_names = [app.name for app in sorted(apps, key=lambda app: app.menu_index)] - # instead of here, move this to config return render('admin_wizard.mako', request, { 'version': hue_version(), diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx index 4b297c34379..9d77b90692c 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx @@ -29,16 +29,6 @@ const Analytics = (): JSX.Element => { const [collectUsage, setCollectUsage] = useState(false); const { t } = i18nReact.useTranslation(); - // const saveCollectUsagePreference = async collectUsage => { - // $.post('/about/update_preferences', { collect_usage: collectUsage ? 'on' : null }, data => { - // if (data.status == 0) { - // huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); - // } else { - // huePubSub.publish('hue.global.error', { message: t(data.data) }); - // } - // }); - // }; - const saveCollectUsagePreference = async (collectUsage: boolean) => { try { const response = await post('/about/update_preferences', { @@ -73,7 +63,7 @@ const Analytics = (): JSX.Element => { checked={collectUsage} onChange={handleCheckboxChange} /> -
diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx index 37439d3cd0d..69614dc4288 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx @@ -39,7 +39,7 @@ function ConfigStatus(): JSX.Element { const columns = [ { dataIndex: 'name', - render: name => {name} + render: name => {name} }, { key: 'details', @@ -47,7 +47,7 @@ function ConfigStatus(): JSX.Element {
{record.value && (

- {t('Current value')}: {record.value} + {t('Current value')}: {record.value}

)}

{record.message}

@@ -69,13 +69,13 @@ function ConfigStatus(): JSX.Element { } if (loading) { - return ; + return ; } const configErrorsExist = Boolean(data?.config_errors?.length); return ( -
+

{t('Checking current configuration')}

{data?.hue_config_dir && (
@@ -92,7 +92,7 @@ function ConfigStatus(): JSX.Element { {t('Potential misconfiguration detected.')} {' '} diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 334fa43d7d5..57c7ff82e4c 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -17,8 +17,7 @@ import React, { useState } from 'react'; import { Button } from 'antd'; import { DownloadOutlined } from '@ant-design/icons'; -// import { fetchWithCsrf } from '../../../reactComponents/utils'; -import { get } from '../../../api/utils'; +import { post } from '../../../api/utils'; import huePubSub from '../../../utils/huePubSub'; import { i18nReact } from '../../../utils/i18nReact'; import './Overview.scss'; @@ -48,7 +47,7 @@ const Examples = (): JSX.Element => { const url = `/${appIdOrOldName}/install_examples`; const data = appData.data ? { data: appData.data } : null; - get(url, data, { + post(url, data, { method: 'POST', silenceErrors: true }) @@ -75,40 +74,6 @@ const Examples = (): JSX.Element => { }); }; - // const handleInstall = async (appData) => { - // try { - // setInstallingAppId(appData.id); - // const appIdOrOldName = appData.old_name || appData.id; - // const url = `/${appIdOrOldName}/install_examples`; - - // const data = appData.data ? { data: appData.data } : {}; - - // const options = { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // } - // }; - // if (appData.data) { - // options.body = JSON.stringify({ data: appData.data }); - // } - // const response = await fetchWithCsrf(url, options); - - // const responseBody = await response.json(); - // if (responseBody.status == 0) { - // if (responseBody.message) { - // huePubSub.publish('hue.global.info', { message: t(responseBody.message) }); - // } else { - // huePubSub.publish('hue.global.info', { message: t('Examples refreshed') }); - // } - // } else { - // huePubSub.publish('hue.global.error', { message: t(responseBody.message) }); - // } - // } finally { - // setInstallingAppId(null); - // } - // }; - return (

{t('Install some data examples')}

diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss index 59001096098..dc2a83addd5 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss @@ -21,7 +21,7 @@ background-color: $fluidx-gray-100; padding: 24px; - .config--spin{ + .config__spin { display: flex; justify-content: center; align-items: center; @@ -36,7 +36,7 @@ border-radius: 2px; } - .config-link { + .config__link { color: $fluidx-blue-600; cursor: pointer; } @@ -45,7 +45,7 @@ margin: 10px 0; } - .config-table-name { + .config__table-name { color: $fluidx-gray-900; font-size: 12px; border-radius: 5px; @@ -55,16 +55,17 @@ border: 1px solid $fluidx-gray-400; } - .overview-examples, .overview-analytics{ + .overview-examples, + .overview-analytics { height: 100vh; } - .usage_analytics{ + .usage__analytics { display: inline-block; padding: 5px; } - .config__trademark-text { + .overview__trademark-text { text-align: right; padding: 10px; } diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index 2a9c071f52c..884a78d8f4e 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -16,32 +16,37 @@ import '@testing-library/jest-dom'; import React from 'react'; -import { queryByText, render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import Overview from './OverviewTab'; -import Analytics from './Analytics'; -import saveCollectUsagePreference from '../Overview/Analytics'; import * as hueConfigModule from '../../../config/hueConfig'; - +import Examples from './Examples'; +import Analytics from './Analytics'; import { post } from '../../../api/utils'; +jest.mock('../../../api/utils', () => ({ + post: jest.fn() +})); + +jest.unmock('./Analytics'); + jest.mock('./ConfigStatus', () => () =>
MockedConfigStatusComponent
); -jest.mock('./Examples', () => () =>
MockedExamplesComponent
); -jest.mock('./Analytics', () => () =>
MockedAnalyticsComponent
); +// jest.mock('./Examples', () => () =>
MockedExamplesComponent
); +// jest.mock('./Analytics', () => () =>
MockedAnalyticsComponent
); jest.mock('../../../config/hueConfig', () => ({ getLastKnownConfig: jest.fn() })); describe('OverviewTab', () => { -beforeEach(() => { - (hueConfigModule.getLastKnownConfig as jest.Mock).mockReturnValue({ - hue_config: { is_admin: true } + beforeEach(() => { + (hueConfigModule.getLastKnownConfig as jest.Mock).mockReturnValue({ + hue_config: { is_admin: true } + }); + }); + afterEach(() => { + jest.clearAllMocks(); }); -}); - afterEach(() => { - jest.clearAllMocks(); - }); test('renders the Tabs with the correct tab labels', () => { render(); @@ -64,7 +69,6 @@ beforeEach(() => { ).toBeInTheDocument(); }); - test('it should not render Overview for non-admin users', () => { (hueConfigModule.getLastKnownConfig as jest.Mock).mockReturnValue({ hue_config: { is_admin: false } @@ -75,35 +79,6 @@ beforeEach(() => { expect(trademarkText).toBeNull(); }); - - // jest.mock('../Overview/Analytics', () => ({ - // __esModule: true, - // saveCollectUsagePreference: jest.fn(() => Promise.resolve({ status: 0 })) - // })); - // jest.mock('../../../api/utils', () => ({ - // post: jest.fn(() => Promise.resolve({ status: 0 })) - // })); - - // describe('Analytics Component', () => { - // beforeEach(() => { - // jest.clearAllMocks(); - // }); - // test('renders Analytics tab and can interact with the checkbox', async () => { - // render(); - // const checkbox = screen.getByRole('checkbox', { - // name: /help improve hue with anonymous usage analytics\./i - // }); - - // expect(checkbox).not.toBeChecked(); - // await userEvent.click(checkbox); - // await waitFor(() => expect(checkbox).toBeChecked()); - // expect(saveCollectUsagePreference).toHaveBeenCalledWith(true); - - // await userEvent.click(checkbox); - // await waitFor(() => expect(checkbox).not.toBeChecked()); - // expect(saveCollectUsagePreference).toHaveBeenCalledWith(false); - // }); - // }); // describe('Analytics Component', () => { // beforeEach(() => { // jest.clearAllMocks(); @@ -119,16 +94,44 @@ beforeEach(() => { // await userEvent.click(checkbox); // await waitFor(() => expect(checkbox).toBeChecked()); - // // Now that we are mocking 'post', we assert that the post API call was made // expect(post).toHaveBeenCalledWith('/about/update_preferences', { collect_usage: 'on' }); // await userEvent.click(checkbox); // await waitFor(() => expect(checkbox).not.toBeChecked()); - - // // Again we check that the 'post' was called with the new state ('null' for off) // expect(post).toHaveBeenCalledWith('/about/update_preferences', { collect_usage: null }); // }); // }); -}); + describe('Analytics Component', () => { + test('renders Analytics tab and can interact with the checkbox', async () => { + (post as jest.Mock).mockResolvedValue({ status: 0, message: 'Success' }); + render(); + const checkbox = screen.getByTitle(/Check to enable usage analytics/i); + fireEvent.click(checkbox); + await waitFor(() => expect(checkbox).toBeChecked()); + expect(post).toHaveBeenCalledWith('/about/update_preferences', { + collect_usage: 'on' + }); + fireEvent.click(checkbox); + await waitFor(() => expect(checkbox).not.toBeChecked()); + expect(post).toHaveBeenCalledWith('/about/update_preferences', { + collect_usage: null + }); + }); + }); -//click on hive app and api of hive is called (toHaveBeenCalled()) + describe('Examples component', () => { + afterEach(cleanup); + test('click on Hive app and the API for Hive is called', async () => { + (post as jest.Mock).mockResolvedValue({ status: 0, message: 'Success' }); + render(); + const hiveButton = screen.getByText('Hive'); + userEvent.click(hiveButton); + await waitFor(() => { + expect(post).toHaveBeenCalledWith('/beeswax/install_examples', null, { + method: 'POST', + silenceErrors: true + }); + }); + }); + }); +}); diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx index 830100f1a67..d46330bc0bd 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx @@ -14,7 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Tabs } from 'antd'; import Examples from './Examples'; import ConfigStatus from './ConfigStatus'; @@ -49,7 +49,7 @@ const Overview = (): JSX.Element | null => { return isAdmin ? (
-
+
Hue and the Hue logo are trademarks of Cloudera, Inc.
diff --git a/desktop/core/src/desktop/js/reactComponents/utils.ts b/desktop/core/src/desktop/js/reactComponents/utils.ts deleted file mode 100644 index a3d43d70a30..00000000000 --- a/desktop/core/src/desktop/js/reactComponents/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -// // Extend the Window interface to include the CSRF_TOKEN property -// declare global { -// interface Window { -// CSRF_TOKEN: string; -// } -// } - -// interface FetchWithCsrfOptions extends RequestInit { -// headers?: HeadersInit; -// } - -// async function fetchWithCsrf(url: string, options: FetchWithCsrfOptions = {}): Promise { -// const headers = new Headers(options.headers || {}); -// headers.append('X-CSRFToken', window.CSRF_TOKEN); - -// return fetch(url, { -// ...options, -// headers: headers -// }); -// } - -// export { fetchWithCsrf }; - -// remove file From e33215f2acb2cde762c7b4bd979710240d0fb284 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Thu, 20 Mar 2025 13:40:39 +0530 Subject: [PATCH 07/19] Fixing unit tests --- .../apps/admin/Overview/OverviewTab.test.tsx | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index 884a78d8f4e..d91abc23853 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -79,28 +79,6 @@ describe('OverviewTab', () => { expect(trademarkText).toBeNull(); }); - // describe('Analytics Component', () => { - // beforeEach(() => { - // jest.clearAllMocks(); - // }); - - // test('renders Analytics tab and can interact with the checkbox', async () => { - // render(); - // const checkbox = screen.getByRole('checkbox', { - // name: /help improve hue with anonymous usage analytics\./i - // }); - - // expect(checkbox).not.toBeChecked(); - // await userEvent.click(checkbox); - // await waitFor(() => expect(checkbox).toBeChecked()); - - // expect(post).toHaveBeenCalledWith('/about/update_preferences', { collect_usage: 'on' }); - - // await userEvent.click(checkbox); - // await waitFor(() => expect(checkbox).not.toBeChecked()); - // expect(post).toHaveBeenCalledWith('/about/update_preferences', { collect_usage: null }); - // }); - // }); describe('Analytics Component', () => { test('renders Analytics tab and can interact with the checkbox', async () => { (post as jest.Mock).mockResolvedValue({ status: 0, message: 'Success' }); @@ -120,18 +98,45 @@ describe('OverviewTab', () => { }); describe('Examples component', () => { - afterEach(cleanup); - test('click on Hive app and the API for Hive is called', async () => { - (post as jest.Mock).mockResolvedValue({ status: 0, message: 'Success' }); - render(); - const hiveButton = screen.getByText('Hive'); - userEvent.click(hiveButton); - await waitFor(() => { - expect(post).toHaveBeenCalledWith('/beeswax/install_examples', null, { - method: 'POST', - silenceErrors: true + + const exampleApps = [ + { id: 'hive', name: 'Hive', old_name: 'beeswax' }, + { id: 'impala', name: 'Impala' }, + { + id: 'search', + name: 'Solr Search', + data: ['log_analytics_demo', 'twitter_demo', 'yelp_demo'] + }, + { id: 'spark', name: 'Spark', old_name: 'notebook' }, + { id: 'oozie', name: 'Oozie Editor/Dashboard' }, + { id: 'hbase', name: 'Hbase Browser' }, + { id: 'pig', name: 'Pig Editor' } + ]; + // We no longer need the hardcoded test_urls + + test.each(exampleApps)( + "when the '%s' button is clicked, the API call for installing examples is executed", + async appData => { + const resolvedValue = { status: 0, message: 'Success' }; + (post as jest.Mock).mockResolvedValue(resolvedValue); + render(); + + const appIdOrOldName = appData.old_name || appData.id; + const url = `/${appIdOrOldName}/install_examples`; + const expectedData = appData.data ? { data: appData.data } : null; + + let button = screen.getByText(appData.name); + fireEvent.click(button); + + await waitFor(() => { + expect(post).toHaveBeenCalledWith(url, expectedData, { + method: 'POST', + silenceErrors: true + }); }); - }); - }); + + (post as jest.Mock).mockClear(); + } + ); }); }); From d5f07d1c6c5c601cdae4dca38d8f2a590631cf0b Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Mon, 24 Mar 2025 13:36:31 +0530 Subject: [PATCH 08/19] Review comment changes --- .../js/apps/admin/Components/utils.tsx | 1 + .../js/apps/admin/Overview/Analytics.tsx | 24 +++++++++---------- .../js/apps/admin/Overview/ConfigStatus.tsx | 12 +++++----- .../js/apps/admin/Overview/Examples.tsx | 22 ++++++++--------- .../apps/admin/Overview/OverviewTab.test.tsx | 12 ++++------ .../js/apps/admin/Overview/OverviewTab.tsx | 9 ++++--- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx b/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx index 7b12312a95c..5747327d658 100644 --- a/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx @@ -16,3 +16,4 @@ export const SERVER_LOGS_API_URL = '/api/v1/logs'; export const INSTALL_APP_EXAMPLES_API_URL = '/api/v1/check_config'; +export const ANALYTICS_PREFERENCES_API_URL = '/about/update_preferences'; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx index 9d77b90692c..3fa2abdc475 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx @@ -17,21 +17,23 @@ import React, { useState } from 'react'; import huePubSub from '../../../utils/huePubSub'; import { i18nReact } from '../../../utils/i18nReact'; +import { Checkbox } from 'antd'; import { post } from '../../../api/utils'; +import { ANALYTICS_PREFERENCES_API_URL } from '../Components/utils'; import './Overview.scss'; -interface PostResponse { +interface UpdatePreferences { status: number; message?: string; } const Analytics = (): JSX.Element => { - const [collectUsage, setCollectUsage] = useState(false); + const [collectUsage, setCollectUsage] = useState(false); const { t } = i18nReact.useTranslation(); const saveCollectUsagePreference = async (collectUsage: boolean) => { try { - const response = await post('/about/update_preferences', { + const response = await post(ANALYTICS_PREFERENCES_API_URL, { collect_usage: collectUsage ? 'on' : null }); @@ -56,16 +58,12 @@ const Analytics = (): JSX.Element => { return (

{t('Anonymous usage analytics')}

- - +
+ + +
); }; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx index 69614dc4288..0e2056a0342 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx @@ -28,8 +28,8 @@ interface ConfigError { message: string; } interface CheckConfigResponse { - hue_config_dir: string; - config_errors: ConfigError[]; + hueConfigDir: string; + configErrors: ConfigError[]; } function ConfigStatus(): JSX.Element { @@ -72,15 +72,15 @@ function ConfigStatus(): JSX.Element { return ; } - const configErrorsExist = Boolean(data?.config_errors?.length); + const configErrorsExist = Boolean(data?.configErrors?.length); return (

{t('Checking current configuration')}

- {data?.hue_config_dir && ( + {data?.hueConfigDir && (
{t('Configuration files located in: ')} - {data['hue_config_dir']} + {data['hueConfigDir']}
)} @@ -104,7 +104,7 @@ function ConfigStatus(): JSX.Element { />
${I18n('Name')}${I18n('Value')}${ I18n('Name') }${ I18n('Value') }
`${record.name}-${record.message.slice(1, 50)}`} pagination={false} diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 57c7ff82e4c..2a8e77b5083 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -39,13 +39,13 @@ type InstallExamplesResponse = { const Examples = (): JSX.Element => { const { t } = i18nReact.useTranslation(); - const [installingAppId, setInstallingAppId] = useState(null); + const [installingAppId, setInstallingAppId] = useState(''); - const handleInstall = async appData => { - setInstallingAppId(appData.id); - const appIdOrOldName = appData.old_name || appData.id; + const handleInstall = async exampleApp => { + setInstallingAppId(exampleApp.id); + const appIdOrOldName = exampleApp.old_name || exampleApp.id; const url = `/${appIdOrOldName}/install_examples`; - const data = appData.data ? { data: appData.data } : null; + const data = exampleApp.data ? { data: exampleApp.data } : null; post(url, data, { method: 'POST', @@ -70,22 +70,22 @@ const Examples = (): JSX.Element => { huePubSub.publish('hue.global.error', { message: errorMessage }); }) .finally(() => { - setInstallingAppId(null); + setInstallingAppId(''); }); }; return (

{t('Install some data examples')}

- {exampleApps.map(appData => ( -
+ {exampleApps.map(exampleApp => ( +
))} diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index d91abc23853..6308483c715 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -16,8 +16,7 @@ import '@testing-library/jest-dom'; import React from 'react'; -import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import Overview from './OverviewTab'; import * as hueConfigModule from '../../../config/hueConfig'; import Examples from './Examples'; @@ -83,7 +82,8 @@ describe('OverviewTab', () => { test('renders Analytics tab and can interact with the checkbox', async () => { (post as jest.Mock).mockResolvedValue({ status: 0, message: 'Success' }); render(); - const checkbox = screen.getByTitle(/Check to enable usage analytics/i); + const checkbox = screen.getByLabelText(/Help improve Hue with anonymous usage analytics./i); + fireEvent.click(checkbox); await waitFor(() => expect(checkbox).toBeChecked()); expect(post).toHaveBeenCalledWith('/about/update_preferences', { @@ -98,7 +98,6 @@ describe('OverviewTab', () => { }); describe('Examples component', () => { - const exampleApps = [ { id: 'hive', name: 'Hive', old_name: 'beeswax' }, { id: 'impala', name: 'Impala' }, @@ -112,10 +111,9 @@ describe('OverviewTab', () => { { id: 'hbase', name: 'Hbase Browser' }, { id: 'pig', name: 'Pig Editor' } ]; - // We no longer need the hardcoded test_urls test.each(exampleApps)( - "when the '%s' button is clicked, the API call for installing examples is executed", + 'renders Examples tab and when any of the exampleApp is clicked, the API call for that particular example is executed', async appData => { const resolvedValue = { status: 0, message: 'Success' }; (post as jest.Mock).mockResolvedValue(resolvedValue); @@ -125,7 +123,7 @@ describe('OverviewTab', () => { const url = `/${appIdOrOldName}/install_examples`; const expectedData = appData.data ? { data: appData.data } : null; - let button = screen.getByText(appData.name); + const button = screen.getByText(appData.name); fireEvent.click(button); await waitFor(() => { diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx index d46330bc0bd..c617145bf58 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx @@ -23,7 +23,7 @@ import { i18nReact } from '../../../utils/i18nReact'; import { getLastKnownConfig } from '../../../config/hueConfig'; import './Overview.scss'; -const Overview = (): JSX.Element | null => { +const Overview = (): JSX.Element => { const { t } = i18nReact.useTranslation(); const config = getLastKnownConfig(); @@ -46,14 +46,17 @@ const Overview = (): JSX.Element | null => { } ]; - return isAdmin ? ( + if (!isAdmin) { + return <>; + } + return (
Hue and the Hue logo are trademarks of Cloudera, Inc.
- ) : null; + ); }; export default Overview; From 2c0216be712fd99cfd3f279d9ba41e8aa6f75e6b Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Tue, 25 Mar 2025 14:34:40 +0530 Subject: [PATCH 09/19] test changes --- .gitignore | 1 - apps/hive/src/hive/tests.py | 44 ------------------- .../js/apps/admin/Overview/OverviewTab.tsx | 2 +- 3 files changed, 1 insertion(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 71483606871..851e7cf4a38 100644 --- a/.gitignore +++ b/.gitignore @@ -63,7 +63,6 @@ desktop/core/ext-py/lxml/src/lxml/lxml-version.h # Coverage reports .coverage* -coverage* coverage.xml reports diff --git a/apps/hive/src/hive/tests.py b/apps/hive/src/hive/tests.py index 3ba8d8670a4..eb3f157f8e9 100644 --- a/apps/hive/src/hive/tests.py +++ b/apps/hive/src/hive/tests.py @@ -21,47 +21,3 @@ import aws from desktop.lib.django_test_util import make_logged_in_client - - -@pytest.mark.django_db -def test_config_check(): - with patch('beeswax.hive_site.get_metastore_warehouse_dir') as get_metastore_warehouse_dir: - with patch('aws.s3.s3fs.S3FileSystem._stats') as s3_stat: - with patch('aws.conf.is_enabled') as is_s3_enabled: - reset = aws.conf.AWS_ACCOUNTS.set_for_testing({ - 'default': { - 'region': 'us-east-1', - 'access_key_id': 'access_key_id', - 'secret_access_key': 'secret_access_key' - } - }), - warehouse = 's3a://yingsdx0602/data1/warehouse/tablespace/managed/hive' - get_metastore_warehouse_dir.return_value = warehouse - is_s3_enabled.return_value = True - s3_stat.return_value = Mock( - DIR_MODE=16895, - FILE_MODE=33206, - aclBit=False, - atime=None, - group='', - isDir=True, - mode=16895, - mtime=None, - name='hive', - path='s3a://yingchensdx/data1/warehouse/tablespace/managed/hive/', - size=0, - type='DIRECTORY', - user='' - ) - - try: - cli = make_logged_in_client() - resp = cli.get('/desktop/debug/check_config') - s3_stat.assert_called() - err_msg = 'Failed to access Hive warehouse: %s' % warehouse - if not isinstance(err_msg, bytes): - err_msg = err_msg.encode('utf-8') - assert err_msg not in resp.content, resp - finally: - for old_conf in reset: - old_conf() diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx index c617145bf58..be6b18be789 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx @@ -30,7 +30,7 @@ const Overview = (): JSX.Element => { const isAdmin = config?.hue_config.is_admin ?? false; const items = [ { - label: t('ConfigStatus'), + label: t('Config Status'), key: '1', children: }, From a83b142be96e6ce5c7478dbadf2aa585fdd5f8a8 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Tue, 25 Mar 2025 14:52:31 +0530 Subject: [PATCH 10/19] Test changes WIP --- .../js/apps/admin/Overview/Examples.tsx | 41 ++++++++----------- .../apps/admin/Overview/OverviewTab.test.tsx | 6 +-- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 2a8e77b5083..b7c2fb060a4 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -25,11 +25,11 @@ import './Overview.scss'; const exampleApps = [ { id: 'hive', name: 'Hive', old_name: 'beeswax' }, { id: 'impala', name: 'Impala' }, - { id: 'search', name: 'Solr Search', data: ['log_analytics_demo', 'twitter_demo', 'yelp_demo'] }, - { id: 'spark', name: 'Spark', old_name: 'notebook' }, - { id: 'oozie', name: 'Oozie Editor/Dashboard' }, { id: 'hbase', name: 'Hbase Browser' }, - { id: 'pig', name: 'Pig Editor' } + { id: 'pig', name: 'Pig Editor' }, + { id: 'oozie', name: 'Oozie Editor/Dashboard' }, + { id: 'spark', name: 'Spark', old_name: 'notebook' }, + { id: 'search', name: 'Solr Search', data: ['log_analytics_demo', 'twitter_demo', 'yelp_demo'] } ]; type InstallExamplesResponse = { @@ -43,30 +43,21 @@ const Examples = (): JSX.Element => { const handleInstall = async exampleApp => { setInstallingAppId(exampleApp.id); - const appIdOrOldName = exampleApp.old_name || exampleApp.id; - const url = `/${appIdOrOldName}/install_examples`; - const data = exampleApp.data ? { data: exampleApp.data } : null; + const url = '/api/v1/install_app_examples'; + const data = { app_name: exampleApp.id }; + + if (exampleApp.data) { + data['data'] = exampleApp.data; + } post(url, data, { method: 'POST', silenceErrors: true - }) - .then(response => { - if (response.status === 0) { - const message = response.message ? t(response.message) : t('Examples refreshed'); - huePubSub.publish('hue.global.info', { message }); - } else { - const errorMessage = response.message - ? t(response.message) - : t('An error occurred while installing examples.'); - huePubSub.publish('hue.global.error', { message: errorMessage }); - } - }) - .catch(error => { - const errorMessage = - error && typeof error === 'object' && error.message - ? t(error.message) - : t('An unexpected error occurred'); + }).then(response => { + const message = response.message ? t(response.message) : t('Examples refreshed'); + huePubSub.publish('hue.global.info', { message }); + }).catch(error => { + const errorMessage = error.message ? t(error.message): t('An error occurred while installing examples.'); huePubSub.publish('hue.global.error', { message: errorMessage }); }) .finally(() => { @@ -85,7 +76,7 @@ const Examples = (): JSX.Element => { disabled={installingAppId === exampleApp.id} icon={} > - {installingAppId === exampleApp.id ? 'Installing...' : exampleApp.name} + {installingAppId === exampleApp.id ? t('Installing...') : t(exampleApp.name)}
))} diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index 6308483c715..d7269678879 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -27,11 +27,7 @@ jest.mock('../../../api/utils', () => ({ post: jest.fn() })); -jest.unmock('./Analytics'); - jest.mock('./ConfigStatus', () => () =>
MockedConfigStatusComponent
); -// jest.mock('./Examples', () => () =>
MockedExamplesComponent
); -// jest.mock('./Analytics', () => () =>
MockedAnalyticsComponent
); jest.mock('../../../config/hueConfig', () => ({ getLastKnownConfig: jest.fn() @@ -49,7 +45,7 @@ describe('OverviewTab', () => { test('renders the Tabs with the correct tab labels', () => { render(); - expect(screen.getByText('ConfigStatus')).toBeInTheDocument(); + expect(screen.getByText('Config Status')).toBeInTheDocument(); expect(screen.getByText('Examples')).toBeInTheDocument(); expect(screen.getByText('Analytics')).toBeInTheDocument(); }); From 3185a2cf66e006c5dbebddcb45bc167c2537dae3 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Tue, 1 Apr 2025 15:38:45 +0530 Subject: [PATCH 11/19] Linting issues fixed --- .../src/desktop/js/apps/admin/Overview/Examples.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index b7c2fb060a4..62d6baba747 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -53,11 +53,15 @@ const Examples = (): JSX.Element => { post(url, data, { method: 'POST', silenceErrors: true - }).then(response => { + }) + .then(response => { const message = response.message ? t(response.message) : t('Examples refreshed'); huePubSub.publish('hue.global.info', { message }); - }).catch(error => { - const errorMessage = error.message ? t(error.message): t('An error occurred while installing examples.'); + }) + .catch(error => { + const errorMessage = error.message + ? t(error.message) + : t('An error occurred while installing examples.'); huePubSub.publish('hue.global.error', { message: errorMessage }); }) .finally(() => { From 93207dd52e9db8448a2304b5e4429e883f6aa178 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Tue, 1 Apr 2025 15:58:51 +0530 Subject: [PATCH 12/19] Test changes --- .../apps/admin/Overview/OverviewTab.test.tsx | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index d7269678879..271df5c7ca3 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -108,29 +108,38 @@ describe('OverviewTab', () => { { id: 'pig', name: 'Pig Editor' } ]; - test.each(exampleApps)( - 'renders Examples tab and when any of the exampleApp is clicked, the API call for that particular example is executed', - async appData => { - const resolvedValue = { status: 0, message: 'Success' }; + const resolvedValue = { status: 0, message: 'Success' }; + + exampleApps.forEach(appData => { + test(`clicking the install button for '${appData.name}' makes an API call to '/api/v1/install_app_examples'`, async () => { (post as jest.Mock).mockResolvedValue(resolvedValue); render(); - const appIdOrOldName = appData.old_name || appData.id; - const url = `/${appIdOrOldName}/install_examples`; - const expectedData = appData.data ? { data: appData.data } : null; - const button = screen.getByText(appData.name); fireEvent.click(button); await waitFor(() => { - expect(post).toHaveBeenCalledWith(url, expectedData, { - method: 'POST', - silenceErrors: true - }); + if (appData.data) { + expect(post).toHaveBeenCalledWith( + '/api/v1/install_app_examples', + { + app_name: appData.id, + data: appData.data + }, + expect.anything() + ); + } else { + expect(post).toHaveBeenCalledWith( + '/api/v1/install_app_examples', + { + app_name: appData.id + }, + expect.anything() + ); + } }); - (post as jest.Mock).mockClear(); - } - ); + }); + }); }); }); From 624d3b3bcdfad2cd2bc7b9eb7424c59cc79df4e8 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Thu, 3 Apr 2025 14:04:46 +0530 Subject: [PATCH 13/19] Re review changes WIP --- desktop/core/src/desktop/api2.py | 3 --- .../js/apps/admin/Components/{utils.tsx => utils.ts} | 0 .../js/apps/admin/Configuration/ConfigurationTab.tsx | 3 ++- .../src/desktop/js/apps/admin/Metrics/MetricsTab.tsx | 3 ++- .../src/desktop/js/apps/admin/Overview/Analytics.tsx | 8 ++++---- .../src/desktop/js/apps/admin/Overview/ConfigStatus.tsx | 9 +++++---- .../desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx | 2 +- desktop/core/src/desktop/js/config/types.ts | 1 - 8 files changed, 14 insertions(+), 15 deletions(-) rename desktop/core/src/desktop/js/apps/admin/Components/{utils.tsx => utils.ts} (100%) diff --git a/desktop/core/src/desktop/api2.py b/desktop/core/src/desktop/api2.py index fddcd37b274..742e65f5ade 100644 --- a/desktop/core/src/desktop/api2.py +++ b/desktop/core/src/desktop/api2.py @@ -147,9 +147,6 @@ def get_config(request): config['storage_browser']['enable_extract_uploaded_archive'] = ENABLE_EXTRACT_UPLOADED_ARCHIVE.get() config['clusters'] = list(get_clusters(request.user).values()) config['documents'] = {'types': list(Document2.objects.documents(user=request.user).order_by().values_list('type', flat=True).distinct())} - apps = appmanager.get_apps(request.user) - app_names = [app.name for app in sorted(apps, key=lambda app: app.menu_index)] - config['app_names'] = app_names config['status'] = 0 return JsonResponse(config) diff --git a/desktop/core/src/desktop/js/apps/admin/Components/utils.tsx b/desktop/core/src/desktop/js/apps/admin/Components/utils.ts similarity index 100% rename from desktop/core/src/desktop/js/apps/admin/Components/utils.tsx rename to desktop/core/src/desktop/js/apps/admin/Components/utils.ts diff --git a/desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx b/desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx index feaceda37b5..beed8b21522 100644 --- a/desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx @@ -15,7 +15,8 @@ // limitations under the License. import React, { useState, useEffect, useMemo } from 'react'; -import { Spin, Alert } from 'antd'; +import { Spin } from 'antd'; +import Alert from 'cuix/dist/components/Alert'; import { i18nReact } from '../../../utils/i18nReact'; import AdminHeader from '../AdminHeader'; import { ConfigurationValue } from './ConfigurationValue'; diff --git a/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTab.tsx b/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTab.tsx index a3135547d83..aad9629c7cd 100644 --- a/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTab.tsx @@ -16,7 +16,8 @@ import React, { useState, useEffect } from 'react'; import MetricsTable, { MetricsResponse, MetricsTableProps } from './MetricsTable'; -import { Spin, Alert } from 'antd'; +import { Spin } from 'antd'; +import Alert from 'cuix/dist/components/Alert'; import { get } from '../../../api/utils'; import { i18nReact } from '../../../utils/i18nReact'; import AdminHeader from '../AdminHeader'; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx index 3fa2abdc475..1cb9d301338 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx @@ -41,18 +41,18 @@ const Analytics = (): JSX.Element => { huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); } else { huePubSub.publish('hue.global.error', { - message: t(response.message || 'Error updating configuration') + message: t('Error updating configuration') }); } } catch (err) { - huePubSub.publish('hue.global.error', { message: t(String(err)) }); + huePubSub.publish('hue.global.error', { message: String(err) }); } }; - const handleCheckboxChange = async event => { + const handleCheckboxChange = event => { const newPreference = event.target.checked; setCollectUsage(newPreference); - await saveCollectUsagePreference(newPreference); + saveCollectUsagePreference(newPreference); }; return ( diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx index 0e2056a0342..841e2524706 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx @@ -14,10 +14,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Assuming you have an API endpoint for `install_app_examples` - import React from 'react'; -import { Spin, Alert, Table } from 'antd'; +// import { Spin } from 'antd'; +import Loading from 'cuix/dist/components/Loading'; +import Alert from 'cuix/dist/components/Alert'; +import Table from 'cuix/dist/components/Table'; import useLoadData from '../../../utils/hooks/useLoadData/useLoadData'; import { INSTALL_APP_EXAMPLES_API_URL } from '../Components/utils'; import { i18nReact } from '../../../utils/i18nReact'; @@ -69,7 +70,7 @@ function ConfigStatus(): JSX.Element { } if (loading) { - return ; + return ; } const configErrorsExist = Boolean(data?.configErrors?.length); diff --git a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx index 044d2f8b5cd..1c0dcf6d2ae 100644 --- a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx @@ -15,7 +15,7 @@ // limitations under the License. import React, { useState } from 'react'; -import { Alert } from 'antd'; +import Alert from 'cuix/dist/components/Alert'; import ServerLogsHeader from './ServerLogsHeader'; import { i18nReact } from '../../../utils/i18nReact'; import useLoadData from '../../../utils/hooks/useLoadData/useLoadData'; diff --git a/desktop/core/src/desktop/js/config/types.ts b/desktop/core/src/desktop/js/config/types.ts index 3ef2c409499..fbe2f180fba 100644 --- a/desktop/core/src/desktop/js/config/types.ts +++ b/desktop/core/src/desktop/js/config/types.ts @@ -90,7 +90,6 @@ export interface HueConfig extends GenericApiResponse { has_computes: boolean; main_button_action: AppConfig; status: number; - app_names: string[]; hue_config: { enable_sharing: boolean; collect_usage: boolean; From d8a7aea9d3c11f880fd341a22bb4735aa7740d28 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Wed, 9 Apr 2025 11:38:43 +0530 Subject: [PATCH 14/19] fixing nits based on reviews WIP WIP WIP --- .../desktop/js/apps/admin/Components/utils.ts | 3 +- .../admin/Configuration/ConfigurationTab.tsx | 6 +- .../js/apps/admin/Metrics/MetricsTab.tsx | 6 +- .../js/apps/admin/Metrics/MetricsTable.tsx | 2 +- .../js/apps/admin/Overview/Analytics.tsx | 34 +++--- .../js/apps/admin/Overview/ConfigStatus.tsx | 109 +++++++++--------- .../js/apps/admin/Overview/Examples.tsx | 14 +-- .../js/apps/admin/Overview/Overview.scss | 16 ++- .../js/apps/admin/Overview/OverviewTab.tsx | 8 +- .../admin/ServerLogs/ServerLogsHeader.scss | 3 + .../admin/ServerLogs/ServerLogsHeader.tsx | 6 +- .../apps/admin/ServerLogs/ServerLogsTab.tsx | 2 +- 12 files changed, 106 insertions(+), 103 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Components/utils.ts b/desktop/core/src/desktop/js/apps/admin/Components/utils.ts index 5747327d658..cdf23bc5281 100644 --- a/desktop/core/src/desktop/js/apps/admin/Components/utils.ts +++ b/desktop/core/src/desktop/js/apps/admin/Components/utils.ts @@ -15,5 +15,6 @@ // limitations under the License. export const SERVER_LOGS_API_URL = '/api/v1/logs'; -export const INSTALL_APP_EXAMPLES_API_URL = '/api/v1/check_config'; +export const CHECK_CONFIG_EXAMPLES_API_URL = '/api/v1/check_config'; export const ANALYTICS_PREFERENCES_API_URL = '/about/update_preferences'; +export const INSTALL_APP_EXAMPLES_API_URL = '/api/v1/install_app_examples'; diff --git a/desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx b/desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx index beed8b21522..82165937149 100644 --- a/desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx @@ -15,7 +15,7 @@ // limitations under the License. import React, { useState, useEffect, useMemo } from 'react'; -import { Spin } from 'antd'; +import Loading from 'cuix/dist/components/Loading'; import Alert from 'cuix/dist/components/Alert'; import { i18nReact } from '../../../utils/i18nReact'; import AdminHeader from '../AdminHeader'; @@ -151,7 +151,7 @@ const Configuration: React.FC = (): JSX.Element => { return (
- + {error && ( { ))} )} - +
); }; diff --git a/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTab.tsx b/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTab.tsx index aad9629c7cd..af9939779b9 100644 --- a/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTab.tsx @@ -16,7 +16,7 @@ import React, { useState, useEffect } from 'react'; import MetricsTable, { MetricsResponse, MetricsTableProps } from './MetricsTable'; -import { Spin } from 'antd'; +import Loading from 'cuix/dist/components/Loading'; import Alert from 'cuix/dist/components/Alert'; import { get } from '../../../api/utils'; import { i18nReact } from '../../../utils/i18nReact'; @@ -95,7 +95,7 @@ const Metrics: React.FC = (): JSX.Element => { return (
- + {!error && ( {
))}
- + ); }; diff --git a/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTable.tsx b/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTable.tsx index 491f3085c7d..1f5dd4bef39 100644 --- a/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTable.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Metrics/MetricsTable.tsx @@ -114,7 +114,7 @@ const MetricsTable: React.FC = ({ caption, dataSource }) => { () => dataSource.map(item => ({ ...item, - name: t(metricLabels[item.name]) || item.name + name: metricLabels[item.name] || item.name })), [dataSource] ); diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx index 1cb9d301338..ab9b1a58e00 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx @@ -17,7 +17,7 @@ import React, { useState } from 'react'; import huePubSub from '../../../utils/huePubSub'; import { i18nReact } from '../../../utils/i18nReact'; -import { Checkbox } from 'antd'; +import Input from 'cuix/dist/components/Input'; import { post } from '../../../api/utils'; import { ANALYTICS_PREFERENCES_API_URL } from '../Components/utils'; import './Overview.scss'; @@ -32,20 +32,16 @@ const Analytics = (): JSX.Element => { const { t } = i18nReact.useTranslation(); const saveCollectUsagePreference = async (collectUsage: boolean) => { - try { - const response = await post(ANALYTICS_PREFERENCES_API_URL, { - collect_usage: collectUsage ? 'on' : null - }); + const response = await post(ANALYTICS_PREFERENCES_API_URL, { + collect_usage: collectUsage ? 'on' : null + }); - if (response.status === 0) { - huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); - } else { - huePubSub.publish('hue.global.error', { - message: t('Error updating configuration') - }); - } - } catch (err) { - huePubSub.publish('hue.global.error', { message: String(err) }); + if (response.status === 0) { + huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); + } else { + huePubSub.publish('hue.global.error', { + message: t('Error updating configuration') + }); } }; @@ -58,8 +54,14 @@ const Analytics = (): JSX.Element => { return (

{t('Anonymous usage analytics')}

-
- +
+ diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx index 841e2524706..f669c89584a 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx @@ -15,12 +15,11 @@ // limitations under the License. import React from 'react'; -// import { Spin } from 'antd'; import Loading from 'cuix/dist/components/Loading'; import Alert from 'cuix/dist/components/Alert'; import Table from 'cuix/dist/components/Table'; import useLoadData from '../../../utils/hooks/useLoadData/useLoadData'; -import { INSTALL_APP_EXAMPLES_API_URL } from '../Components/utils'; +import { CHECK_CONFIG_EXAMPLES_API_URL } from '../Components/utils'; import { i18nReact } from '../../../utils/i18nReact'; import './Overview.scss'; @@ -35,7 +34,7 @@ interface CheckConfigResponse { function ConfigStatus(): JSX.Element { const { t } = i18nReact.useTranslation(); - const { data, loading, error } = useLoadData(INSTALL_APP_EXAMPLES_API_URL); + const { data, loading, error } = useLoadData(CHECK_CONFIG_EXAMPLES_API_URL); const columns = [ { @@ -48,7 +47,7 @@ function ConfigStatus(): JSX.Element {
{record.value && (

- {t('Current value')}: {record.value} + {t('Current value')}: {record.value}

)}

{record.message}

@@ -57,67 +56,63 @@ function ConfigStatus(): JSX.Element { } ]; - if (error) { - return ( -
- -
- ); - } - - if (loading) { - return ; - } - const configErrorsExist = Boolean(data?.configErrors?.length); return (
-

{t('Checking current configuration')}

- {data?.hueConfigDir && ( -
- {t('Configuration files located in: ')} - {data['hueConfigDir']} -
+ {loading && } + {error && ( + )} - - {configErrorsExist && data ? ( + {!loading && !error && ( <> - - - {t('Potential misconfiguration detected.')} - {' '} - {t('Fix and restart Hue.')} - - } - type="warning" - className="config__alert-margin" - /> +

{t('Checking current configuration')}

+ {data?.hueConfigDir && ( +
+ {t('Configuration files located in: ')} + {data['hueConfigDir']} +
+ )} -
`${record.name}-${record.message.slice(1, 50)}`} - pagination={false} - showHeader={false} - /> + {configErrorsExist && data ? ( + <> + + + {t('Potential misconfiguration detected.')} + {' '} + {t('Fix and restart Hue.')} + + } + type="warning" + className="config__alert-margin" + /> + +
`${record.name}-${record.message.slice(1, 50)}`} + pagination={false} + showHeader={false} + /> + + ) : ( + + )} - ) : ( - )} ); diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 62d6baba747..29cc06faa8b 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -17,6 +17,7 @@ import React, { useState } from 'react'; import { Button } from 'antd'; import { DownloadOutlined } from '@ant-design/icons'; +import { INSTALL_APP_EXAMPLES_API_URL } from '../Components/utils'; import { post } from '../../../api/utils'; import huePubSub from '../../../utils/huePubSub'; import { i18nReact } from '../../../utils/i18nReact'; @@ -43,24 +44,21 @@ const Examples = (): JSX.Element => { const handleInstall = async exampleApp => { setInstallingAppId(exampleApp.id); - const url = '/api/v1/install_app_examples'; + const url = INSTALL_APP_EXAMPLES_API_URL; const data = { app_name: exampleApp.id }; if (exampleApp.data) { data['data'] = exampleApp.data; } - post(url, data, { - method: 'POST', - silenceErrors: true - }) + post(url, data) .then(response => { - const message = response.message ? t(response.message) : t('Examples refreshed'); + const message = response.message ? response.message : t('Examples refreshed'); huePubSub.publish('hue.global.info', { message }); }) .catch(error => { const errorMessage = error.message - ? t(error.message) + ? error.message : t('An error occurred while installing examples.'); huePubSub.publish('hue.global.error', { message: errorMessage }); }) @@ -80,7 +78,7 @@ const Examples = (): JSX.Element => { disabled={installingAppId === exampleApp.id} icon={} > - {installingAppId === exampleApp.id ? t('Installing...') : t(exampleApp.name)} + {installingAppId === exampleApp.id ? t('Installing...') : exampleApp.name} ))} diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss index dc2a83addd5..869d12ef806 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss @@ -45,7 +45,7 @@ margin: 10px 0; } - .config__table-name { + .config__table-name, .config__table-value { color: $fluidx-gray-900; font-size: 12px; border-radius: 5px; @@ -60,9 +60,19 @@ height: 100vh; } + .analytics-checkbox-container { + display: flex; + align-items: center; + } + + .analytics__checkbox-icon { + width: auto; + height: auto; + min-height: 0; + } + .usage__analytics { - display: inline-block; - padding: 5px; + margin-left: 8px; } .overview__trademark-text { diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx index be6b18be789..f0d623d7872 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx @@ -20,14 +20,11 @@ import Examples from './Examples'; import ConfigStatus from './ConfigStatus'; import Analytics from './Analytics'; import { i18nReact } from '../../../utils/i18nReact'; -import { getLastKnownConfig } from '../../../config/hueConfig'; import './Overview.scss'; const Overview = (): JSX.Element => { const { t } = i18nReact.useTranslation(); - const config = getLastKnownConfig(); - const isAdmin = config?.hue_config.is_admin ?? false; const items = [ { label: t('Config Status'), @@ -46,14 +43,11 @@ const Overview = (): JSX.Element => { } ]; - if (!isAdmin) { - return <>; - } return (
- Hue and the Hue logo are trademarks of Cloudera, Inc. + t('Hue and the Hue logo are trademarks of Cloudera, Inc.')
); diff --git a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.scss b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.scss index 372016113fc..cf209a3149f 100644 --- a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.scss +++ b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.scss @@ -58,6 +58,9 @@ .server__checkbox-icon { margin-right: 2px; + width: auto; + height: auto; + min-height: 0; } .server__download-button { diff --git a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.tsx b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.tsx index b0d983ef81c..e139bf1bb2c 100644 --- a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.tsx +++ b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.tsx @@ -15,7 +15,7 @@ // limitations under the License. import React, { useState } from 'react'; -import { Input, Checkbox } from 'antd'; +import Input from 'cuix/dist/components/Input'; import Button from 'cuix/dist/components/Button'; import Search from '@cloudera/cuix-core/icons/react/SearchIcon'; import Download from '@cloudera/cuix-core/icons/react/DownloadIcon'; @@ -62,8 +62,8 @@ const ServerLogsHeader: React.FC = ({ />
- {t(`Host: ${hostName}`)} - {`${t('Host:')} ${hostName}`} + { setWrapLogs(e.target.checked); onWrapLogsChange(e.target.checked); diff --git a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx index 1c0dcf6d2ae..d5162836aca 100644 --- a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsTab.tsx @@ -43,7 +43,7 @@ const ServerLogs: React.FC = (): JSX.Element => { return (
From d3e86bd06eb50a4d029cd04ff15223d95569fc94 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Wed, 9 Apr 2025 14:14:24 +0530 Subject: [PATCH 15/19] Fixing unit tests WIP --- .../desktop/js/apps/admin/Components/utils.ts | 1 + .../js/apps/admin/Overview/Examples.tsx | 69 ++++++---- .../apps/admin/Overview/OverviewTab.test.tsx | 128 +++++++++--------- .../admin/ServerLogs/ServerLogsHeader.tsx | 3 +- 4 files changed, 113 insertions(+), 88 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Components/utils.ts b/desktop/core/src/desktop/js/apps/admin/Components/utils.ts index cdf23bc5281..f1ac03d4db3 100644 --- a/desktop/core/src/desktop/js/apps/admin/Components/utils.ts +++ b/desktop/core/src/desktop/js/apps/admin/Components/utils.ts @@ -18,3 +18,4 @@ export const SERVER_LOGS_API_URL = '/api/v1/logs'; export const CHECK_CONFIG_EXAMPLES_API_URL = '/api/v1/check_config'; export const ANALYTICS_PREFERENCES_API_URL = '/about/update_preferences'; export const INSTALL_APP_EXAMPLES_API_URL = '/api/v1/install_app_examples'; +export const INSTALL_AVAILABLE_EXAMPLES_API_URL = '/api/v1/available_app_examples'; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 29cc06faa8b..d935432406c 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -14,22 +14,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Button } from 'antd'; import { DownloadOutlined } from '@ant-design/icons'; -import { INSTALL_APP_EXAMPLES_API_URL } from '../Components/utils'; -import { post } from '../../../api/utils'; +import Loading from 'cuix/dist/components/Loading'; +import { + INSTALL_APP_EXAMPLES_API_URL, + INSTALL_AVAILABLE_EXAMPLES_API_URL +} from '../Components/utils'; +import { get, post } from '../../../api/utils'; import huePubSub from '../../../utils/huePubSub'; import { i18nReact } from '../../../utils/i18nReact'; import './Overview.scss'; -const exampleApps = [ - { id: 'hive', name: 'Hive', old_name: 'beeswax' }, - { id: 'impala', name: 'Impala' }, - { id: 'hbase', name: 'Hbase Browser' }, - { id: 'pig', name: 'Pig Editor' }, - { id: 'oozie', name: 'Oozie Editor/Dashboard' }, - { id: 'spark', name: 'Spark', old_name: 'notebook' }, +const exampleAppsWithData = [ { id: 'search', name: 'Solr Search', data: ['log_analytics_demo', 'twitter_demo', 'yelp_demo'] } ]; @@ -41,14 +39,33 @@ type InstallExamplesResponse = { const Examples = (): JSX.Element => { const { t } = i18nReact.useTranslation(); const [installingAppId, setInstallingAppId] = useState(''); + const [availableApps, setAvailableApps] = useState<[]>([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchAvailableApps = async () => { + setLoading(true); + try { + const response = await get(INSTALL_AVAILABLE_EXAMPLES_API_URL, {}); + setAvailableApps(response.apps); + } catch (error) { + console.error('Error fetching available app examples:', error); + } finally { + setLoading(false); + } + }; + + fetchAvailableApps(); + }, []); const handleInstall = async exampleApp => { setInstallingAppId(exampleApp.id); const url = INSTALL_APP_EXAMPLES_API_URL; const data = { app_name: exampleApp.id }; - if (exampleApp.data) { - data['data'] = exampleApp.data; + const appWithExtraData = exampleAppsWithData.find(app => app.id === exampleApp.id); + if (appWithExtraData && appWithExtraData.data) { + data['data'] = appWithExtraData.data; } post(url, data) @@ -70,18 +87,22 @@ const Examples = (): JSX.Element => { return (

{t('Install some data examples')}

- {exampleApps.map(exampleApp => ( -
- -
- ))} + {loading ? ( + + ) : ( + Object.entries(availableApps).map(([appId, appName]) => ( +
+ +
+ )) + )}
); }; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index 271df5c7ca3..4033e8aa07d 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -21,10 +21,15 @@ import Overview from './OverviewTab'; import * as hueConfigModule from '../../../config/hueConfig'; import Examples from './Examples'; import Analytics from './Analytics'; -import { post } from '../../../api/utils'; +import { + INSTALL_APP_EXAMPLES_API_URL, + INSTALL_AVAILABLE_EXAMPLES_API_URL +} from '../Components/utils'; +import { get, post } from '../../../api/utils'; jest.mock('../../../api/utils', () => ({ - post: jest.fn() + post: jest.fn(), + get: jest.fn() })); jest.mock('./ConfigStatus', () => () =>
MockedConfigStatusComponent
); @@ -57,23 +62,6 @@ describe('OverviewTab', () => { expect(screen.queryByText('MockedAnalyticsComponent')).not.toBeInTheDocument(); }); - test('shows the trademark text', () => { - render(); - expect( - screen.getByText('Hue and the Hue logo are trademarks of Cloudera, Inc.') - ).toBeInTheDocument(); - }); - - test('it should not render Overview for non-admin users', () => { - (hueConfigModule.getLastKnownConfig as jest.Mock).mockReturnValue({ - hue_config: { is_admin: false } - }); - - const { queryByText } = render(); - const trademarkText = queryByText('Hue and the Hue logo are trademarks of Cloudera, Inc.'); - expect(trademarkText).toBeNull(); - }); - describe('Analytics Component', () => { test('renders Analytics tab and can interact with the checkbox', async () => { (post as jest.Mock).mockResolvedValue({ status: 0, message: 'Success' }); @@ -94,51 +82,65 @@ describe('OverviewTab', () => { }); describe('Examples component', () => { - const exampleApps = [ - { id: 'hive', name: 'Hive', old_name: 'beeswax' }, - { id: 'impala', name: 'Impala' }, - { - id: 'search', - name: 'Solr Search', - data: ['log_analytics_demo', 'twitter_demo', 'yelp_demo'] - }, - { id: 'spark', name: 'Spark', old_name: 'notebook' }, - { id: 'oozie', name: 'Oozie Editor/Dashboard' }, - { id: 'hbase', name: 'Hbase Browser' }, - { id: 'pig', name: 'Pig Editor' } - ]; - - const resolvedValue = { status: 0, message: 'Success' }; - - exampleApps.forEach(appData => { - test(`clicking the install button for '${appData.name}' makes an API call to '/api/v1/install_app_examples'`, async () => { - (post as jest.Mock).mockResolvedValue(resolvedValue); - render(); - - const button = screen.getByText(appData.name); - fireEvent.click(button); - - await waitFor(() => { - if (appData.data) { - expect(post).toHaveBeenCalledWith( - '/api/v1/install_app_examples', - { - app_name: appData.id, - data: appData.data - }, - expect.anything() - ); - } else { - expect(post).toHaveBeenCalledWith( - '/api/v1/install_app_examples', - { - app_name: appData.id - }, - expect.anything() - ); - } + const availableAppsResponse = { + apps: { + hive: 'Hive', + impala: 'Impala' + } + }; + beforeEach(() => { + (get as jest.Mock).mockImplementation(url => { + if (url === INSTALL_AVAILABLE_EXAMPLES_API_URL) { + return Promise.resolve(availableAppsResponse); + } + return Promise.reject(); + }); + + (post as jest.Mock).mockImplementation(() => + Promise.resolve({ status: 0, message: 'Success' }) + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('fetch and display available apps', async () => { + render(); + + await waitFor(() => { + expect(get).toHaveBeenCalledWith(INSTALL_AVAILABLE_EXAMPLES_API_URL, {}); + Object.entries(availableAppsResponse.apps).forEach(([appName]) => { + expect(screen.getByText(appName)).toBeInTheDocument(); }); - (post as jest.Mock).mockClear(); + }); + }); + + test('post to install app example when the install button is clicked', async () => { + render(); + + await waitFor(() => { + expect(get).toHaveBeenCalledWith(INSTALL_AVAILABLE_EXAMPLES_API_URL, {}); + Object.entries(availableAppsResponse.apps).forEach(([appName]) => { + expect(screen.getByText(appName)).toBeInTheDocument(); + }); + }); + + const installButton = screen.getByText('Hive'); + fireEvent.click(installButton); + + await waitFor(() => { + expect(post).toHaveBeenCalledWith(INSTALL_APP_EXAMPLES_API_URL, { app_name: 'hive' }); + }); + expect(installButton.textContent).toContain('Hive'); + }); + + test('display a confirmation message after a successful install', async () => { + render(); + + const appName = 'Hive'; + await waitFor(() => { + expect(screen.getByText(appName)).toBeInTheDocument(); }); }); }); diff --git a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.tsx b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.tsx index e139bf1bb2c..e1d0f10704f 100644 --- a/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.tsx +++ b/desktop/core/src/desktop/js/apps/admin/ServerLogs/ServerLogsHeader.tsx @@ -63,7 +63,8 @@ const ServerLogsHeader: React.FC = ({
{`${t('Host:')} ${hostName}`} - { setWrapLogs(e.target.checked); onWrapLogsChange(e.target.checked); From a0c120393af4f4ec2c0c4faf8c612cc4a0635d7e Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Tue, 15 Apr 2025 16:22:05 +0530 Subject: [PATCH 16/19] Fixing the new urls to make them same as the old ones wip WIP --- .../js/apps/admin/Overview/Examples.tsx | 39 ++++++++++++++++--- .../apps/admin/Overview/OverviewTab.test.tsx | 33 ++-------------- 2 files changed, 38 insertions(+), 34 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index d935432406c..999596b2b68 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -31,6 +31,8 @@ const exampleAppsWithData = [ { id: 'search', name: 'Solr Search', data: ['log_analytics_demo', 'twitter_demo', 'yelp_demo'] } ]; +const excludedApps = ['notebook']; + type InstallExamplesResponse = { status: number; message?: string; @@ -39,7 +41,7 @@ type InstallExamplesResponse = { const Examples = (): JSX.Element => { const { t } = i18nReact.useTranslation(); const [installingAppId, setInstallingAppId] = useState(''); - const [availableApps, setAvailableApps] = useState<[]>([]); + const [availableApps, setAvailableApps] = useState<{ [key: string]: string }>({}); const [loading, setLoading] = useState(true); useEffect(() => { @@ -47,7 +49,13 @@ const Examples = (): JSX.Element => { setLoading(true); try { const response = await get(INSTALL_AVAILABLE_EXAMPLES_API_URL, {}); - setAvailableApps(response.apps); + if (response.apps) { + const filteredApps = Object.entries(response.apps) + .filter(([appId]) => !excludedApps.includes(appId)) + .reduce((apps, [appId, appName]) => ({ ...apps, [appId]: appName }), {}); + + setAvailableApps(filteredApps); + } } catch (error) { console.error('Error fetching available app examples:', error); } finally { @@ -61,13 +69,34 @@ const Examples = (): JSX.Element => { const handleInstall = async exampleApp => { setInstallingAppId(exampleApp.id); const url = INSTALL_APP_EXAMPLES_API_URL; - const data = { app_name: exampleApp.id }; + + let actualAppName; + switch (exampleApp.id) { + case 'spark': + actualAppName = 'notebook'; + break; + case 'jobsub': + actualAppName = 'oozie'; + break; + default: + actualAppName = exampleApp.id; + } + + const payload = { app_name: actualAppName }; const appWithExtraData = exampleAppsWithData.find(app => app.id === exampleApp.id); if (appWithExtraData && appWithExtraData.data) { - data['data'] = appWithExtraData.data; + payload['data'] = appWithExtraData.data; + appWithExtraData.data.map(eachData => { + payload['data'] = eachData; + installExamplesCall(url, payload); + }); + } else { + installExamplesCall(url, payload); } + }; + function installExamplesCall(url, data) { post(url, data) .then(response => { const message = response.message ? response.message : t('Examples refreshed'); @@ -82,7 +111,7 @@ const Examples = (): JSX.Element => { .finally(() => { setInstallingAppId(''); }); - }; + } return (
diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index 4033e8aa07d..28200878c7c 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -55,13 +55,6 @@ describe('OverviewTab', () => { expect(screen.getByText('Analytics')).toBeInTheDocument(); }); - test('renders the ConfigStatus tab by default', () => { - render(); - expect(screen.getByText('MockedConfigStatusComponent')).toBeInTheDocument(); - expect(screen.queryByText('MockedExamplesComponent')).not.toBeInTheDocument(); - expect(screen.queryByText('MockedAnalyticsComponent')).not.toBeInTheDocument(); - }); - describe('Analytics Component', () => { test('renders Analytics tab and can interact with the checkbox', async () => { (post as jest.Mock).mockResolvedValue({ status: 0, message: 'Success' }); @@ -110,38 +103,20 @@ describe('OverviewTab', () => { await waitFor(() => { expect(get).toHaveBeenCalledWith(INSTALL_AVAILABLE_EXAMPLES_API_URL, {}); - Object.entries(availableAppsResponse.apps).forEach(([appName]) => { + Object.entries(availableAppsResponse.apps).forEach(([, appName]) => { expect(screen.getByText(appName)).toBeInTheDocument(); }); }); }); - test('post to install app example when the install button is clicked', async () => { + test('post call to install app example when the install button is clicked', async () => { render(); await waitFor(() => { - expect(get).toHaveBeenCalledWith(INSTALL_AVAILABLE_EXAMPLES_API_URL, {}); - Object.entries(availableAppsResponse.apps).forEach(([appName]) => { - expect(screen.getByText(appName)).toBeInTheDocument(); - }); - }); - - const installButton = screen.getByText('Hive'); - fireEvent.click(installButton); - - await waitFor(() => { + const installButton = screen.getByText('Hive'); + fireEvent.click(installButton); expect(post).toHaveBeenCalledWith(INSTALL_APP_EXAMPLES_API_URL, { app_name: 'hive' }); }); - expect(installButton.textContent).toContain('Hive'); - }); - - test('display a confirmation message after a successful install', async () => { - render(); - - const appName = 'Hive'; - await waitFor(() => { - expect(screen.getByText(appName)).toBeInTheDocument(); - }); }); }); }); From 0cfb914e047d517dbe3b756e3d6875ec8034e121 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Thu, 17 Apr 2025 13:33:25 +0530 Subject: [PATCH 17/19] Final review changes wip linting --- .../desktop/js/apps/admin/Components/utils.ts | 1 + .../js/apps/admin/Overview/Analytics.tsx | 14 ++-- .../js/apps/admin/Overview/ConfigStatus.tsx | 13 ++-- .../js/apps/admin/Overview/Examples.tsx | 69 +++++++++---------- .../js/apps/admin/Overview/Overview.scss | 4 ++ .../apps/admin/Overview/OverviewTab.test.tsx | 41 ++++++++--- .../js/apps/admin/Overview/OverviewTab.tsx | 2 +- 7 files changed, 86 insertions(+), 58 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Components/utils.ts b/desktop/core/src/desktop/js/apps/admin/Components/utils.ts index f1ac03d4db3..3c4dc5f567b 100644 --- a/desktop/core/src/desktop/js/apps/admin/Components/utils.ts +++ b/desktop/core/src/desktop/js/apps/admin/Components/utils.ts @@ -19,3 +19,4 @@ export const CHECK_CONFIG_EXAMPLES_API_URL = '/api/v1/check_config'; export const ANALYTICS_PREFERENCES_API_URL = '/about/update_preferences'; export const INSTALL_APP_EXAMPLES_API_URL = '/api/v1/install_app_examples'; export const INSTALL_AVAILABLE_EXAMPLES_API_URL = '/api/v1/available_app_examples'; +export const HUE_DOCS_CONFIG_URL = 'https://docs.gethue.com/administrator/configuration/'; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx index ab9b1a58e00..b2a4a4dbd6a 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Analytics.tsx @@ -33,15 +33,19 @@ const Analytics = (): JSX.Element => { const saveCollectUsagePreference = async (collectUsage: boolean) => { const response = await post(ANALYTICS_PREFERENCES_API_URL, { - collect_usage: collectUsage ? 'on' : null + collect_usage: collectUsage }); if (response.status === 0) { - huePubSub.publish('hue.global.info', { message: t('Configuration updated') }); + const successMessage = collectUsage + ? t('Analytics have been activated.') + : t('Analytics have been deactivated.'); + huePubSub.publish('hue.global.info', { message: successMessage }); } else { - huePubSub.publish('hue.global.error', { - message: t('Error updating configuration') - }); + const errorMessage = collectUsage + ? t('Failed to activate analytics.') + : t('Failed to deactivate analytics.'); + huePubSub.publish('hue.global.error', { message: errorMessage }); } }; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx index f669c89584a..2b00c3080e0 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/ConfigStatus.tsx @@ -20,6 +20,7 @@ import Alert from 'cuix/dist/components/Alert'; import Table from 'cuix/dist/components/Table'; import useLoadData from '../../../utils/hooks/useLoadData/useLoadData'; import { CHECK_CONFIG_EXAMPLES_API_URL } from '../Components/utils'; +import { HUE_DOCS_CONFIG_URL } from '../Components/utils'; import { i18nReact } from '../../../utils/i18nReact'; import './Overview.scss'; @@ -32,7 +33,7 @@ interface CheckConfigResponse { configErrors: ConfigError[]; } -function ConfigStatus(): JSX.Element { +const ConfigStatus = (): JSX.Element => { const { t } = i18nReact.useTranslation(); const { data, loading, error } = useLoadData(CHECK_CONFIG_EXAMPLES_API_URL); @@ -83,11 +84,7 @@ function ConfigStatus(): JSX.Element { - + {t('Potential misconfiguration detected.')} {' '} {t('Fix and restart Hue.')} @@ -98,7 +95,7 @@ function ConfigStatus(): JSX.Element { />
`${record.name}-${record.message.slice(1, 50)}`} pagination={false} @@ -116,6 +113,6 @@ function ConfigStatus(): JSX.Element { )} ); -} +}; export default ConfigStatus; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 999596b2b68..fb48a473b40 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -15,7 +15,7 @@ // limitations under the License. import React, { useState, useEffect } from 'react'; -import { Button } from 'antd'; +import LinkButton from 'cuix/dist/components/Button/LinkButton'; import { DownloadOutlined } from '@ant-design/icons'; import Loading from 'cuix/dist/components/Loading'; import { @@ -46,7 +46,6 @@ const Examples = (): JSX.Element => { useEffect(() => { const fetchAvailableApps = async () => { - setLoading(true); try { const response = await get(INSTALL_AVAILABLE_EXAMPLES_API_URL, {}); if (response.apps) { @@ -66,7 +65,12 @@ const Examples = (): JSX.Element => { fetchAvailableApps(); }, []); - const handleInstall = async exampleApp => { + const installExamplesCall = async (url: string, data: Record) => { + const response = await post(url, data); + return response; + }; + + const handleInstall = (exampleApp: { id: string; name: string }) => { setInstallingAppId(exampleApp.id); const url = INSTALL_APP_EXAMPLES_API_URL; @@ -82,37 +86,30 @@ const Examples = (): JSX.Element => { actualAppName = exampleApp.id; } - const payload = { app_name: actualAppName }; - - const appWithExtraData = exampleAppsWithData.find(app => app.id === exampleApp.id); - if (appWithExtraData && appWithExtraData.data) { - payload['data'] = appWithExtraData.data; - appWithExtraData.data.map(eachData => { - payload['data'] = eachData; - installExamplesCall(url, payload); - }); - } else { - installExamplesCall(url, payload); + try { + const appWithExtraData = exampleAppsWithData.find(app => app.id === exampleApp.id); + if (appWithExtraData && appWithExtraData.data) { + for (const eachData of appWithExtraData.data) { + installExamplesCall(url, { + app_name: actualAppName, + data: eachData + }); + } + } else { + installExamplesCall(url, { app_name: actualAppName }); + } + const message = t('Examples refreshed'); + huePubSub.publish('hue.global.info', { message }); + } catch (error) { + const errorMessage = error.message + ? error.message + : t('An error occurred while installing examples.'); + huePubSub.publish('hue.global.error', { message: errorMessage }); + } finally { + setInstallingAppId(''); } }; - function installExamplesCall(url, data) { - post(url, data) - .then(response => { - const message = response.message ? response.message : t('Examples refreshed'); - huePubSub.publish('hue.global.info', { message }); - }) - .catch(error => { - const errorMessage = error.message - ? error.message - : t('An error occurred while installing examples.'); - huePubSub.publish('hue.global.error', { message: errorMessage }); - }) - .finally(() => { - setInstallingAppId(''); - }); - } - return (

{t('Install some data examples')}

@@ -121,14 +118,16 @@ const Examples = (): JSX.Element => { ) : ( Object.entries(availableApps).map(([appId, appName]) => (
- + {installingAppId === appId + ? t('Installing...') + : appName[0].toUpperCase() + appName.slice(1)} +
)) )} diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss index 869d12ef806..51c71ad19ae 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Overview.scss @@ -60,6 +60,10 @@ height: 100vh; } + .examples__install-btn{ + margin: 10px; + } + .analytics-checkbox-container { display: flex; align-items: center; diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx index 28200878c7c..659643c1735 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.test.tsx @@ -16,7 +16,8 @@ import '@testing-library/jest-dom'; import React from 'react'; -import { render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import Overview from './OverviewTab'; import * as hueConfigModule from '../../../config/hueConfig'; import Examples from './Examples'; @@ -61,15 +62,16 @@ describe('OverviewTab', () => { render(); const checkbox = screen.getByLabelText(/Help improve Hue with anonymous usage analytics./i); - fireEvent.click(checkbox); - await waitFor(() => expect(checkbox).toBeChecked()); + expect(checkbox).not.toBeChecked(); + await userEvent.click(checkbox); + expect(checkbox).toBeChecked(); expect(post).toHaveBeenCalledWith('/about/update_preferences', { - collect_usage: 'on' + collect_usage: true }); - fireEvent.click(checkbox); + userEvent.click(checkbox); await waitFor(() => expect(checkbox).not.toBeChecked()); expect(post).toHaveBeenCalledWith('/about/update_preferences', { - collect_usage: null + collect_usage: false }); }); }); @@ -78,7 +80,8 @@ describe('OverviewTab', () => { const availableAppsResponse = { apps: { hive: 'Hive', - impala: 'Impala' + impala: 'Impala', + search: 'Solr Search' } }; beforeEach(() => { @@ -109,14 +112,34 @@ describe('OverviewTab', () => { }); }); - test('post call to install app example when the install button is clicked', async () => { + test('post call to install apps without data like hive when the install button is clicked', async () => { render(); await waitFor(() => { const installButton = screen.getByText('Hive'); - fireEvent.click(installButton); + userEvent.click(installButton); expect(post).toHaveBeenCalledWith(INSTALL_APP_EXAMPLES_API_URL, { app_name: 'hive' }); }); }); + + test('post call to install Solr Search example and its data when the install button is clicked', async () => { + render(); + + const solrData = ['log_analytics_demo', 'twitter_demo', 'yelp_demo']; + await waitFor(() => screen.getByText('Solr Search')); + const installButton = screen.getByText('Solr Search'); + userEvent.click(installButton); + + await waitFor(() => { + solrData.forEach(dataEntry => { + expect(post).toHaveBeenCalledWith(INSTALL_APP_EXAMPLES_API_URL, { + app_name: 'search', + data: dataEntry + }); + }); + }); + + expect(post).toHaveBeenCalledTimes(solrData.length); + }); }); }); diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx index f0d623d7872..f9f2cf53d30 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/OverviewTab.tsx @@ -47,7 +47,7 @@ const Overview = (): JSX.Element => {
- t('Hue and the Hue logo are trademarks of Cloudera, Inc.') + {t('Hue and the Hue logo are trademarks of Cloudera, Inc.')}
); From 2ae295141fd89e58f80716685613206c238086ab Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Mon, 21 Apr 2025 09:30:52 +0530 Subject: [PATCH 18/19] Review changes --- .../desktop/js/apps/admin/Overview/Examples.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index fb48a473b40..9e4391c5525 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -70,7 +70,7 @@ const Examples = (): JSX.Element => { return response; }; - const handleInstall = (exampleApp: { id: string; name: string }) => { + const handleInstall = async (exampleApp: { id: string; name: string }) => { setInstallingAppId(exampleApp.id); const url = INSTALL_APP_EXAMPLES_API_URL; @@ -89,21 +89,22 @@ const Examples = (): JSX.Element => { try { const appWithExtraData = exampleAppsWithData.find(app => app.id === exampleApp.id); if (appWithExtraData && appWithExtraData.data) { - for (const eachData of appWithExtraData.data) { + const installPromises = appWithExtraData.data.map(eachData => installExamplesCall(url, { app_name: actualAppName, data: eachData - }); - } + }) + ); + await Promise.all(installPromises); } else { - installExamplesCall(url, { app_name: actualAppName }); + await installExamplesCall(url, { app_name: actualAppName }); } - const message = t('Examples refreshed'); + const message = `${exampleApp.name} ${t('examples installed successfully')}`; huePubSub.publish('hue.global.info', { message }); } catch (error) { const errorMessage = error.message - ? error.message - : t('An error occurred while installing examples.'); + ? `${t('An error occurred while installing')} ${exampleApp.name}: ${error.message}` + : `${t('An error occurred while installing')} ${exampleApp.name}.`; huePubSub.publish('hue.global.error', { message: errorMessage }); } finally { setInstallingAppId(''); From 74616c516911f745fb3adfaf91415c487828b783 Mon Sep 17 00:00:00 2001 From: Ananya_Agarwal Date: Wed, 23 Apr 2025 13:01:19 +0530 Subject: [PATCH 19/19] nit commit --- .../core/src/desktop/js/apps/admin/Overview/Examples.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx index 9e4391c5525..ea06830c01b 100644 --- a/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx +++ b/desktop/core/src/desktop/js/apps/admin/Overview/Examples.tsx @@ -99,12 +99,12 @@ const Examples = (): JSX.Element => { } else { await installExamplesCall(url, { app_name: actualAppName }); } - const message = `${exampleApp.name} ${t('examples installed successfully')}`; + const message = `${actualAppName} ${t('examples installed successfully')}`; huePubSub.publish('hue.global.info', { message }); } catch (error) { const errorMessage = error.message - ? `${t('An error occurred while installing')} ${exampleApp.name}: ${error.message}` - : `${t('An error occurred while installing')} ${exampleApp.name}.`; + ? `${t('An error occurred while installing')} ${actualAppName}: ${error.message}` + : `${t('An error occurred while installing')} ${actualAppName}.`; huePubSub.publish('hue.global.error', { message: errorMessage }); } finally { setInstallingAppId('');