mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-2660 add set resource property mutation to resource manager updated (#1214)
* CB-2660: Add set property mutation to resource manager * CB-2660 fix: save resource property * CB-2660 Modify graphql schema to allow properties in RMResource to be null * CB-2660 fix: resource properties keys * CB-2660 fix: codestyle * CB-2660 Read resource properties in navigator model * CB-2660 Respect nameMask parameter * CB-2660 feat: bind execution context information * CB-2660 fix: don't save properties for readonly scripts * CB-2660 fix: don't link readonly scripts with datasource * CB-2660 fix: script linking with datasource * CB-2660 fix: sql editor context switch flickering * CB-2660 fix(plugin-resource-manager): resource finding Co-authored-by: Wroud <wroud@yandex.ru> Co-authored-by: serge-rider <serge@jkiss.org> Co-authored-by: Alexey <wrouds@gmail.com>
This commit is contained in:
co-authored by
Wroud
serge-rider
Alexey
parent
74b960ed57
commit
39987507a5
+1
-1
@@ -91,7 +91,7 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
if (children == null) {
|
||||
List<DBNResourceManagerResource> rfList = new ArrayList<>();
|
||||
for (RMResource resource : getResourceController().listResources(
|
||||
project.getId(), null, null, false, false, false)) {
|
||||
project.getId(), null, null, true, false, false)) {
|
||||
|
||||
rfList.add(new DBNResourceManagerResource(this, resource));
|
||||
}
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
if (children == null) {
|
||||
List<DBNResourceManagerResource> rfList = new ArrayList<>();
|
||||
for (RMResource resource : getResourceController().listResources(
|
||||
getResourceProject().getId(), getResourceFolder(), null, false, false, false)) {
|
||||
getResourceProject().getId(), getResourceFolder(), null, true, false, false)) {
|
||||
rfList.add(new DBNResourceManagerResource(this, resource));
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -347,7 +347,7 @@ public class LocalResourceController implements RMController {
|
||||
if (!folderPath.startsWith(projectPath)) {
|
||||
throw new DBException("Invalid folder path");
|
||||
}
|
||||
return readChildResources(projectId, folderPath, readProperties, readHistory, recursive);
|
||||
return readChildResources(projectId, folderPath, nameMask, readProperties, readHistory, recursive);
|
||||
} catch (NoSuchFileException e) {
|
||||
throw new DBException("Invalid resource folder " + folder);
|
||||
} catch (IOException e) {
|
||||
@@ -359,14 +359,18 @@ public class LocalResourceController implements RMController {
|
||||
private RMResource[] readChildResources(
|
||||
@NotNull String projectId,
|
||||
@NotNull Path folderPath,
|
||||
@Nullable String nameMask,
|
||||
boolean readProperties,
|
||||
boolean readHistory,
|
||||
boolean recursive
|
||||
) throws IOException {
|
||||
try (Stream<Path> files = Files.list(folderPath)) {
|
||||
return files.filter(path -> !path.getFileName().toString().startsWith(".")) // skip hidden files
|
||||
return files.filter(path -> {
|
||||
String fileName = path.getFileName().toString();
|
||||
return (nameMask == null || nameMask.equals(fileName)) && !fileName.startsWith(".");
|
||||
}) // skip hidden files
|
||||
.sorted(Comparator.comparing(path -> path.getFileName().toString(), String.CASE_INSENSITIVE_ORDER))
|
||||
.map((Path path) -> makeResourceFromPath(projectId, path, readProperties, readHistory, recursive))
|
||||
.map((Path path) -> makeResourceFromPath(projectId, path, nameMask, readProperties, readHistory, recursive))
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(RMResource[]::new);
|
||||
}
|
||||
@@ -631,7 +635,7 @@ public class LocalResourceController implements RMController {
|
||||
|
||||
for (var resourceName : relativeResourcePath) {
|
||||
resourcePath = resourcePath.resolve(resourceName);
|
||||
result.add(makeResourceFromPath(projectId, resourcePath, false, false, recursive));
|
||||
result.add(makeResourceFromPath(projectId, resourcePath, null, false, false, recursive));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -640,6 +644,7 @@ public class LocalResourceController implements RMController {
|
||||
private RMResource makeResourceFromPath(
|
||||
@NotNull String projectId,
|
||||
@NotNull Path path,
|
||||
@Nullable String nameMask,
|
||||
boolean readProperties,
|
||||
boolean readHistory,
|
||||
boolean recursive
|
||||
@@ -687,7 +692,7 @@ public class LocalResourceController implements RMController {
|
||||
|
||||
if (recursive && resource.isFolder()) {
|
||||
try {
|
||||
resource.setChildren(readChildResources(projectId, path, readProperties, readHistory, true));
|
||||
resource.setChildren(readChildResources(projectId, path, nameMask, readProperties, readHistory, true));
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ type RMResource {
|
||||
name: String!
|
||||
folder: Boolean!
|
||||
length: Int!
|
||||
# Properties map
|
||||
properties: Object
|
||||
}
|
||||
|
||||
input RMSubjectProjectPermissions {
|
||||
@@ -90,4 +92,5 @@ extend type Mutation {
|
||||
|
||||
rmSetSubjectProjectPermissions(subjectId: String!, permissions: [RMProjectPermissions!]!): Boolean!
|
||||
|
||||
rmSetResourceProperty(projectId: String!, resourcePath: String!, name: ID!, value: String): Boolean!
|
||||
}
|
||||
+19
@@ -58,6 +58,25 @@ public interface DBWServiceRM extends DBWService {
|
||||
boolean readProperties,
|
||||
boolean readHistory) throws DBException;
|
||||
|
||||
/**
|
||||
* Sets resource property.
|
||||
*
|
||||
* @param webSession the web session
|
||||
* @param projectId the project id
|
||||
* @param resourcePath the resource path
|
||||
* @param propertyName the property name
|
||||
* @param propertyValue the property value
|
||||
* @return the resource property
|
||||
* @throws DBException the db exception
|
||||
*/
|
||||
@WebProjectAction(requireProjectPermissions = RMConstants.PERMISSION_PROJECT_RESOURCE_EDIT)
|
||||
String setResourceProperty(
|
||||
@NotNull WebSession webSession,
|
||||
@NotNull @WebObjectId String projectId,
|
||||
@NotNull String resourcePath,
|
||||
@NotNull String propertyName,
|
||||
@Nullable Object propertyValue) throws DBException;
|
||||
|
||||
@WebProjectAction(
|
||||
requireProjectPermissions = RMConstants.PERMISSION_PROJECT_RESOURCE_VIEW
|
||||
)
|
||||
|
||||
+7
-2
@@ -81,7 +81,6 @@ public class WebServiceBindingRM extends WebServiceBindingBase<DBWServiceRM> {
|
||||
env.getArgument("resourcePath"),
|
||||
env.getArgument("data"),
|
||||
env.getArgument("forceOverwrite")))
|
||||
|
||||
.dataFetcher("rmCreateProject", env -> getService(env).createProject(
|
||||
getWebSession(env),
|
||||
env.getArgument("projectName"),
|
||||
@@ -96,12 +95,18 @@ public class WebServiceBindingRM extends WebServiceBindingBase<DBWServiceRM> {
|
||||
env.getArgument("projectId"),
|
||||
new RMSubjectProjectPermissions(env.getArgument("permissions"))
|
||||
))
|
||||
.dataFetcher("rmSetResourceProperty", env -> getService(env).setResourceProperty(
|
||||
getWebSession(env),
|
||||
env.getArgument("projectId"),
|
||||
env.getArgument("resourcePath"),
|
||||
env.getArgument("name"),
|
||||
env.getArgument("value")
|
||||
))
|
||||
.dataFetcher("rmSetSubjectProjectPermissions", env -> getService(env).setSubjectProjectPermissions(
|
||||
getWebSession(env),
|
||||
env.getArgument("subjectId"),
|
||||
new RMProjectPermissions(env.getArgument("permissions"))
|
||||
))
|
||||
;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+56
-7
@@ -43,7 +43,7 @@ import java.util.Set;
|
||||
public class WebServiceRM implements DBWServiceRM {
|
||||
|
||||
@Override
|
||||
public RMProject[] listProjects(WebSession webSession) throws DBWebException {
|
||||
public RMProject[] listProjects(@NotNull WebSession webSession) throws DBWebException {
|
||||
try {
|
||||
return getResourceController(webSession).listAccessibleProjects();
|
||||
} catch (DBException e) {
|
||||
@@ -71,7 +71,13 @@ public class WebServiceRM implements DBWServiceRM {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public RMResource[] listResources(WebSession webSession, @NotNull String projectId, @Nullable String folder, @Nullable String nameMask, boolean readProperties, boolean readHistory) throws DBException {
|
||||
public RMResource[] listResources(@NotNull WebSession webSession,
|
||||
@NotNull String projectId,
|
||||
@Nullable String folder,
|
||||
@Nullable String nameMask,
|
||||
boolean readProperties,
|
||||
boolean readHistory
|
||||
) throws DBException {
|
||||
checkIsRmEnabled(webSession);
|
||||
try {
|
||||
return getResourceController(webSession).listResources(projectId, folder, nameMask, readProperties, readHistory, false);
|
||||
@@ -80,8 +86,40 @@ public class WebServiceRM implements DBWServiceRM {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets resource property.
|
||||
*
|
||||
* @param webSession the web session
|
||||
* @param projectId the project id
|
||||
* @param resourcePath the resource path
|
||||
* @param propertyName the property name
|
||||
* @param propertyValue the property value
|
||||
* @return the resource property
|
||||
* @throws DBException the db exception
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public String readResourceAsString(@NotNull WebSession webSession, @NotNull String projectId, @NotNull String resourcePath) throws DBException {
|
||||
public String setResourceProperty(@NotNull WebSession webSession,
|
||||
@NotNull String projectId,
|
||||
@NotNull String resourcePath,
|
||||
@NotNull String propertyName,
|
||||
@Nullable Object propertyValue
|
||||
) throws DBException {
|
||||
checkIsRmEnabled(webSession);
|
||||
try {
|
||||
getResourceController(webSession).setResourceProperty(projectId, resourcePath, propertyName, propertyValue);
|
||||
return Boolean.TRUE.toString();
|
||||
} catch (DBException e) {
|
||||
String message = String.format("Error setting property [%s] for the the resource: [%s]", resourcePath, propertyName);
|
||||
throw new DBWebException(message, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readResourceAsString(@NotNull WebSession webSession,
|
||||
@NotNull String projectId,
|
||||
@NotNull String resourcePath
|
||||
) throws DBException {
|
||||
checkIsRmEnabled(webSession);
|
||||
try {
|
||||
byte[] data = getResourceController(webSession).getResourceContents(projectId, resourcePath);
|
||||
@@ -92,7 +130,11 @@ public class WebServiceRM implements DBWServiceRM {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createResource(@NotNull WebSession webSession, @NotNull String projectId, @NotNull String resourcePath, boolean isFolder) throws DBException {
|
||||
public String createResource(@NotNull WebSession webSession,
|
||||
@NotNull String projectId,
|
||||
@NotNull String resourcePath,
|
||||
boolean isFolder
|
||||
) throws DBException {
|
||||
checkIsRmEnabled(webSession);
|
||||
try {
|
||||
return getResourceController(webSession).createResource(projectId, resourcePath, isFolder);
|
||||
@@ -102,7 +144,10 @@ public class WebServiceRM implements DBWServiceRM {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteResource(@NotNull WebSession webSession, @NotNull String projectId, @NotNull String resourcePath) throws DBException {
|
||||
public boolean deleteResource(@NotNull WebSession webSession,
|
||||
@NotNull String projectId,
|
||||
@NotNull String resourcePath
|
||||
) throws DBException {
|
||||
checkIsRmEnabled(webSession);
|
||||
try {
|
||||
getResourceController(webSession).deleteResource(projectId, resourcePath, false);
|
||||
@@ -215,7 +260,9 @@ public class WebServiceRM implements DBWServiceRM {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SMObjectPermissionsGrant> listProjectGrantedPermissions(@NotNull WebSession webSession, @NotNull String projectId) throws DBWebException {
|
||||
public List<SMObjectPermissionsGrant> listProjectGrantedPermissions(@NotNull WebSession webSession,
|
||||
@NotNull String projectId
|
||||
) throws DBWebException {
|
||||
SMController sm = webSession.getSecurityController();
|
||||
try {
|
||||
return sm.getObjectPermissionGrants(projectId, SMObjects.PROJECT);
|
||||
@@ -225,7 +272,9 @@ public class WebServiceRM implements DBWServiceRM {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SMObjectPermissionsGrant> listSubjectProjectsPermissionGrants(@NotNull WebSession webSession, @NotNull String subjectId) throws DBWebException {
|
||||
public List<SMObjectPermissionsGrant> listSubjectProjectsPermissionGrants(@NotNull WebSession webSession,
|
||||
@NotNull String subjectId
|
||||
) throws DBWebException {
|
||||
try {
|
||||
SMAdminController sm = webSession.getAdminSecurityController();
|
||||
return sm.getSubjectObjectPermissionGrants(subjectId, SMObjects.PROJECT);
|
||||
|
||||
+11
-1
@@ -33,6 +33,8 @@ export const ConnectionExecutionContextProjectKey = (projectId: string) => resou
|
||||
projectId
|
||||
);
|
||||
|
||||
export const NOT_INITIALIZED_CONTEXT_ID = '-1';
|
||||
|
||||
@injectable()
|
||||
export class ConnectionExecutionContextResource extends CachedMapResource<string, IConnectionExecutionContextInfo> {
|
||||
constructor(
|
||||
@@ -157,7 +159,7 @@ export class ConnectionExecutionContextResource extends CachedMapResource<string
|
||||
originalKey: ResourceKey<string>
|
||||
): Promise<Map<string, IConnectionExecutionContextInfo>> {
|
||||
let projectId: string | undefined;
|
||||
const all = this.includes(originalKey, CachedMapAllKey);
|
||||
const all = this.isAliasEqual(originalKey, CachedMapAllKey);
|
||||
const isProjectFolders = isConnectionExecutionContextProjectKey(originalKey);
|
||||
originalKey = this.transformParam(originalKey);
|
||||
|
||||
@@ -260,3 +262,11 @@ function isConnectionExecutionContextProjectKey(
|
||||
): param is ResourceKeyList<string> {
|
||||
return isResourceKeyList(param) && param.list.includes(connectionExecutionContextProjectKeySymbol);
|
||||
}
|
||||
|
||||
export function getRealExecutionContextId(id: string | undefined | null): string | null {
|
||||
if (id === NOT_INITIALIZED_CONTEXT_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return id ?? null;
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ export class ConnectionInfoResource extends CachedMapResource<IConnectionInfoPar
|
||||
includes: CachedResourceIncludeArgs<Connection, ConnectionInfoIncludes>
|
||||
): Promise<Map<IConnectionInfoParams, Connection>> {
|
||||
let projectId: string | undefined;
|
||||
const all = this.includes(originalKey, CachedMapAllKey);
|
||||
const all = this.isAliasEqual(originalKey, CachedMapAllKey);
|
||||
const isProjectKey = isConnectionInfoProjectKey(originalKey);
|
||||
const key = this.transformParam(originalKey);
|
||||
|
||||
@@ -418,6 +418,10 @@ export class ConnectionInfoResource extends CachedMapResource<IConnectionInfoPar
|
||||
...this.getIncludesMap(key, includes),
|
||||
});
|
||||
|
||||
if (connectionId && !connections.some(connection => connection.id === connectionId)) {
|
||||
throw new Error(`Connection is not found (${connectionId})`);
|
||||
}
|
||||
|
||||
runInAction(() => {
|
||||
if (all) {
|
||||
this.resetIncludes();
|
||||
|
||||
@@ -27,7 +27,7 @@ export interface IScheduleOptions {
|
||||
|
||||
const queueLimit = 100;
|
||||
|
||||
export class TaskScheduler<TIdentifier> {
|
||||
export class TaskScheduler<TIdentifier = void> {
|
||||
get activeList(): TIdentifier[] {
|
||||
return this.queue.map(task => task.id);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface INavNodeMoveData {
|
||||
}
|
||||
|
||||
export interface INavNodeRenameData {
|
||||
projectId?: string;
|
||||
nodeId: string;
|
||||
newNodeId: string;
|
||||
}
|
||||
@@ -264,6 +265,7 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
|
||||
|
||||
this.markOutdated(node.parentId);
|
||||
await this.onNodeRename.execute({
|
||||
projectId: node.projectId,
|
||||
nodeId: node.id,
|
||||
newNodeId,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createExtension, isExtension, IExtension } from '@cloudbeaver/core-extensions';
|
||||
|
||||
const projectProviderSymbol = Symbol('@extension/ProjectProvider');
|
||||
|
||||
export type IProjectProvider<T = never> = (context: T) => string | undefined;
|
||||
|
||||
export function projectProvider<T>(provider: IProjectProvider<T>) {
|
||||
return createExtension<T>(provider, projectProviderSymbol);
|
||||
}
|
||||
|
||||
export function isProjectProvider<T>(obj: IExtension<T>): obj is IProjectProvider<T> & IExtension<T> {
|
||||
return isExtension(obj, projectProviderSymbol);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createExtension, isExtension, IExtension } from '@cloudbeaver/core-extensions';
|
||||
|
||||
|
||||
const projectSetterSymbol = Symbol('@extension/ProjectSetter');
|
||||
|
||||
export type IProjectSetter<T = never> = (
|
||||
projectId: string | null,
|
||||
context: T
|
||||
) => Promise<boolean> | boolean;
|
||||
|
||||
export function projectSetter<T>(setter: IProjectSetter<T>) {
|
||||
return createExtension<T>(setter, projectSetterSymbol);
|
||||
}
|
||||
|
||||
export function isProjectSetter<T>(obj: IExtension<T>): obj is IProjectSetter<T> & IExtension<T> {
|
||||
return isExtension(obj, projectSetterSymbol);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createExtension, isExtension, IExtension } from '@cloudbeaver/core-extensions';
|
||||
|
||||
|
||||
const projectSetterStateSymbol = Symbol('@extension/ProjectSetterState');
|
||||
|
||||
export type IProjectSetterState<T = never> = (context: T) => Promise<boolean> | boolean;
|
||||
|
||||
export function projectSetterState<T>(setter: IProjectSetterState<T>) {
|
||||
return createExtension<T>(setter, projectSetterStateSymbol);
|
||||
}
|
||||
|
||||
export function isProjectSetterState<T>(obj: IExtension<T>): obj is IProjectSetterState<T> & IExtension<T> {
|
||||
return isExtension(obj, projectSetterStateSymbol);
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
export * from './extensions/IProjectProvider';
|
||||
export * from './extensions/IProjectSetter';
|
||||
export * from './extensions/IProjectSetterState';
|
||||
export * from './activeProjectsContext';
|
||||
export * from './ProjectInfoResource';
|
||||
export * from './ProjectsService';
|
||||
|
||||
@@ -493,14 +493,14 @@ export abstract class CachedResource<
|
||||
param: TParam,
|
||||
context: TContext,
|
||||
update: (param: TParam, context: TContext) => Promise<T>,
|
||||
exitCheck: () => boolean
|
||||
exitCheck: (param: TParam, context: TContext) => boolean
|
||||
): Promise<T | undefined>;
|
||||
|
||||
protected async performUpdate<T>(
|
||||
param: TParam,
|
||||
context: TContext,
|
||||
update: (param: TParam, context: TContext) => Promise<T>,
|
||||
exitCheck?: () => boolean
|
||||
exitCheck?: (param: TParam, context: TContext) => boolean
|
||||
): Promise<T | undefined> {
|
||||
const contexts = await this.beforeLoad.execute(param);
|
||||
|
||||
@@ -510,7 +510,7 @@ export abstract class CachedResource<
|
||||
|
||||
await this.scheduler.waitRelease(param);
|
||||
|
||||
if (exitCheck?.()) {
|
||||
if (exitCheck?.(param, context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -519,7 +519,7 @@ export abstract class CachedResource<
|
||||
param,
|
||||
async () => {
|
||||
// repeated because previous task maybe has been load requested data
|
||||
if (exitCheck?.()) {
|
||||
if (exitCheck?.(param, context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ fragment NavNodeInfo on NavigatorNodeInfo {
|
||||
inline
|
||||
navigable
|
||||
features
|
||||
projectId
|
||||
object {
|
||||
features
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
query getResourceList($projectId: String!, $folder: String, $nameMask: String, $readProperties: Boolean, $readHistory: Boolean) {
|
||||
query getResourceList(
|
||||
$projectId: String!
|
||||
$folder: String
|
||||
$nameMask: String
|
||||
$readProperties: Boolean
|
||||
$readHistory: Boolean
|
||||
) {
|
||||
resources: rmListResources(
|
||||
projectId: $projectId,
|
||||
folder: $folder,
|
||||
nameMask: $nameMask,
|
||||
readProperties: $readProperties,
|
||||
projectId: $projectId
|
||||
folder: $folder
|
||||
nameMask: $nameMask
|
||||
readProperties: $readProperties
|
||||
readHistory: $readHistory
|
||||
) {
|
||||
name,
|
||||
folder,
|
||||
name
|
||||
folder
|
||||
length
|
||||
properties
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
mutation setResourceProperty(
|
||||
$projectId: String!
|
||||
$resourcePath: String!
|
||||
$name: ID!
|
||||
$value: String
|
||||
) {
|
||||
properties: rmSetResourceProperty(
|
||||
projectId: $projectId
|
||||
resourcePath: $resourcePath
|
||||
name: $name
|
||||
value: $value
|
||||
)
|
||||
}
|
||||
@@ -413,6 +413,7 @@ export interface Mutation {
|
||||
rmDeleteResource?: Maybe<Scalars['Boolean']>;
|
||||
rmMoveResource: Scalars['String'];
|
||||
rmSetProjectPermissions: Scalars['Boolean'];
|
||||
rmSetResourceProperty: Scalars['Boolean'];
|
||||
rmSetSubjectProjectPermissions: Scalars['Boolean'];
|
||||
rmWriteResourceStringContent: Scalars['String'];
|
||||
setConnectionNavigatorSettings: ConnectionInfo;
|
||||
@@ -612,6 +613,14 @@ export interface MutationRmSetProjectPermissionsArgs {
|
||||
}
|
||||
|
||||
|
||||
export interface MutationRmSetResourcePropertyArgs {
|
||||
name: Scalars['ID'];
|
||||
projectId: Scalars['String'];
|
||||
resourcePath: Scalars['String'];
|
||||
value?: InputMaybe<Scalars['String']>;
|
||||
}
|
||||
|
||||
|
||||
export interface MutationRmSetSubjectProjectPermissionsArgs {
|
||||
permissions: Array<RmProjectPermissions>;
|
||||
subjectId: Scalars['String'];
|
||||
@@ -1379,6 +1388,7 @@ export interface RmResource {
|
||||
folder: Scalars['Boolean'];
|
||||
length: Scalars['Int'];
|
||||
name: Scalars['String'];
|
||||
properties?: Maybe<Scalars['Object']>;
|
||||
}
|
||||
|
||||
export interface RmSubjectProjectPermissions {
|
||||
@@ -2256,7 +2266,7 @@ export type NavGetStructContainersQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type NavGetStructContainersQuery = { navGetStructContainers: { supportsCatalogChange: boolean, supportsSchemaChange: boolean, catalogList: Array<{ catalog: { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }, schemaList: Array<{ id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }> }>, schemaList: Array<{ id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }> } };
|
||||
export type NavGetStructContainersQuery = { navGetStructContainers: { supportsCatalogChange: boolean, supportsSchemaChange: boolean, catalogList: Array<{ catalog: { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }, schemaList: Array<{ id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }> }>, schemaList: Array<{ id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }> } };
|
||||
|
||||
export type AdminObjectGrantInfoFragment = { subjectId: string, subjectType: AdminSubjectType, objectPermissions: { objectId: string, permissions: Array<string> } };
|
||||
|
||||
@@ -2288,7 +2298,7 @@ export type ExecutionContextInfoFragment = { id: string, projectId: string, conn
|
||||
|
||||
export type NavNodeDbObjectInfoFragment = { id: string, object?: { type?: string, features?: Array<string>, properties?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> } };
|
||||
|
||||
export type NavNodeInfoFragment = { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> };
|
||||
export type NavNodeInfoFragment = { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> };
|
||||
|
||||
export type NavNodePropertiesFragment = { id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number };
|
||||
|
||||
@@ -2449,7 +2459,7 @@ export type GetNodeParentsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetNodeParentsQuery = { node: { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }, parents: Array<{ id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }> };
|
||||
export type GetNodeParentsQuery = { node: { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }, parents: Array<{ id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }> };
|
||||
|
||||
export type NavDeleteNodesMutationVariables = Exact<{
|
||||
nodePaths: Array<Scalars['ID']> | Scalars['ID'];
|
||||
@@ -2474,7 +2484,7 @@ export type NavNodeChildrenQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type NavNodeChildrenQuery = { navNodeChildren: Array<{ id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }>, navNodeInfo: { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> } };
|
||||
export type NavNodeChildrenQuery = { navNodeChildren: Array<{ id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> }>, navNodeInfo: { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> } };
|
||||
|
||||
export type NavNodeInfoQueryVariables = Exact<{
|
||||
nodePath: Scalars['ID'];
|
||||
@@ -2482,7 +2492,7 @@ export type NavNodeInfoQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type NavNodeInfoQuery = { navNodeInfo: { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> } };
|
||||
export type NavNodeInfoQuery = { navNodeInfo: { id: string, name?: string, hasChildren?: boolean, nodeType?: string, icon?: string, folder?: boolean, inline?: boolean, navigable?: boolean, features?: Array<string>, projectId?: string, object?: { features?: Array<string> }, nodeDetails?: Array<{ id?: string, category?: string, dataType?: string, description?: string, displayName?: string, length: ObjectPropertyLength, features: Array<string>, value?: any, order: number }> } };
|
||||
|
||||
export type NavRefreshNodeQueryVariables = Exact<{
|
||||
nodePath: Scalars['ID'];
|
||||
@@ -2566,7 +2576,7 @@ export type GetResourceListQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetResourceListQuery = { resources: Array<{ name: string, folder: boolean, length: number }> };
|
||||
export type GetResourceListQuery = { resources: Array<{ name: string, folder: boolean, length: number, properties?: any }> };
|
||||
|
||||
export type GetResourceProjectListQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -2610,6 +2620,16 @@ export type SetProjectPermissionsMutationVariables = Exact<{
|
||||
|
||||
export type SetProjectPermissionsMutation = { rmSetProjectPermissions: boolean };
|
||||
|
||||
export type SetResourcePropertyMutationVariables = Exact<{
|
||||
projectId: Scalars['String'];
|
||||
resourcePath: Scalars['String'];
|
||||
name: Scalars['ID'];
|
||||
value?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type SetResourcePropertyMutation = { properties: boolean };
|
||||
|
||||
export type SetSubjectProjectsPermissionsMutationVariables = Exact<{
|
||||
subjectId: Scalars['String'];
|
||||
permissions: Array<RmProjectPermissions> | RmProjectPermissions;
|
||||
@@ -3074,6 +3094,7 @@ export const NavNodeInfoFragmentDoc = `
|
||||
inline
|
||||
navigable
|
||||
features
|
||||
projectId
|
||||
object {
|
||||
features
|
||||
}
|
||||
@@ -4146,6 +4167,7 @@ export const GetResourceListDocument = `
|
||||
name
|
||||
folder
|
||||
length
|
||||
properties
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -4196,6 +4218,16 @@ export const SetProjectPermissionsDocument = `
|
||||
rmSetProjectPermissions(projectId: $projectId, permissions: $permissions)
|
||||
}
|
||||
`;
|
||||
export const SetResourcePropertyDocument = `
|
||||
mutation setResourceProperty($projectId: String!, $resourcePath: String!, $name: ID!, $value: String) {
|
||||
properties: rmSetResourceProperty(
|
||||
projectId: $projectId
|
||||
resourcePath: $resourcePath
|
||||
name: $name
|
||||
value: $value
|
||||
)
|
||||
}
|
||||
`;
|
||||
export const SetSubjectProjectsPermissionsDocument = `
|
||||
mutation setSubjectProjectsPermissions($subjectId: String!, $permissions: [RMProjectPermissions!]!) {
|
||||
rmSetSubjectProjectPermissions(subjectId: $subjectId, permissions: $permissions)
|
||||
@@ -4747,6 +4779,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
|
||||
setProjectPermissions(variables: SetProjectPermissionsMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<SetProjectPermissionsMutation> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<SetProjectPermissionsMutation>(SetProjectPermissionsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'setProjectPermissions', 'mutation');
|
||||
},
|
||||
setResourceProperty(variables: SetResourcePropertyMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<SetResourcePropertyMutation> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<SetResourcePropertyMutation>(SetResourcePropertyDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'setResourceProperty', 'mutation');
|
||||
},
|
||||
setSubjectProjectsPermissions(variables: SetSubjectProjectsPermissionsMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<SetSubjectProjectsPermissionsMutation> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<SetSubjectProjectsPermissionsMutation>(SetSubjectProjectsPermissionsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'setSubjectProjectsPermissions', 'mutation');
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
export function isValuesEqual<T extends string | boolean | number>(
|
||||
first: T | null | undefined,
|
||||
second: T | null | undefined,
|
||||
defaultValue?: T
|
||||
defaultValue?: T | null | undefined
|
||||
): boolean {
|
||||
return (first ?? defaultValue) === (second ?? defaultValue);
|
||||
}
|
||||
|
||||
+11
-1
@@ -59,7 +59,17 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap {
|
||||
items = [...items];
|
||||
|
||||
const connections = this.connectionsManagerService.projectConnections
|
||||
.filter(connection => !connection.template)
|
||||
.filter(connection => {
|
||||
if (
|
||||
!this.connectionSchemaManagerService.isProjectChangeable
|
||||
&& this.connectionSchemaManagerService.activeProjectId
|
||||
&& connection.projectId !== this.connectionSchemaManagerService.activeProjectId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !connection.template;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
if (a.connected === b.connected) {
|
||||
return compareConnectionsInfo(a, b);
|
||||
|
||||
+62
@@ -13,6 +13,7 @@ import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ExtensionUtils, IExtension } from '@cloudbeaver/core-extensions';
|
||||
import { type IObjectNavNodeProvider, type IDataContextActiveNode, isObjectNavNodeProvider } from '@cloudbeaver/core-navigation-tree';
|
||||
import { IProjectProvider, IProjectSetter, IProjectSetterState, isProjectProvider, isProjectSetter, isProjectSetterState } from '@cloudbeaver/core-projects';
|
||||
import { ITab, NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
|
||||
|
||||
|
||||
@@ -25,10 +26,13 @@ interface IActiveItem<T> {
|
||||
id: string;
|
||||
context: T;
|
||||
getCurrentNavNode?: IObjectNavNodeProvider<T>;
|
||||
getProjectSetterState?: IProjectSetterState<T>;
|
||||
getCurrentProjectId?: IProjectProvider<T>;
|
||||
getCurrentConnectionId?: IConnectionProvider<T>;
|
||||
getCurrentSchemaId?: IObjectSchemaProvider<T>;
|
||||
getCurrentCatalogId?: IObjectCatalogProvider<T>;
|
||||
changeConnectionId?: IConnectionSetter<T>;
|
||||
changeProjectId?: IProjectSetter<T>;
|
||||
changeCatalogId?: IObjectCatalogSetter<T>;
|
||||
changeSchemaId?: IObjectSchemaSetter<T>;
|
||||
}
|
||||
@@ -44,6 +48,15 @@ export class ConnectionSchemaManagerService {
|
||||
return this.activeItem.getCurrentNavNode(this.activeItem.context);
|
||||
}
|
||||
|
||||
get activeProjectId(): string | null | undefined {
|
||||
|
||||
if (!this.activeItem?.getCurrentProjectId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.activeItem.getCurrentProjectId(this.activeItem.context);
|
||||
}
|
||||
|
||||
get activeConnectionKey(): IConnectionInfoParams | null | undefined {
|
||||
|
||||
if (!this.activeItem?.getCurrentConnectionId) {
|
||||
@@ -53,6 +66,14 @@ export class ConnectionSchemaManagerService {
|
||||
return this.activeItem.getCurrentConnectionId(this.activeItem.context);
|
||||
}
|
||||
|
||||
get currentProjectId(): string | null | undefined {
|
||||
if (this.pendingProjectId !== null) {
|
||||
return this.pendingProjectId;
|
||||
}
|
||||
|
||||
return this.activeProjectId;
|
||||
}
|
||||
|
||||
get currentConnectionKey(): IConnectionInfoParams | null | undefined {
|
||||
if (this.pendingConnectionKey !== null) {
|
||||
return this.pendingConnectionKey;
|
||||
@@ -130,6 +151,10 @@ export class ConnectionSchemaManagerService {
|
||||
});
|
||||
}
|
||||
|
||||
get isProjectChangeable(): boolean {
|
||||
return !!this.activeItem?.changeProjectId && !!this.activeItem.getProjectSetterState?.(this.activeItem.context);
|
||||
}
|
||||
|
||||
get isConnectionChangeable(): boolean {
|
||||
return !!this.activeItem?.changeConnectionId;
|
||||
}
|
||||
@@ -148,6 +173,10 @@ export class ConnectionSchemaManagerService {
|
||||
);
|
||||
}
|
||||
|
||||
get isChangingProject(): boolean {
|
||||
return this.changingProjectId;
|
||||
}
|
||||
|
||||
get isChangingConnection(): boolean {
|
||||
return this.changingConnection;
|
||||
}
|
||||
@@ -177,9 +206,11 @@ export class ConnectionSchemaManagerService {
|
||||
return item;
|
||||
}
|
||||
|
||||
private pendingProjectId: string | null;
|
||||
private pendingConnectionKey: IConnectionInfoParams | null;
|
||||
private pendingCatalogId: string | null | undefined;
|
||||
private pendingSchemaId: string | null | undefined;
|
||||
private changingProjectId: boolean;
|
||||
private changingConnection: boolean;
|
||||
private changingConnectionContainer: boolean;
|
||||
|
||||
@@ -190,17 +221,21 @@ export class ConnectionSchemaManagerService {
|
||||
private readonly dbDriverResource: DBDriverResource,
|
||||
private readonly notificationService: NotificationService
|
||||
) {
|
||||
this.changingProjectId = false;
|
||||
this.changingConnection = false;
|
||||
this.changingConnectionContainer = false;
|
||||
this.pendingConnectionKey = null;
|
||||
this.pendingProjectId = null;
|
||||
this.pendingCatalogId = null;
|
||||
this.pendingSchemaId = null;
|
||||
|
||||
makeObservable<
|
||||
ConnectionSchemaManagerService,
|
||||
'activeItem'
|
||||
| 'changingProjectId'
|
||||
| 'changingConnection'
|
||||
| 'changingConnectionContainer'
|
||||
| 'pendingProjectId'
|
||||
| 'pendingConnectionKey'
|
||||
| 'pendingCatalogId'
|
||||
| 'pendingSchemaId'
|
||||
@@ -218,8 +253,10 @@ export class ConnectionSchemaManagerService {
|
||||
isObjectCatalogChangeable: computed,
|
||||
isObjectSchemaChangeable: computed,
|
||||
activeItem: computed,
|
||||
changingProjectId: observable,
|
||||
changingConnection: observable,
|
||||
changingConnectionContainer: observable,
|
||||
pendingProjectId: observable,
|
||||
pendingConnectionKey: observable,
|
||||
pendingCatalogId: observable,
|
||||
pendingSchemaId: observable,
|
||||
@@ -227,6 +264,24 @@ export class ConnectionSchemaManagerService {
|
||||
});
|
||||
}
|
||||
|
||||
async selectProjectId(projectId: string): Promise<void> {
|
||||
if (!this.activeItem?.changeProjectId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
runInAction(() => {
|
||||
this.changingProjectId = true;
|
||||
this.pendingProjectId = projectId;
|
||||
});
|
||||
await this.activeItem.changeProjectId(projectId, this.activeItem.context);
|
||||
} finally {
|
||||
runInAction(() => {
|
||||
this.changingProjectId = false;
|
||||
this.pendingProjectId = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger when user select connection in dropdown
|
||||
*/
|
||||
@@ -236,7 +291,9 @@ export class ConnectionSchemaManagerService {
|
||||
}
|
||||
try {
|
||||
runInAction(() => {
|
||||
this.changingProjectId = true;
|
||||
this.changingConnection = true;
|
||||
this.pendingProjectId = connectionKey.projectId;
|
||||
this.pendingConnectionKey = connectionKey;
|
||||
this.pendingSchemaId = undefined;
|
||||
this.pendingCatalogId = undefined;
|
||||
@@ -245,7 +302,9 @@ export class ConnectionSchemaManagerService {
|
||||
await this.updateContainer(connectionKey);
|
||||
} finally {
|
||||
runInAction(() => {
|
||||
this.changingProjectId = false;
|
||||
this.changingConnection = false;
|
||||
this.pendingProjectId = null;
|
||||
this.pendingConnectionKey = null;
|
||||
this.pendingSchemaId = null;
|
||||
this.pendingCatalogId = null;
|
||||
@@ -361,10 +420,13 @@ export class ConnectionSchemaManagerService {
|
||||
private setExtensions<T>(item: IActiveItem<T>, extensions: Array<IExtension<T>>) {
|
||||
ExtensionUtils.from(extensions)
|
||||
.on(isObjectNavNodeProvider, extension => { item.getCurrentNavNode = extension; })
|
||||
.on(isProjectSetterState, extension => { item.getProjectSetterState = extension; })
|
||||
.on(isProjectProvider, extension => { item.getCurrentProjectId = extension; })
|
||||
.on(isConnectionProvider, extension => { item.getCurrentConnectionId = extension; })
|
||||
.on(isObjectCatalogProvider, extension => { item.getCurrentCatalogId = extension; })
|
||||
.on(isObjectSchemaProvider, extension => { item.getCurrentSchemaId = extension; })
|
||||
|
||||
.on(isProjectSetter, extension => { item.changeProjectId = extension; })
|
||||
.on(isConnectionSetter, extension => { item.changeConnectionId = extension; })
|
||||
.on(isObjectCatalogSetter, extension => { item.changeCatalogId = extension; })
|
||||
.on(isObjectSchemaSetter, extension => { item.changeSchemaId = extension; });
|
||||
|
||||
@@ -45,7 +45,10 @@ export const ProjectSelect = observer(function ProjectSelect({
|
||||
|
||||
const projectsLoader = useMapResource(ProjectSelect, ProjectInfoResource, CachedMapAllKey, {
|
||||
onData: () => {
|
||||
if (!value && possibleOptions.length > 0) {
|
||||
if (
|
||||
(!value && possibleOptions.length > 0)
|
||||
|| (value && !possibleOptions.includes(value))
|
||||
) {
|
||||
onChange(possibleOptions[0]);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -65,6 +65,23 @@ export class NavResourceNodeService {
|
||||
);
|
||||
}
|
||||
|
||||
async setProperties(resourceData: IResourceData, diff: Record<string, any>): Promise<Record<string, any>> {
|
||||
return await this.resourceManagerResource.setProperties(
|
||||
resourceData.key.projectId,
|
||||
resourceData.resourcePath,
|
||||
diff
|
||||
);
|
||||
}
|
||||
|
||||
async getProperties(resourceData: IResourceData): Promise<Record<string, any>> {
|
||||
const resource = await this.resourceManagerResource.loadProperties(
|
||||
resourceData.key.projectId,
|
||||
resourceData.resourcePath
|
||||
);
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
getResourceData(nodeId: string): IResourceData | undefined {
|
||||
if (!isRMNavNode(nodeId)) {
|
||||
return;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { CachedMapResource, GraphQLService, ResourceKey, ResourceKeyUtils, RmResource } from '@cloudbeaver/core-sdk';
|
||||
import { isValuesEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
export type RmResourceInfo = RmResource;
|
||||
export interface IResourceManagerParams {
|
||||
@@ -23,6 +24,11 @@ export class ResourceManagerResource extends CachedMapResource<IResourceManagerP
|
||||
super();
|
||||
}
|
||||
|
||||
getResourceName(resourcePath: string): string {
|
||||
const parts = resourcePath.split('/');
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
|
||||
getFolder(resourcePath: string): string {
|
||||
const parts = resourcePath.split('/');
|
||||
return parts.slice(0, parts.length - 1).join('/');
|
||||
@@ -30,14 +36,16 @@ export class ResourceManagerResource extends CachedMapResource<IResourceManagerP
|
||||
|
||||
hasResource(key: IResourceManagerParams, resourcePath: string): boolean {
|
||||
const resources = this.get(key);
|
||||
const name = this.getResourceName(resourcePath);
|
||||
|
||||
return resources?.some(resource => resource.name === resourcePath) === true;
|
||||
return resources?.some(resource => resource.name === name) === true;
|
||||
}
|
||||
|
||||
getResource(key: IResourceManagerParams, resourcePath: string): RmResourceInfo | undefined {
|
||||
const resources = this.get(key);
|
||||
const name = this.getResourceName(resourcePath);
|
||||
|
||||
return resources?.find(resource => resource.name === resourcePath);
|
||||
return resources?.find(resource => resource.name === name);
|
||||
}
|
||||
|
||||
async move(projectId: string, oldPath: string, newPath: string): Promise<void> {
|
||||
@@ -55,6 +63,81 @@ export class ResourceManagerResource extends CachedMapResource<IResourceManagerP
|
||||
// this.set();
|
||||
}
|
||||
|
||||
async loadProperties(
|
||||
projectId: string,
|
||||
resourcePath: string
|
||||
): Promise<Record<string, any>> {
|
||||
const folder = this.getFolder(resourcePath);
|
||||
const nameMask = this.getResourceName(resourcePath);
|
||||
const key: IResourceManagerParams = { projectId, folder };
|
||||
|
||||
await this.load(key);
|
||||
await this.performUpdate(key, undefined, async () => {
|
||||
const { resources } = await this.graphQLService.sdk.getResourceList({
|
||||
projectId,
|
||||
folder,
|
||||
nameMask,
|
||||
readProperties: true,
|
||||
});
|
||||
|
||||
const currentResources = this.get(key);
|
||||
const resource = this.getResource(key, resourcePath);
|
||||
|
||||
if (resource) {
|
||||
currentResources?.splice(
|
||||
currentResources.indexOf(resource),
|
||||
1,
|
||||
...resources.map(resource => ({ properties: {}, ...resource }))
|
||||
);
|
||||
}
|
||||
|
||||
// this.dataSet(
|
||||
// key,
|
||||
// resources.map(resource => ({ properties: {}, ...resource }))
|
||||
// );
|
||||
}, () => {
|
||||
const resource = this.getResource(key, resourcePath);
|
||||
return resource?.properties && this.isLoaded(key) && !this.isOutdated(key);
|
||||
});
|
||||
|
||||
return this.getResource(key, resourcePath)?.properties ?? {};
|
||||
}
|
||||
|
||||
async setProperties(
|
||||
projectId: string,
|
||||
resourcePath: string,
|
||||
diff: Record<string, any>
|
||||
): Promise<Record<string, any>> {
|
||||
const folder = this.getFolder(resourcePath);
|
||||
const key: IResourceManagerParams = { projectId, folder };
|
||||
const propertiesPatch: Record<string, any> = {};
|
||||
const properties = await this.loadProperties(projectId, resourcePath);
|
||||
|
||||
await this.performUpdate(key, undefined, async () => {
|
||||
for (const [name, value] of Object.entries(diff)) {
|
||||
if (
|
||||
properties[name] === value
|
||||
|| (value === null && !(name in properties))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.graphQLService.sdk.setResourceProperty({
|
||||
projectId,
|
||||
resourcePath,
|
||||
name,
|
||||
value,
|
||||
});
|
||||
|
||||
propertiesPatch[name] = value;
|
||||
}
|
||||
|
||||
Object.assign(properties, propertiesPatch);
|
||||
});
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
async createResource(projectId: string, resourcePath: string, folder: boolean) {
|
||||
await this.graphQLService.sdk.createResource({
|
||||
projectId,
|
||||
@@ -62,7 +145,7 @@ export class ResourceManagerResource extends CachedMapResource<IResourceManagerP
|
||||
isFolder: folder,
|
||||
});
|
||||
|
||||
await this.load({ projectId, folder:folder ? resourcePath : this.getFolder(resourcePath) });
|
||||
await this.load({ projectId, folder: folder ? resourcePath : this.getFolder(resourcePath) });
|
||||
}
|
||||
|
||||
async writeResource(projectId: string, resourcePath: string, data: string, forceOverwrite: boolean) {
|
||||
@@ -98,7 +181,7 @@ export class ResourceManagerResource extends CachedMapResource<IResourceManagerP
|
||||
return key;
|
||||
}
|
||||
|
||||
const ref = this.keys.find(k => k.projectId === key.projectId && k.folder === key.folder);
|
||||
const ref = this.keys.find(k => k.projectId === key.projectId && isValuesEqual(k.folder, key.folder, ''));
|
||||
|
||||
if (ref) {
|
||||
return ref;
|
||||
@@ -117,7 +200,7 @@ export class ResourceManagerResource extends CachedMapResource<IResourceManagerP
|
||||
folder,
|
||||
});
|
||||
|
||||
this.dataSet(key, resources);
|
||||
this.dataSet(key, resources.map(({ properties, ...resource }) => resource));
|
||||
});
|
||||
|
||||
return this.data;
|
||||
|
||||
@@ -22,6 +22,7 @@ const style = css`
|
||||
|
||||
interface Payload {
|
||||
defaultScriptName?: string;
|
||||
projectId?: string | null;
|
||||
}
|
||||
|
||||
export interface ISaveScriptDialogResult {
|
||||
@@ -48,7 +49,7 @@ export const SaveScriptDialog: DialogComponent<Payload, ISaveScriptDialogResult>
|
||||
|
||||
const state = useObservableRef<State>(() => ({
|
||||
name: payload.defaultScriptName ?? '',
|
||||
projectId: null,
|
||||
projectId: payload.projectId ?? null,
|
||||
errorMessage: null,
|
||||
validate() {
|
||||
this.errorMessage = null;
|
||||
@@ -116,7 +117,7 @@ export const SaveScriptDialog: DialogComponent<Payload, ISaveScriptDialogResult>
|
||||
</InputField>
|
||||
<ProjectSelect
|
||||
value={state.projectId}
|
||||
filter={p => p.canEditResources}
|
||||
filter={p => (p.canEditResources && p.id === (payload.projectId ?? p.id))}
|
||||
autoHide
|
||||
onChange={projectId => {state.projectId = projectId;}}
|
||||
/>
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connecti
|
||||
|
||||
export interface IResourceNodeInfo {
|
||||
nodeId: string;
|
||||
projectId?: string;
|
||||
parents: string[];
|
||||
}
|
||||
|
||||
|
||||
@@ -11,13 +11,14 @@ import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dial
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { NavNodeManagerService, NavTreeResource, NavNodeInfoResource, NodeManagerUtils, type INodeNavigationData, NavigationType } from '@cloudbeaver/core-navigation-tree';
|
||||
import { ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
import { DATA_CONTEXT_TAB_ID } from '@cloudbeaver/core-ui';
|
||||
import { createPath } from '@cloudbeaver/core-utils';
|
||||
import { ActionService, ACTION_SAVE, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view';
|
||||
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
|
||||
import { NavResourceNodeService, RESOURCE_NODE_TYPE, SaveScriptDialog, ResourceManagerService, RESOURCES_NODE_PATH, ResourceProjectsResource, ResourcesProjectsNavNodeService } from '@cloudbeaver/plugin-resource-manager';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE, getSqlEditorName, SqlDataSourceService, SqlEditorService, SqlEditorSettingsService, SQL_EDITOR_ACTIONS_MENU } from '@cloudbeaver/plugin-sql-editor';
|
||||
import { NavResourceNodeService, RESOURCE_NODE_TYPE, SaveScriptDialog, ResourceManagerService, RESOURCES_NODE_PATH, ResourcesProjectsNavNodeService } from '@cloudbeaver/plugin-resource-manager';
|
||||
import { DATA_CONTEXT_SQL_EDITOR_STATE, getSqlEditorName, SqlDataSourceService, SqlEditorSettingsService, SQL_EDITOR_ACTIONS_MENU } from '@cloudbeaver/plugin-sql-editor';
|
||||
import { isSQLEditorTab, SqlEditorNavigatorService, SqlEditorTabService } from '@cloudbeaver/plugin-sql-editor-navigation-tab';
|
||||
|
||||
import { isScript } from './isScript';
|
||||
@@ -31,14 +32,13 @@ export class PluginBootstrap extends Bootstrap {
|
||||
private readonly navNodeManagerService: NavNodeManagerService,
|
||||
private readonly navTreeResource: NavTreeResource,
|
||||
private readonly navResourceNodeService: NavResourceNodeService,
|
||||
private readonly sqlEditorService: SqlEditorService,
|
||||
private readonly navNodeInfoResource: NavNodeInfoResource,
|
||||
private readonly navigationTabsService: NavigationTabsService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly sqlEditorNavigatorService: SqlEditorNavigatorService,
|
||||
private readonly resourceManagerService: ResourceManagerService,
|
||||
private readonly projectsService: ProjectsService,
|
||||
private readonly resourceProjectsResource: ResourceProjectsResource,
|
||||
private readonly projectInfoResource: ProjectInfoResource,
|
||||
private readonly sqlEditorTabResourceService: SqlEditorTabResourceService,
|
||||
private readonly commonDialogService: CommonDialogService,
|
||||
private readonly actionService: ActionService,
|
||||
@@ -86,20 +86,33 @@ export class PluginBootstrap extends Bootstrap {
|
||||
}
|
||||
|
||||
if (action === ACTION_SAVE) {
|
||||
let projectId = dataSource.executionContext?.projectId ?? null;
|
||||
await this.projectInfoResource.load(CachedMapAllKey);
|
||||
const name = getSqlEditorName(state, dataSource);
|
||||
|
||||
if (projectId) {
|
||||
const project = this.projectInfoResource.get(projectId);
|
||||
|
||||
if (!project?.canEditResources) {
|
||||
projectId = null;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.commonDialogService.open(SaveScriptDialog, {
|
||||
defaultScriptName: name,
|
||||
projectId,
|
||||
});
|
||||
|
||||
if (result !== DialogueStateResult.Rejected && result !== DialogueStateResult.Resolved) {
|
||||
try {
|
||||
if (!result.projectId) {
|
||||
projectId = result.projectId;
|
||||
|
||||
if (!projectId) {
|
||||
throw new Error('Project not selected');
|
||||
}
|
||||
|
||||
await this.resourceProjectsResource.load();
|
||||
const scriptName = `${result.name.trim()}.${SCRIPT_EXTENSION}`;
|
||||
const folder = createPath(RESOURCES_NODE_PATH, result.projectId);
|
||||
const folder = createPath(RESOURCES_NODE_PATH, projectId);
|
||||
const resourceData = this.navResourceNodeService.getResourceData(folder);
|
||||
|
||||
if (!resourceData) {
|
||||
@@ -109,7 +122,6 @@ export class PluginBootstrap extends Bootstrap {
|
||||
|
||||
const nodeId = await this.navResourceNodeService.saveScript(resourceData, scriptName, dataSource.script);
|
||||
|
||||
|
||||
await this.navTreeResource.preloadNodeParents(NodeManagerUtils.parentsFromPath(nodeId), nodeId);
|
||||
const node = await this.navNodeInfoResource.load(nodeId);
|
||||
|
||||
@@ -124,16 +136,16 @@ export class PluginBootstrap extends Bootstrap {
|
||||
const parents = NodeManagerUtils.parentsFromPath(nodeId);
|
||||
|
||||
dataSource.setNodeInfo({
|
||||
projectId,
|
||||
nodeId,
|
||||
parents,
|
||||
});
|
||||
|
||||
if (previousDataSource) {
|
||||
if (previousDataSource?.executionContext) {
|
||||
dataSource.setExecutionContext(previousDataSource.executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
this.sqlEditorService.setName(node.name ?? scriptName, state);
|
||||
this.notificationService.logSuccess({ title: 'plugin_resource_manager_save_script_success', message: node.name });
|
||||
|
||||
if (!this.resourceManagerService.active) {
|
||||
@@ -178,7 +190,7 @@ export class PluginBootstrap extends Bootstrap {
|
||||
}
|
||||
|
||||
try {
|
||||
const nodeInfo = await contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
|
||||
const nodeInfo = contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
|
||||
const node = await this.navNodeInfoResource.load(data.nodeId);
|
||||
|
||||
if (node.nodeType !== RESOURCE_NODE_TYPE || !isScript(node.id)) {
|
||||
@@ -246,11 +258,12 @@ export class PluginBootstrap extends Bootstrap {
|
||||
const parents = NodeManagerUtils.parentsFromPath(nodeId);
|
||||
|
||||
dataSource.setNodeInfo({
|
||||
projectId: node.projectId,
|
||||
nodeId,
|
||||
parents,
|
||||
});
|
||||
|
||||
if (previousDataSource) {
|
||||
if (previousDataSource?.executionContext) {
|
||||
dataSource.setExecutionContext(previousDataSource.executionContext);
|
||||
}
|
||||
|
||||
|
||||
+209
-73
@@ -6,15 +6,22 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
import { action, computed, makeObservable, observable, runInAction, toJS } from 'mobx';
|
||||
|
||||
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import { IConnectionExecutionContextInfo, NOT_INITIALIZED_CONTEXT_ID } from '@cloudbeaver/core-connections';
|
||||
import { TaskScheduler } from '@cloudbeaver/core-executor';
|
||||
import type { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import { debounce, isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
import { debounce, isArraysEqual, isObjectsEqual, isValuesEqual } from '@cloudbeaver/core-utils';
|
||||
import { BaseSqlDataSource, ESqlDataSourceFeatures } from '@cloudbeaver/plugin-sql-editor';
|
||||
|
||||
import type { IResourceNodeInfo, IResourceSqlDataSourceState } from './IResourceSqlDataSourceState';
|
||||
|
||||
interface IResourceProperties {
|
||||
'default-datasource'?: string;
|
||||
'default-catalog'?: string;
|
||||
'default-schema'?: string;
|
||||
}
|
||||
|
||||
interface IResourceInfo {
|
||||
isReadonly?: (dataSource: ResourceSqlDataSource) => boolean;
|
||||
}
|
||||
@@ -23,6 +30,15 @@ interface IResourceActions {
|
||||
rename(dataSource: ResourceSqlDataSource, nodeId: string, name: string): Promise<string>;
|
||||
read(dataSource: ResourceSqlDataSource, nodeId: string): Promise<string>;
|
||||
write(dataSource: ResourceSqlDataSource, nodeId: string, value: string): Promise<void>;
|
||||
getProperties(
|
||||
dataSource: ResourceSqlDataSource,
|
||||
nodeId: string
|
||||
): Promise<Record<string, any>>;
|
||||
setProperties(
|
||||
dataSource: ResourceSqlDataSource,
|
||||
nodeId: string,
|
||||
diff: Record<string, any>
|
||||
): Promise<Record<string, any>>;
|
||||
}
|
||||
|
||||
const VALUE_SYNC_DELAY = 1 * 1000;
|
||||
@@ -42,6 +58,10 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
return this._script;
|
||||
}
|
||||
|
||||
get projectId(): string | null {
|
||||
return this.nodeInfo?.projectId ?? null;
|
||||
}
|
||||
|
||||
get executionContext(): IConnectionExecutionContextInfo | undefined {
|
||||
return this.state.executionContext;
|
||||
}
|
||||
@@ -68,9 +88,10 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
private info?: IResourceInfo;
|
||||
private lastAction?: () => Promise<void>;
|
||||
private readonly state: IResourceSqlDataSourceState;
|
||||
private resourceProperties: IResourceProperties;
|
||||
|
||||
private loading: boolean;
|
||||
private loaded: boolean;
|
||||
private readonly scheduler: TaskScheduler;
|
||||
|
||||
constructor(
|
||||
private readonly navNodeInfoResource: NavNodeInfoResource,
|
||||
@@ -80,11 +101,12 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
this.state = state;
|
||||
this._script = '';
|
||||
this.saved = true;
|
||||
this.loading = false;
|
||||
this.loaded = false;
|
||||
this.resourceProperties = {};
|
||||
this.scheduler = new TaskScheduler(() => true);
|
||||
this.debouncedWrite = debounce(this.debouncedWrite.bind(this), VALUE_SYNC_DELAY);
|
||||
|
||||
makeObservable<this, '_script' | 'lastAction' | 'loading' | 'loaded'>(this, {
|
||||
makeObservable<this, '_script' | 'lastAction' | 'loaded' | 'resourceProperties'>(this, {
|
||||
script: computed,
|
||||
executionContext: computed,
|
||||
nodeInfo: computed,
|
||||
@@ -92,9 +114,10 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
equals: isArraysEqual,
|
||||
}),
|
||||
_script: observable,
|
||||
lastAction: observable,
|
||||
loading: observable,
|
||||
lastAction: observable.ref,
|
||||
loaded: observable,
|
||||
resourceProperties: observable,
|
||||
setExecutionContext: action,
|
||||
setScript: action,
|
||||
setNodeInfo: action,
|
||||
});
|
||||
@@ -113,7 +136,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
}
|
||||
|
||||
isLoading(): boolean {
|
||||
return this.loading;
|
||||
return this.scheduler.executing;
|
||||
}
|
||||
|
||||
setNodeInfo(nodeInfo?: IResourceNodeInfo): void {
|
||||
@@ -135,6 +158,10 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
this.rename(name);
|
||||
}
|
||||
|
||||
setProject(projectId: string | null): void {
|
||||
|
||||
}
|
||||
|
||||
canRename(name: string | null): boolean {
|
||||
if (this.isReadonly()) {
|
||||
return false;
|
||||
@@ -168,96 +195,205 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
|
||||
async load(): Promise<void> {
|
||||
await this.read();
|
||||
await this.updateProperties();
|
||||
}
|
||||
|
||||
setExecutionContext(executionContext?: IConnectionExecutionContextInfo): void {
|
||||
this.state.executionContext = executionContext;
|
||||
if (
|
||||
this.nodeInfo?.projectId
|
||||
&& executionContext?.projectId
|
||||
&& this.nodeInfo.projectId !== executionContext.projectId
|
||||
) {
|
||||
console.warn('Cant change execution context because of different projects');
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!isObjectsEqual(toJS(this.state.executionContext), toJS(executionContext))
|
||||
) {
|
||||
this.state.executionContext = toJS(executionContext);
|
||||
}
|
||||
|
||||
if (
|
||||
this.isReadonly()
|
||||
|| (
|
||||
this.resourceProperties['default-datasource'] === executionContext?.connectionId
|
||||
&& this.resourceProperties['default-catalog'] === executionContext?.defaultCatalog
|
||||
&& this.resourceProperties['default-schema'] === executionContext?.defaultSchema
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resourceProperties['default-datasource'] = executionContext?.connectionId;
|
||||
this.resourceProperties['default-catalog'] = executionContext?.defaultCatalog;
|
||||
this.resourceProperties['default-schema'] = executionContext?.defaultSchema;
|
||||
|
||||
this.setProperties(toJS(this.resourceProperties));
|
||||
}
|
||||
|
||||
async rename(name: string | null) {
|
||||
await this.write();
|
||||
|
||||
if (
|
||||
!this.actions
|
||||
await this.scheduler.schedule(undefined, async () => {
|
||||
if (
|
||||
!this.actions
|
||||
|| !this.nodeInfo
|
||||
|| !this.saved
|
||||
|| !name?.trim()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name.toLowerCase().endsWith('.sql')) {
|
||||
name += '.sql';
|
||||
}
|
||||
|
||||
this.lastAction = this.rename.bind(this, name);
|
||||
this.loading = true;
|
||||
this.message = 'Renaming script...';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
this.nodeInfo.nodeId = await this.actions.rename(this, this.nodeInfo.nodeId, name);
|
||||
|
||||
if (this.nodeInfo.parents.length > 0) {
|
||||
this.nodeInfo.parents.splice(this.nodeInfo.parents.length - 1, 1, this.nodeInfo.nodeId);
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.markOutdated();
|
||||
this.saved = true;
|
||||
this.loaded = false;
|
||||
} catch (exception: any) {
|
||||
this.exception = exception;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.message = undefined;
|
||||
}
|
||||
if (!name.toLowerCase().endsWith('.sql')) {
|
||||
name += '.sql';
|
||||
}
|
||||
|
||||
this.lastAction = this.rename.bind(this, name);
|
||||
this.message = 'Renaming script...';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
this.nodeInfo.nodeId = await this.actions.rename(this, this.nodeInfo.nodeId, name);
|
||||
|
||||
if (this.nodeInfo.parents.length > 0) {
|
||||
this.nodeInfo.parents.splice(this.nodeInfo.parents.length - 1, 1, this.nodeInfo.nodeId);
|
||||
}
|
||||
|
||||
this.markOutdated();
|
||||
this.saved = true;
|
||||
this.loaded = false;
|
||||
} catch (exception: any) {
|
||||
this.exception = exception;
|
||||
} finally {
|
||||
this.message = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async read() {
|
||||
if (!this.actions || !this.nodeInfo || !this.isOutdated() || !this.saved) {
|
||||
return;
|
||||
}
|
||||
await this.scheduler.schedule(undefined, async () => {
|
||||
if (!this.actions || !this.nodeInfo || !this.isOutdated() || !this.saved) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastAction = this.read.bind(this);
|
||||
this.loading = true;
|
||||
this.message = 'Reading script...';
|
||||
this.lastAction = this.read.bind(this);
|
||||
this.message = 'Reading script...';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
this._script = await this.actions.read(this, this.nodeInfo.nodeId);
|
||||
this.state.name = this.name ?? undefined;
|
||||
this.markUpdated();
|
||||
this.loaded = true;
|
||||
super.setScript(this.script);
|
||||
} catch (exception: any) {
|
||||
this.exception = exception;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.message = undefined;
|
||||
}
|
||||
try {
|
||||
this.exception = null;
|
||||
this._script = await this.actions.read(this, this.nodeInfo.nodeId);
|
||||
this.state.name = this.name ?? undefined;
|
||||
this.markUpdated();
|
||||
this.loaded = true;
|
||||
super.setScript(this.script);
|
||||
} catch (exception: any) {
|
||||
this.exception = exception;
|
||||
} finally {
|
||||
this.message = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async write() {
|
||||
if (!this.actions || !this.nodeInfo || this.saved) {
|
||||
await this.scheduler.schedule(undefined, async () => {
|
||||
if (!this.actions || !this.nodeInfo || this.saved) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastAction = this.write.bind(this);
|
||||
this.message = 'Saving script...';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
await this.actions.write(this, this.nodeInfo.nodeId, this.script);
|
||||
this.state.name = this.name ?? undefined;
|
||||
this.saved = true;
|
||||
this.markUpdated();
|
||||
} catch (exception: any) {
|
||||
this.exception = exception;
|
||||
} finally {
|
||||
this.message = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async setProperties(properties: IResourceProperties) {
|
||||
if (Object.keys(properties).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastAction = this.write.bind(this);
|
||||
this.loading = true;
|
||||
this.message = 'Saving script...';
|
||||
await this.scheduler.schedule(undefined, async () => {
|
||||
if (!this.actions || !this.nodeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
await this.actions.write(this, this.nodeInfo.nodeId, this.script);
|
||||
this.state.name = this.name ?? undefined;
|
||||
this.saved = true;
|
||||
this.markUpdated();
|
||||
} catch (exception: any) {
|
||||
this.exception = exception;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.message = undefined;
|
||||
this.lastAction = this.setProperties.bind(this, properties);
|
||||
this.message = 'Update info...';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
|
||||
if (!this.isReadonly()) {
|
||||
await this.actions.setProperties(
|
||||
this,
|
||||
this.nodeInfo.nodeId,
|
||||
properties
|
||||
);
|
||||
}
|
||||
|
||||
await this.updateProperties();
|
||||
} catch (exception: any) {
|
||||
this.exception = exception;
|
||||
} finally {
|
||||
this.message = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async updateProperties() {
|
||||
if (!this.actions || !this.nodeInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousProperties = this.resourceProperties;
|
||||
|
||||
const resourceProperties = await this.actions.getProperties(this, this.nodeInfo.nodeId);
|
||||
|
||||
runInAction(() => {
|
||||
this.resourceProperties = toJS(resourceProperties);
|
||||
|
||||
if (isObjectsEqual(toJS(previousProperties), toJS(this.resourceProperties)) && this.isReadonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionId = this.resourceProperties['default-datasource'];
|
||||
const defaultCatalog = this.resourceProperties['default-catalog'];
|
||||
const defaultSchema = this.resourceProperties['default-schema'];
|
||||
|
||||
if (!this.nodeInfo!.projectId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectionId) {
|
||||
if (
|
||||
!isValuesEqual(this.state.executionContext?.connectionId, connectionId, null)
|
||||
|| !isValuesEqual(this.state.executionContext?.defaultCatalog, defaultCatalog, null)
|
||||
|| !isValuesEqual(this.state.executionContext?.defaultSchema, defaultSchema, null)
|
||||
|| !isValuesEqual(this.state.executionContext?.projectId, this.nodeInfo!.projectId, null)
|
||||
) {
|
||||
this.state.executionContext = {
|
||||
id: NOT_INITIALIZED_CONTEXT_ID,
|
||||
projectId: this.nodeInfo!.projectId,
|
||||
connectionId,
|
||||
defaultCatalog,
|
||||
defaultSchema,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this.state.executionContext = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private debouncedWrite() {
|
||||
|
||||
+54
-4
@@ -8,7 +8,6 @@
|
||||
|
||||
import { action, makeObservable, observable, untracked } from 'mobx';
|
||||
|
||||
import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
@@ -69,6 +68,7 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
!['undefined', 'object'].includes(typeof value.nodeInfo)
|
||||
|| !['string', 'undefined'].includes(typeof value.name)
|
||||
|| !['string', 'undefined'].includes(typeof value.nodeInfo?.nodeId)
|
||||
|| !['string', 'undefined'].includes(typeof value.nodeInfo?.projectId)
|
||||
|| !['undefined', 'object'].includes(typeof value.nodeInfo?.parents)
|
||||
|| !['undefined', 'object'].includes(typeof value.executionContext)
|
||||
|| !['string', 'undefined'].includes(typeof value.executionContext?.connectionId)
|
||||
@@ -97,10 +97,13 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
this.navNodeInfoResource,
|
||||
this.createState(
|
||||
editorId,
|
||||
options?.executionContext,
|
||||
)
|
||||
);
|
||||
|
||||
if (options?.executionContext) {
|
||||
dataSource.setExecutionContext(options.executionContext);
|
||||
}
|
||||
|
||||
if (options?.script) {
|
||||
dataSource.setScript(options.script);
|
||||
}
|
||||
@@ -113,6 +116,8 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
rename: this.rename.bind(this),
|
||||
read: this.read.bind(this),
|
||||
write: this.write.bind(this),
|
||||
getProperties: this.getProperties.bind(this),
|
||||
setProperties: this.setProperties.bind(this),
|
||||
});
|
||||
|
||||
dataSource.setInfo({
|
||||
@@ -169,14 +174,12 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
|
||||
private createState(
|
||||
editorId: string,
|
||||
executionContext?: IConnectionExecutionContextInfo,
|
||||
nodeInfo?: IResourceNodeInfo
|
||||
): IResourceSqlDataSourceState {
|
||||
let state = this.dataSourceStateState.get(editorId);
|
||||
|
||||
if (!state) {
|
||||
state = observable<IResourceSqlDataSourceState>({
|
||||
executionContext,
|
||||
nodeInfo,
|
||||
});
|
||||
|
||||
@@ -309,6 +312,53 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
private async getProperties(
|
||||
dataSource: ResourceSqlDataSource,
|
||||
nodeId: string
|
||||
): Promise<Record<string, any>> {
|
||||
if (!dataSource.nodeInfo) {
|
||||
throw new Error('Node info is not provided');
|
||||
}
|
||||
|
||||
try {
|
||||
await this.navTreeResource.preloadNodeParents(dataSource.nodeInfo.parents, dataSource.nodeInfo.nodeId);
|
||||
const resourceData = this.navResourceNodeService.getResourceData(nodeId);
|
||||
|
||||
if (!resourceData) {
|
||||
throw new Error('Can\'t find resource');
|
||||
}
|
||||
|
||||
return await this.navResourceNodeService.getProperties(resourceData);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception as any, 'plugin_resource_manager_sync_script_error');
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private async setProperties(
|
||||
dataSource: ResourceSqlDataSource,
|
||||
nodeId: string,
|
||||
diff: Record<string, any>
|
||||
): Promise<Record<string, any>> {
|
||||
if (!this.resourceManagerService.enabled || !dataSource.nodeInfo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
await this.navTreeResource.preloadNodeParents(dataSource.nodeInfo.parents, dataSource.nodeInfo.nodeId);
|
||||
const resourceData = this.navResourceNodeService.getResourceData(nodeId);
|
||||
|
||||
if (!resourceData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return await this.navResourceNodeService.setProperties(resourceData, diff);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception as any, 'plugin_resource_manager_update_script_error');
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private async read(dataSource: ResourceSqlDataSource, nodeId: string): Promise<string> {
|
||||
if (!dataSource.nodeInfo) {
|
||||
throw new Error('Node info is not provided');
|
||||
|
||||
@@ -29,9 +29,10 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { Executor, ExecutorInterrupter, IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { objectNavNodeProvider, NodeManagerUtils, NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import { projectProvider, projectSetter, projectSetterState } from '@cloudbeaver/core-projects';
|
||||
import { CachedMapAllKey, NavNodeInfoFragment, ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
|
||||
import { NavigationTabsService, TabHandler, ITab, ITabOptions } from '@cloudbeaver/plugin-navigation-tabs';
|
||||
import { SqlResultTabsService, ISqlEditorTabState, SqlEditorService, SqlDataSourceService } from '@cloudbeaver/plugin-sql-editor';
|
||||
import { SqlResultTabsService, ISqlEditorTabState, SqlEditorService, SqlDataSourceService, ESqlDataSourceFeatures } from '@cloudbeaver/plugin-sql-editor';
|
||||
|
||||
import { isSQLEditorTab } from './isSQLEditorTab';
|
||||
import { SqlEditorPanel } from './SqlEditorPanel';
|
||||
@@ -75,9 +76,12 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
canClose: this.handleCanTabClose.bind(this),
|
||||
extensions: [
|
||||
objectNavNodeProvider(this.getNavNode.bind(this)),
|
||||
projectSetterState(this.getProjectSetState.bind(this)),
|
||||
projectProvider(this.getProjectId.bind(this)),
|
||||
connectionProvider(this.getConnectionId.bind(this)),
|
||||
objectCatalogProvider(this.getObjectCatalogId.bind(this)),
|
||||
objectSchemaProvider(this.getObjectSchemaId.bind(this)),
|
||||
projectSetter(this.setProjectId.bind(this)),
|
||||
connectionSetter((connectionId, tab) => this.setConnectionId(tab, connectionId)),
|
||||
objectCatalogSetter(this.setObjectCatalogId.bind(this)),
|
||||
objectSchemaSetter(this.setObjectSchemaId.bind(this)),
|
||||
@@ -211,7 +215,10 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
isSQLEditorTab(tab => {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
|
||||
return !!dataSource?.executionContext;
|
||||
return (
|
||||
!!dataSource?.executionContext
|
||||
&& ResourceKeyUtils.includes(key, dataSource.executionContext.id)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -318,6 +325,18 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
return true;
|
||||
}
|
||||
|
||||
private getProjectId(tab: ITab<ISqlEditorTabState>): string | undefined {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
|
||||
return dataSource?.projectId ?? undefined;
|
||||
}
|
||||
|
||||
private getProjectSetState(tab: ITab<ISqlEditorTabState>): boolean {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
|
||||
return !!dataSource?.features.includes(ESqlDataSourceFeatures.setProject);
|
||||
}
|
||||
|
||||
private getConnectionId(tab: ITab<ISqlEditorTabState>): IConnectionInfoParams | undefined {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
|
||||
@@ -343,6 +362,15 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
return context?.defaultSchema;
|
||||
}
|
||||
|
||||
private setProjectId(projectId: string | null, tab: ITab<ISqlEditorTabState>): boolean {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
|
||||
dataSource?.setProject(projectId);
|
||||
this.attachToProject(tab, projectId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async setConnectionId(
|
||||
tab: ITab<ISqlEditorTabState>,
|
||||
connectionKey: IConnectionInfoParams,
|
||||
|
||||
@@ -20,6 +20,11 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
abstract get executionContext(): IConnectionExecutionContextInfo | undefined;
|
||||
exception?: Error | Error[] | null | undefined;
|
||||
message?: string;
|
||||
|
||||
get projectId(): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
get features(): ESqlDataSourceFeatures[] {
|
||||
return [];
|
||||
}
|
||||
@@ -91,6 +96,10 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
this.editing = state;
|
||||
}
|
||||
|
||||
setProject(projectId: string | null): void {
|
||||
|
||||
}
|
||||
|
||||
load(): Promise<void> | void { }
|
||||
dispose(): void | Promise<void> { }
|
||||
}
|
||||
@@ -7,5 +7,6 @@
|
||||
*/
|
||||
|
||||
export enum ESqlDataSourceFeatures {
|
||||
setName
|
||||
setName,
|
||||
setProject
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import type { ESqlDataSourceFeatures } from './ESqlDataSourceFeatures';
|
||||
export interface ISqlDataSource extends ILoadableState {
|
||||
readonly name: string | null;
|
||||
readonly script: string;
|
||||
readonly projectId: string | null;
|
||||
readonly executionContext?: IConnectionExecutionContextInfo;
|
||||
readonly message?: string;
|
||||
readonly onSetScript: ISyncExecutor<string>;
|
||||
@@ -27,6 +28,7 @@ export interface ISqlDataSource extends ILoadableState {
|
||||
markUpdated(): void;
|
||||
canRename(name: string | null): boolean;
|
||||
setName(name: string | null): void;
|
||||
setProject(projectId: string | null): void;
|
||||
setScript(script: string): void;
|
||||
setEditing(state: boolean): void;
|
||||
setExecutionContext(executionContext?: IConnectionExecutionContextInfo): void;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useEffect } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { splitStyles, Split, ResizerControls, Pane, splitHorizontalStyles, Overlay, OverlayMessage, OverlayActions, Button, useMapResource, getComputed, OverlayHeader, OverlayHeaderIcon, OverlayHeaderTitle, OverlayHeaderSubTitle, useSplitUserState, Loader, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { ConnectionExecutionContextResource, ConnectionInfoResource, DBDriverResource } from '@cloudbeaver/core-connections';
|
||||
import { ConnectionExecutionContextResource, ConnectionInfoResource, createConnectionParam, DBDriverResource, getRealExecutionContextId } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NodeManagerUtils } from '@cloudbeaver/core-navigation-tree';
|
||||
import { CaptureView } from '@cloudbeaver/core-view';
|
||||
@@ -74,10 +74,7 @@ export const SqlEditor = observer<Props>(function SqlEditor({ state }) {
|
||||
SqlEditor,
|
||||
ConnectionInfoResource,
|
||||
dataSource?.executionContext
|
||||
? ({
|
||||
projectId: dataSource.executionContext.projectId,
|
||||
connectionId: dataSource.executionContext.connectionId,
|
||||
})
|
||||
? createConnectionParam(dataSource.executionContext.projectId, dataSource.executionContext.connectionId)
|
||||
: null
|
||||
);
|
||||
const driver = useMapResource(SqlEditor, DBDriverResource, connection.data?.driverId ?? null);
|
||||
@@ -88,13 +85,13 @@ export const SqlEditor = observer<Props>(function SqlEditor({ state }) {
|
||||
const context = useMapResource(
|
||||
SqlEditor,
|
||||
ConnectionExecutionContextResource,
|
||||
connected ? (dataSource?.executionContext?.id ?? null) : null
|
||||
connected ? getRealExecutionContextId(dataSource?.executionContext?.id) : null
|
||||
);
|
||||
|
||||
const initializingContext = getComputed(() => connection.isLoading() || context.isLoading());
|
||||
const initExecutionContext = getComputed(() => (
|
||||
context.data === undefined
|
||||
&& dataSource?.executionContext !== undefined
|
||||
&& connection.data !== undefined
|
||||
));
|
||||
|
||||
async function cancelConnection() {
|
||||
|
||||
Reference in New Issue
Block a user