mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Merge branch 'devel' into CB-4325-remove-preloaded-property-from-useResource-hook
This commit is contained in:
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18"
|
||||
node-version: "20"
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
|
||||
+2
-1
@@ -54,7 +54,8 @@ public class WebAuthInfo implements SMSessionPrincipal {
|
||||
@NotNull WebAuthProviderDescriptor authProvider,
|
||||
@NotNull DBWUserIdentity userIdentity,
|
||||
@NotNull SMSession authSession,
|
||||
@NotNull OffsetDateTime loginTime) {
|
||||
@NotNull OffsetDateTime loginTime
|
||||
) {
|
||||
this.session = session;
|
||||
this.user = user;
|
||||
this.authProvider = authProvider;
|
||||
|
||||
+11
-19
@@ -273,28 +273,22 @@ public class WebSession extends BaseWebSession
|
||||
continue;
|
||||
}
|
||||
switch (type) {
|
||||
case DATASOURCE_CREATED: {
|
||||
case DATASOURCE_CREATED -> {
|
||||
WebConnectionInfo connectionInfo = new WebConnectionInfo(this, ds);
|
||||
this.connections.put(connectionInfo.getId(), connectionInfo);
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
break;
|
||||
}
|
||||
case DATASOURCE_UPDATED: {
|
||||
// if settings were changed we need to send event
|
||||
case DATASOURCE_UPDATED -> // if settings were changed we need to send event
|
||||
sendDataSourceUpdatedEvent |= !ds.equalSettings(oldDataSources.get(dsId));
|
||||
break;
|
||||
}
|
||||
case DATASOURCE_DELETED: {
|
||||
case DATASOURCE_DELETED -> {
|
||||
WebDataSourceUtils.disconnectDataSource(this, ds);
|
||||
if (registry instanceof DBPDataSourceRegistryCache) {
|
||||
((DBPDataSourceRegistryCache) registry).removeDataSourceFromList(ds);
|
||||
if (registry instanceof DBPDataSourceRegistryCache dsrc) {
|
||||
dsrc.removeDataSourceFromList(ds);
|
||||
}
|
||||
this.connections.remove(ds.getId());
|
||||
sendDataSourceUpdatedEvent = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -764,8 +758,8 @@ public class WebSession extends BaseWebSession
|
||||
public <T> T getAttribute(String name) {
|
||||
synchronized (attributes) {
|
||||
Object value = attributes.get(name);
|
||||
if (value instanceof PersistentAttribute) {
|
||||
value = ((PersistentAttribute) value).getValue();
|
||||
if (value instanceof PersistentAttribute persistentAttribute) {
|
||||
value = persistentAttribute.getValue();
|
||||
}
|
||||
return (T) value;
|
||||
}
|
||||
@@ -780,8 +774,8 @@ public class WebSession extends BaseWebSession
|
||||
public <T> T getAttribute(String name, Function<T, T> creator, Function<T, T> disposer) {
|
||||
synchronized (attributes) {
|
||||
Object value = attributes.get(name);
|
||||
if (value instanceof PersistentAttribute) {
|
||||
value = ((PersistentAttribute) value).getValue();
|
||||
if (value instanceof PersistentAttribute persistentAttribute) {
|
||||
value = persistentAttribute.getValue();
|
||||
}
|
||||
if (value == null) {
|
||||
value = creator.apply(null);
|
||||
@@ -965,9 +959,7 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
private <T> boolean isAuthInfoInstanceOf(WebAuthInfo authInfo, Class<T> adapter) {
|
||||
if (authInfo != null && authInfo.getAuthSession() != null) {
|
||||
if (adapter.isInstance(authInfo.getAuthSession())) {
|
||||
return true;
|
||||
}
|
||||
return adapter.isInstance(authInfo.getAuthSession());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+9
-3
@@ -22,9 +22,7 @@ import org.jkiss.dbeaver.model.DBPDataSource;
|
||||
import org.jkiss.dbeaver.model.DBUtils;
|
||||
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
|
||||
import org.jkiss.dbeaver.model.data.DBDRowIdentifier;
|
||||
import org.jkiss.dbeaver.model.struct.DBSAttributeBase;
|
||||
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
|
||||
import org.jkiss.dbeaver.model.struct.DBSTypedObject;
|
||||
import org.jkiss.dbeaver.model.struct.*;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -126,4 +124,12 @@ public class WebSQLResultsInfo {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean canRefreshResults() {
|
||||
DBSEntity entity = getDefaultRowIdentifier().getEntity();
|
||||
// FIXME: do not refresh documents for now. Can be solved by extracting document ID attributes
|
||||
// FIXME: but it will require to provide dynamic document metadata.
|
||||
return entity == null || entity.getDataSource() == null ||
|
||||
(!(entity instanceof DBSDocumentContainer) && !entity.getDataSource().getInfo().isDynamicMetadata());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -557,7 +557,12 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
} else if (serverURL == null) {
|
||||
String hostName = serverHost;
|
||||
if (CommonUtils.isEmpty(hostName)) {
|
||||
hostName = InetAddress.getLocalHost().getHostName();
|
||||
try {
|
||||
hostName = InetAddress.getLocalHost().getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
log.debug("Error resolving localhost address: " + e.getMessage());
|
||||
hostName = "localhost";
|
||||
}
|
||||
}
|
||||
serverURL = "http://" + hostName + ":" + serverPort;
|
||||
}
|
||||
@@ -604,7 +609,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
|
||||
}
|
||||
}
|
||||
parseAdditionalConfiguration(configProps);
|
||||
} catch (IOException | DBException e) {
|
||||
} catch (DBException e) {
|
||||
throw new DBException("Error parsing server configuration", e);
|
||||
}
|
||||
|
||||
|
||||
+26
-1
@@ -58,6 +58,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Web SQL processor.
|
||||
@@ -417,13 +418,22 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
DBCExecutionPurpose.UTIL,
|
||||
"Refresh row(s) after insert/update")
|
||||
) {
|
||||
boolean canRefreshResults = resultsInfo.canRefreshResults();
|
||||
for (Object[] row : newResultSetRows) {
|
||||
if (row.length == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!canRefreshResults) {
|
||||
makeWebCellRow(resultsInfo, row, dataFormat);
|
||||
continue;
|
||||
}
|
||||
List<DBDAttributeConstraint> constraints = new ArrayList<>();
|
||||
boolean hasKey = true;
|
||||
for (DBDAttributeBinding attr : resultsInfo.getAttributes()) {
|
||||
// get attributes only from row identifiers
|
||||
Set<DBDAttributeBinding> idAttributes = resultsInfo.getRowIdentifiers().stream()
|
||||
.flatMap(r -> r.getAttributes().stream())
|
||||
.collect(Collectors.toSet());
|
||||
for (DBDAttributeBinding attr : idAttributes) {
|
||||
if (attr.getRowIdentifier() == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -439,6 +449,7 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
}
|
||||
if (!hasKey) {
|
||||
// No key value for this row
|
||||
makeWebCellRow(resultsInfo, row, dataFormat);
|
||||
continue;
|
||||
}
|
||||
DBDDataFilter filter = new DBDDataFilter(constraints);
|
||||
@@ -457,6 +468,20 @@ public class WebSQLProcessor implements WebSessionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeWebCellRow(
|
||||
@NotNull WebSQLResultsInfo resultsInfo,
|
||||
@NotNull Object[] row,
|
||||
@Nullable WebDataFormat dataFormat
|
||||
) throws DBCException {
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
row[i] = WebSQLUtils.makeWebCellValue(
|
||||
webSession,
|
||||
resultsInfo.getAttributeByPosition(i),
|
||||
row[i],
|
||||
dataFormat);
|
||||
}
|
||||
}
|
||||
|
||||
public String generateResultsDataUpdateScript(
|
||||
@NotNull DBRProgressMonitor monitor,
|
||||
@NotNull WebSQLContextInfo contextInfo,
|
||||
|
||||
+5
-5
@@ -2222,12 +2222,12 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
|
||||
dbStat.setString(1, token);
|
||||
try (var dbResult = dbStat.executeQuery()) {
|
||||
if (!dbResult.next()) {
|
||||
throw new SMException("Invalid token");
|
||||
throw new SMException("Error reading permissions: input token not recognized.");
|
||||
}
|
||||
userId = dbResult.getString(1);
|
||||
var expiredDate = dbResult.getTimestamp(2);
|
||||
if (application.isMultiNode() && isTokenExpired(expiredDate)) {
|
||||
throw new SMAccessTokenExpiredException("Token expired");
|
||||
throw new SMAccessTokenExpiredException("Error reading permissions: token has expired");
|
||||
}
|
||||
sessionId = dbResult.getString(3);
|
||||
authRole = dbResult.getString(4);
|
||||
@@ -2296,7 +2296,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
|
||||
|
||||
dbStat.setString(6, sessionId);
|
||||
if (dbStat.executeUpdate() <= 0) {
|
||||
throw new DBCException("Session not exists in database");
|
||||
throw new DBCException("Session '" + sessionId + "' not found in database");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
@@ -2700,12 +2700,12 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
|
||||
dbStat.setString(1, smAccessToken);
|
||||
try (var dbResult = dbStat.executeQuery()) {
|
||||
if (!dbResult.next()) {
|
||||
throw new SMException("Invalid token");
|
||||
throw new SMException("Error reading subject role: input token not recognized.");
|
||||
}
|
||||
return dbResult.getString(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error reading lm role in database", e);
|
||||
throw new DBCException("Error reading subject role in database", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -57,6 +57,7 @@ import org.jkiss.utils.SecurityUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
@@ -458,8 +459,12 @@ public class CBDatabase {
|
||||
}
|
||||
|
||||
private void checkInstanceRecord(Connection connection) throws SQLException, IOException {
|
||||
InetAddress localHost = InetAddress.getLocalHost();
|
||||
String hostName = localHost.getHostName();
|
||||
String hostName;
|
||||
try {
|
||||
hostName = InetAddress.getLocalHost().getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
hostName = "localhost";
|
||||
}
|
||||
byte[] hardwareAddress = RuntimeUtils.getLocalMacAddress();
|
||||
String macAddress = CommonUtils.toHexString(hardwareAddress);
|
||||
|
||||
|
||||
@@ -72,12 +72,12 @@ export const Switch: SwitchType = observer(function Switch({
|
||||
}
|
||||
|
||||
return (
|
||||
<Field data-testid="field" title={rest.title}>
|
||||
<div data-testid="switch-body" className={styles.switchBody}>
|
||||
<div data-testid="switch-control" className={s(styles, { switchControl: true, disabled: disabled, checked: checkboxState.checked })}>
|
||||
<div data-testid="switch-control-track" className={styles.switchControlTrack} />
|
||||
<div data-testid="switch-control-underlay" className={styles.switchControlUnderlay}>
|
||||
<div data-testid="switch-control-thumb" className={styles.switchControlThumb} />
|
||||
<Field title={rest.title}>
|
||||
<div className={styles.switchBody}>
|
||||
<div className={s(styles, { switchControl: true, disabled: disabled, checked: checkboxState.checked })}>
|
||||
<div className={styles.switchControlTrack} />
|
||||
<div className={styles.switchControlUnderlay}>
|
||||
<div className={styles.switchControlThumb} />
|
||||
<input
|
||||
{...rest}
|
||||
type="checkbox"
|
||||
@@ -86,13 +86,12 @@ export const Switch: SwitchType = observer(function Switch({
|
||||
aria-checked={checkboxState.checked}
|
||||
checked={checkboxState.checked}
|
||||
disabled={disabled}
|
||||
data-testid="switch-input"
|
||||
className={styles.switchInput}
|
||||
onChange={checkboxState.change}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FieldLabel htmlFor={id || value || name} data-testid="field-label" className={styles.fieldLabel}>
|
||||
<FieldLabel htmlFor={id || value || name} className={styles.fieldLabel}>
|
||||
{children}
|
||||
</FieldLabel>
|
||||
</div>
|
||||
|
||||
@@ -293,13 +293,7 @@ export const Combobox: ComboboxType = observer(function Combobox({
|
||||
<MenuButton {...menu} disabled={readOnly || disabled || hideMenu} className={styles.menuButton}>
|
||||
<Icon name="arrow" viewBox="0 0 16 16" className={s(styles, { icon: true, focus })} />
|
||||
</MenuButton>
|
||||
<Menu
|
||||
{...menu}
|
||||
ref={menuRef}
|
||||
aria-label={propertyName}
|
||||
className={s(styles, { menu: true })}
|
||||
modal
|
||||
>
|
||||
<Menu {...menu} ref={menuRef} aria-label={propertyName} className={s(styles, { menu: true })} modal>
|
||||
{!filteredItems.length ? (
|
||||
<MenuItem id="placeholder" disabled {...menu} className={s(styles, { menuItem: true })}>
|
||||
{translate('combobox_no_results_placeholder')}
|
||||
@@ -322,11 +316,11 @@ export const Combobox: ComboboxType = observer(function Combobox({
|
||||
onClick={event => handleSelect(event.currentTarget.id)}
|
||||
>
|
||||
{iconSelector && (
|
||||
<div data-testid="item-icon" className={s(styles, { itemIcon: true })}>
|
||||
<div className={s(styles, { itemIcon: true })}>
|
||||
{icon && typeof icon === 'string' ? <IconOrImage icon={icon} className={s(styles, { iconOrImage: true })} /> : icon}
|
||||
</div>
|
||||
)}
|
||||
<div data-testid="item-value">{valueSelector(item)}</div>
|
||||
<div>{valueSelector(item)}</div>
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -192,11 +192,7 @@ export const InputField: InputFieldType = observer<ControlledProps | ObjectProps
|
||||
<Icon name="copy" viewBox="0 0 32 32" className={styles.icon} />
|
||||
</div>
|
||||
)}
|
||||
{icon && (
|
||||
<div data-testid="icon-container" className={s(styles, { customIconContainer: true })}>
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
{icon && <div className={s(styles, { customIconContainer: true })}>{icon}</div>}
|
||||
</div>
|
||||
{(description || passwordType) && <FieldDescription invalid={error}>{description}</FieldDescription>}
|
||||
</Field>
|
||||
|
||||
@@ -17,14 +17,12 @@ interface Props {
|
||||
export const SnackbarBody: React.FC<React.PropsWithChildren<Props>> = function SnackbarBody({ title, className, children }) {
|
||||
const styles = useS(style);
|
||||
return (
|
||||
<div data-testid="notification-body" className={s(styles, { notificationBody: true }, className)}>
|
||||
<div data-testid="body-text-block" className={styles.bodyTextBlock}>
|
||||
<h2 data-testid="text-block-title" title={title} className={styles.textBlockTitle}>
|
||||
<div className={s(styles, { notificationBody: true }, className)}>
|
||||
<div className={styles.bodyTextBlock}>
|
||||
<h2 title={title} className={styles.textBlockTitle}>
|
||||
{title}
|
||||
</h2>
|
||||
<div data-testid="message" className={styles.message}>
|
||||
{children}
|
||||
</div>
|
||||
<div className={styles.message}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,9 +15,5 @@ interface Props {
|
||||
|
||||
export const SnackbarContent: React.FC<React.PropsWithChildren<Props>> = function SnackbarContent({ children, className }) {
|
||||
const styles = useS(style);
|
||||
return (
|
||||
<div data-testid="notification-content" className={s(styles, { notificationContent: true }, className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return <div className={s(styles, { notificationContent: true }, className)}>{children}</div>;
|
||||
};
|
||||
|
||||
@@ -19,13 +19,9 @@ export const SnackbarFooter: React.FC<React.PropsWithChildren<Props>> = function
|
||||
const timeStringFromTimestamp = new Date(timestamp).toLocaleTimeString();
|
||||
|
||||
return (
|
||||
<div data-testid="notification-footer" className={s(styles, { notificationFooter: true }, className)}>
|
||||
<span data-testid="footer-time" className={styles.footerTime}>
|
||||
{timeStringFromTimestamp}
|
||||
</span>
|
||||
<div data-testid="actions" className={styles.actions}>
|
||||
{children}
|
||||
</div>
|
||||
<div className={s(styles, { notificationFooter: true }, className)}>
|
||||
<span className={styles.footerTime}>{timeStringFromTimestamp}</span>
|
||||
<div className={styles.actions}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ interface SnackbarStatusProps {
|
||||
export const SnackbarStatus: React.FC<SnackbarStatusProps> = function SnackbarStatus({ status, className }) {
|
||||
const styles = useS(style);
|
||||
return status === ENotificationType.Loading ? (
|
||||
<div data-testid="loader-container" className={s(styles, { loaderContainer: true }, className)}>
|
||||
<div className={s(styles, { loaderContainer: true }, className)}>
|
||||
<Loader className={styles.loader} hideMessage />
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -34,7 +34,7 @@ export const SnackbarWrapper: React.FC<React.PropsWithChildren<Props>> = functio
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div data-testid="notification" className={s(styles, { notification: true, mounted, closing }, className)}>
|
||||
<div className={s(styles, { notification: true, mounted, closing }, className)}>
|
||||
{children}
|
||||
{!persistent && onClose && (
|
||||
<IconButton name="cross" viewBox="0 0 16 16" className={s(styles, { iconButton: true, large: true })} onClick={onClose} />
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const testingAttributes = require('../dist/babel-plugins/TestingAttributes.js');
|
||||
|
||||
module.exports = api => {
|
||||
const devMode = !api.env('production');
|
||||
@@ -28,6 +26,6 @@ module.exports = api => {
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [[testingAttributes, {}], [require('@reshadow/babel'), {}], devMode && require.resolve('react-refresh/babel')].filter(Boolean),
|
||||
plugins: [[require('@reshadow/babel'), {}], devMode && require.resolve('react-refresh/babel')].filter(Boolean),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
"core-cli-validate-dependencies": "./bin/validate-dependencies.js",
|
||||
"typescript-resolve-references": "./bin/typescript-resolve-references.js"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "tsc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Based on https://github.com/akameco/babel-plugin-react-data-testid
|
||||
*/
|
||||
import type { NodePath, PluginObj, Visitor } from '@babel/core';
|
||||
import syntaxJsx from '@babel/plugin-syntax-jsx';
|
||||
import * as t from '@babel/types';
|
||||
|
||||
type FunctionType = t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression;
|
||||
|
||||
function addTestIdAttribute(node: t.JSXOpeningElement, name: string): void {
|
||||
if (
|
||||
t.isJSXIdentifier(node.name) &&
|
||||
node.name.name !== 'Fragment' &&
|
||||
node.name.name !== '_Fragment' &&
|
||||
!hasDataAttribute(node, DEFAULT_DATA_TESTID)
|
||||
) {
|
||||
const dataAttribute = createDataAttribute(name, DEFAULT_DATA_TESTID);
|
||||
const indexOf = node.attributes.findIndex(attribute => t.isJSXSpreadAttribute(attribute));
|
||||
if (indexOf !== -1) {
|
||||
node.attributes.splice(indexOf, 0, dataAttribute);
|
||||
} else {
|
||||
node.attributes.push(dataAttribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nameForReactComponent(path: NodePath<FunctionType>): t.Identifier | null {
|
||||
const { parentPath } = path;
|
||||
if (!t.isArrowFunctionExpression(path.node) && t.isIdentifier(path.node.id)) {
|
||||
return path.node.id;
|
||||
}
|
||||
if (t.isVariableDeclarator(parentPath.node)) {
|
||||
// @ts-expect-error expected
|
||||
return parentPath.node.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const DEFAULT_DATA_TESTID = 'data-testid';
|
||||
|
||||
function createDataAttribute(name: string, attributeName: string) {
|
||||
return t.jsxAttribute(t.jsxIdentifier(attributeName), t.stringLiteral(name));
|
||||
}
|
||||
|
||||
function hasDataAttribute(node: t.JSXOpeningElement, attributeName: string): boolean {
|
||||
return node.attributes.some(attribute => t.isJSXAttribute(attribute) && t.isJSXIdentifier(attribute.name, { name: attributeName }));
|
||||
}
|
||||
|
||||
type VisitorState = { name: string };
|
||||
|
||||
const returnStatementVisitor: Visitor<VisitorState> = {
|
||||
JSXFragment(path) {
|
||||
path.skip();
|
||||
},
|
||||
JSXElement(path, { name }) {
|
||||
const openingElement = path.get('openingElement');
|
||||
|
||||
path.skip();
|
||||
|
||||
addTestIdAttribute(openingElement.node, name);
|
||||
},
|
||||
};
|
||||
|
||||
const functionVisitor: Visitor<VisitorState> = {
|
||||
ReturnStatement(path, state) {
|
||||
const arg = path.get('argument');
|
||||
if (!arg.isIdentifier()) {
|
||||
path.traverse(returnStatementVisitor, state);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function getElementName(node: t.JSXOpeningElement['name']): string {
|
||||
if (t.isJSXNamespacedName(node)) {
|
||||
return [getElementName(node.namespace), getElementName(node.name)].join(':');
|
||||
}
|
||||
|
||||
if (t.isJSXIdentifier(node)) {
|
||||
return node.name;
|
||||
}
|
||||
|
||||
return [getElementName(node.object), getElementName(node.property)].join('.');
|
||||
}
|
||||
|
||||
export default function plugin(): PluginObj {
|
||||
return {
|
||||
name: 'cloudbeaver-data-testid',
|
||||
inherits: syntaxJsx,
|
||||
visitor: {
|
||||
'FunctionExpression|ArrowFunctionExpression|FunctionDeclaration': (p: NodePath<FunctionType>) => {
|
||||
const identifier = nameForReactComponent(p);
|
||||
if (!identifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p.isArrowFunctionExpression()) {
|
||||
p.traverse(returnStatementVisitor, { name: identifier.name });
|
||||
} else {
|
||||
p.traverse(functionVisitor, { name: identifier.name });
|
||||
}
|
||||
},
|
||||
CallExpression(p) {
|
||||
p.traverse({
|
||||
JSXOpeningElement({ node }) {
|
||||
if (t.isJSXOpeningFragment(node)) {
|
||||
return;
|
||||
}
|
||||
addTestIdAttribute(node, getElementName(node.name));
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
} as PluginObj;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src",
|
||||
"tests",
|
||||
"bin",
|
||||
"configs"
|
||||
],
|
||||
"exclude": ["**/node_modules", "**/lib"]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"module": "CommonJS",
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"emitDeclarationOnly": false,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules/**/*", "dist/**/*", "tests/**/*"]
|
||||
}
|
||||
@@ -107,6 +107,7 @@ export default [
|
||||
['ui_upload_file_fail', 'Failed to upload file'],
|
||||
['ui_filter', 'Filter'],
|
||||
['ui_disable', 'Disable'],
|
||||
['ui_test', 'Test'],
|
||||
|
||||
['root_permission_denied', "You don't have permissions"],
|
||||
['root_permission_no_permission', "You don't have permission for this action"],
|
||||
|
||||
@@ -91,6 +91,7 @@ export default [
|
||||
['ui_upload_file_fail', 'Failed to upload file'],
|
||||
['ui_filter', 'Filter'],
|
||||
['ui_disable', 'Disable'],
|
||||
['ui_test', 'Test'],
|
||||
|
||||
['root_permission_denied', 'Non hai i permessi'],
|
||||
['app_root_session_expire_warning_title', 'La sessione sta per scadere'],
|
||||
|
||||
@@ -103,6 +103,7 @@ export default [
|
||||
['ui_upload_file_fail', 'Не удалось загрузить файл'],
|
||||
['ui_filter', 'Фильтр'],
|
||||
['ui_disable', 'Отключить'],
|
||||
['ui_test', 'Проверить'],
|
||||
|
||||
['root_permission_denied', 'Отказано в доступе'],
|
||||
['root_permission_no_permission', 'У вас нет разрешения на это действие'],
|
||||
|
||||
@@ -104,6 +104,7 @@ export default [
|
||||
['ui_upload_file_fail', 'Failed to upload file'],
|
||||
['ui_filter', 'Filter'],
|
||||
['ui_disable', 'Disable'],
|
||||
['ui_test', 'Test'],
|
||||
|
||||
['root_permission_denied', '您没有权限'],
|
||||
['root_permission_no_permission', '您没有权限执行此操作'],
|
||||
|
||||
@@ -6,19 +6,11 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Button, Container, GroupItem, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { Button, Container, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { useAuthenticationAction } from './useAuthenticationAction';
|
||||
|
||||
const styles = css`
|
||||
Container {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export type Props = {
|
||||
providerId: string;
|
||||
className?: string;
|
||||
@@ -29,21 +21,22 @@ export type Props = {
|
||||
|
||||
export const AuthenticationProvider = observer<Props>(function AuthenticationProvider(props) {
|
||||
const translate = useTranslate();
|
||||
const style = useStyles(styles);
|
||||
const action = useAuthenticationAction(props);
|
||||
|
||||
if (action.authorized) {
|
||||
return (props.children?.() as null) || null;
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<Container className={props.className} gap vertical>
|
||||
<GroupItem keepSize>{translate('authentication_request_token')}</GroupItem>
|
||||
<GroupItem keepSize>
|
||||
<Button type="button" mod={['unelevated']} loading={action.authenticating} onClick={action.auth}>
|
||||
{translate('authentication_authenticate')}
|
||||
</Button>
|
||||
</GroupItem>
|
||||
</Container>,
|
||||
return (
|
||||
<Container className={props.className} gap vertical center>
|
||||
<Container keepSize center vertical gap dense>
|
||||
<Container keepSize>{translate('authentication_request_token')}</Container>
|
||||
<Container keepSize>
|
||||
<Button type="button" mod={['unelevated']} loading={action.authenticating} onClick={action.auth}>
|
||||
{translate('authentication_authenticate')}
|
||||
</Button>
|
||||
</Container>
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import { runInAction } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource, UserInfoResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
ColoredContainer,
|
||||
ExceptionMessage,
|
||||
@@ -20,7 +21,7 @@ import {
|
||||
useStyles,
|
||||
useTranslate,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { createConnectionParam } from '@cloudbeaver/core-connections';
|
||||
import { createConnectionParam, DatabaseAuthModelsResource, DBDriverResource } from '@cloudbeaver/core-connections';
|
||||
import { TabContainerPanelComponent, useTab, useTabState } from '@cloudbeaver/core-ui';
|
||||
|
||||
import type { IConnectionFormProps } from '../IConnectionFormProps';
|
||||
@@ -38,12 +39,25 @@ const style = css`
|
||||
}
|
||||
`;
|
||||
|
||||
export const OriginInfo: TabContainerPanelComponent<IConnectionFormProps> = observer(function OriginInfo({ tabId, state: { info, resource } }) {
|
||||
export const OriginInfo: TabContainerPanelComponent<IConnectionFormProps> = observer(function OriginInfo({
|
||||
tabId,
|
||||
state: { info, resource, config },
|
||||
}) {
|
||||
const tab = useTab(tabId);
|
||||
const translate = useTranslate();
|
||||
// const userInfoService = useService(UserInfoResource);
|
||||
const userInfoLoader = useResource(OriginInfo, UserInfoResource, undefined);
|
||||
const state = useTabState<Record<string, any>>();
|
||||
const styles = useStyles(style);
|
||||
const driverLoader = useResource(OriginInfo, DBDriverResource, config.driverId ?? null);
|
||||
const authModeLoader = useResource(
|
||||
OriginInfo,
|
||||
DatabaseAuthModelsResource,
|
||||
config.authModelId ?? info?.authModel ?? driverLoader.data?.defaultAuthModel ?? null,
|
||||
);
|
||||
|
||||
const providerId = authModeLoader.data?.requiredAuth ?? info?.requiredAuth ?? AUTH_PROVIDER_LOCAL_ID;
|
||||
const isAuthenticated = userInfoLoader.resource.hasToken(providerId);
|
||||
const providerLoader = useResource(OriginInfo, AuthProvidersResource, providerId);
|
||||
|
||||
const connection = useResource(
|
||||
OriginInfo,
|
||||
@@ -53,7 +67,7 @@ export const OriginInfo: TabContainerPanelComponent<IConnectionFormProps> = obse
|
||||
includes: ['includeOrigin', 'customIncludeOriginDetails'] as const,
|
||||
},
|
||||
{
|
||||
// isActive: () => !info?.origin || userInfoService.hasOrigin(info.origin),
|
||||
active: isAuthenticated,
|
||||
onData: connection => {
|
||||
runInAction(() => {
|
||||
if (!connection.origin.details) {
|
||||
@@ -89,15 +103,17 @@ export const OriginInfo: TabContainerPanelComponent<IConnectionFormProps> = obse
|
||||
);
|
||||
}
|
||||
|
||||
// const authorized = !info?.origin || userInfoService.hasOrigin(info.origin);
|
||||
|
||||
// if (!authorized && info?.origin) {
|
||||
// return styled(styles)(
|
||||
// <ColoredContainer parent vertical>
|
||||
// <AuthenticationProvider origin={info.origin} onAuthenticate={connection.reload} />
|
||||
// </ColoredContainer>
|
||||
// );
|
||||
// }
|
||||
if (!isAuthenticated) {
|
||||
return styled(styles)(
|
||||
<ColoredContainer parent>
|
||||
<TextPlaceholder>
|
||||
{translate('connections_public_connection_cloud_auth_required', undefined, {
|
||||
providerLabel: providerLoader.data?.label,
|
||||
})}
|
||||
</TextPlaceholder>
|
||||
</ColoredContainer>,
|
||||
);
|
||||
}
|
||||
|
||||
if (!connection.data?.origin.details || connection.data.origin.details.length === 0) {
|
||||
return styled(styles)(
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ const NavTreeLimitMessage: NavigationNodeRendererComponent = observer(function N
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={s(styles, { loadMoreBox: true })} data-testid="load-more-box">
|
||||
<div className={s(styles, { loadMoreBox: true })}>
|
||||
<Link title={translate('app_navigationTree_limited', undefined, { limit: limit })} onClick={loadMore}>
|
||||
{translate('ui_load_more')}
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user