Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CB-3456 refactor: user form #2010

Merged
merged 28 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
289db17
CB-3456 chore: cleanup users administration
Wroud Sep 14, 2023
382113f
CB-3456 chore: add label for user credentials column
Wroud Sep 14, 2023
f18c342
CB-3456 chore: add deprecation message to reshadow system
Wroud Sep 14, 2023
494ccf1
CB-3456 chore: add deprecation message to ComponentStyle
Wroud Sep 14, 2023
57cb3cd
Merge remote-tracking branch 'origin/devel' into feat/cb-3456/form-te…
Wroud Sep 18, 2023
68f3a21
CB-3456 refactor: user form
Wroud Sep 21, 2023
5fa14cd
Merge remote-tracking branch 'origin/devel' into feat/cb-3456/form-te…
Wroud Sep 21, 2023
c3e0034
Merge branch 'devel' into feat/cb-3456/form-template
Wroud Sep 22, 2023
b72aa1a
Merge branch 'devel' into feat/cb-3456/form-template
Wroud Sep 22, 2023
76a3def
CB-3456 fix: eslint problems
Wroud Sep 22, 2023
8940f67
CB-3456 fix: after review
Wroud Sep 25, 2023
b41695f
CB-3456 fix: base form localization
Wroud Sep 25, 2023
4bf9d9c
CB-3456 fix: remove unused import
Wroud Sep 25, 2023
f5b2927
Merge branch 'devel' into feat/cb-3456/form-template
EvgeniaBzzz Sep 25, 2023
0d61056
Merge branch 'devel' into feat/cb-3456/form-template
EvgeniaBzzz Sep 26, 2023
dfb635e
CB-3456 fix: user default team assignment
Wroud Sep 26, 2023
e6530a1
Merge branch 'devel' into feat/cb-3456/form-template
EvgeniaBzzz Sep 26, 2023
7d58ced
Merge branch 'devel' into feat/cb-3456/form-template
EvgeniaBzzz Sep 26, 2023
1d797af
CB-3456 chore: update git indexes
Wroud Sep 27, 2023
c225a46
CB-3456 fix: imports
Wroud Sep 27, 2023
7adf9d6
Merge remote-tracking branch 'origin/devel' into feat/cb-3456/form-te…
Wroud Sep 27, 2023
8e3eac6
CB-3456 fix: enable user by default
Wroud Sep 27, 2023
a386436
CB-3456 refactor: implement FormPart
Wroud Sep 27, 2023
bd9b9be
Merge remote-tracking branch 'origin/devel' into feat/cb-3456/form-te…
Wroud Sep 27, 2023
ec9ad2c
CB-3456 fix: make sh executable
Wroud Sep 28, 2023
92735f1
CB-3456 fix: make sh executable
Wroud Sep 28, 2023
f61e6d6
CB-3456 fix: handle form part exceptions on save
Wroud Sep 28, 2023
3743a1f
Merge branch 'devel' into feat/cb-3456/form-template
EvgeniaBzzz Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 5 additions & 24 deletions webapp/packages/core-authentication/src/UsersResource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,6 @@ export const UsersResourceNewUsers = resourceKeyListAlias('@users-resource/new-u

interface UserCreateOptions {
userId: string;
teams: string[];
credentials: IAuthCredentials;
metaParameters: Record<string, any>;
grantedConnections: string[];
enabled: boolean;
authRole?: string;
}

@injectable()
Expand Down Expand Up @@ -129,30 +123,17 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
await this.graphQLService.sdk.saveUserMetaParameters({ userId, parameters });
}

async create({ userId, teams, credentials, metaParameters, grantedConnections, enabled, authRole }: UserCreateOptions): Promise<AdminUser> {
async create({ userId }: UserCreateOptions): Promise<AdminUser> {
const { user } = await this.graphQLService.sdk.createUser({
userId,
enabled,
authRole,
enabled: false,
...this.getDefaultIncludes(),
...this.getIncludesMap(userId),
});

try {
await this.updateCredentials(userId, credentials);

for (const teamId of teams) {
await this.grantTeam(userId, teamId, true);
}

await this.setConnections(userId, grantedConnections);
await this.setMetaParameters(userId, metaParameters);
const user = (await this.refresh(userId)) as unknown as AdminUserNew;
user[NEW_USER_SYMBOL] = true;
} catch (exception: any) {
this.delete(userId);
throw exception;
}
const newUser = user as unknown as AdminUserNew;
newUser[NEW_USER_SYMBOL] = true;
this.set(user.userId, newUser);

return this.get(user.userId)!;
}
Expand Down
4 changes: 4 additions & 0 deletions webapp/packages/core-blocks/src/Containers/Group.m.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@
&.center {
margin: 0 auto;
}

&.overflow {
overflow: auto;
}
}
67 changes: 67 additions & 0 deletions webapp/packages/core-blocks/src/FormControls/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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.
*/
import React, { forwardRef, useState } from 'react';

import { useCombinedRef } from '../useCombinedRef';
import { useFocus } from '../useFocus';
import { FormChangeHandler, FormContext, IFormContext } from './FormContext';
import { useForm } from './useForm';

type FormDetailedProps = Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, 'onChange' | 'onSubmit'> & {
context?: IFormContext;
disabled?: boolean;
disableEnterSubmit?: boolean;
focusFirstChild?: boolean;
onSubmit?: (event?: SubmitEvent) => Promise<void> | void;
onChange?: FormChangeHandler;
};

export const Form = forwardRef<HTMLFormElement, FormDetailedProps>(function Form(
{ context, disabled: disabledProp, disableEnterSubmit, focusFirstChild, children, onSubmit, onChange, ...rest },
ref,
) {
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild });
const [disabledLocal, setDisabledLocal] = useState(false);

const disabled = disabledLocal || disabledProp || false;

const formContext = useForm({
disableEnterSubmit,
parent: context,
onSubmit(event) {
const result = onSubmit?.(event);

if (result instanceof Promise) {
setDisabledLocal(true);
result.finally(() => {
setDisabledLocal(false);
});
}
},
onChange,
});

const setFormRef = useCombinedRef<HTMLFormElement>(formContext.setRef, focusedRef, ref);

if (formContext.parent && formContext.parent !== context) {
return (
<fieldset disabled={disabled} className={rest.className}>
<FormContext.Provider value={formContext}>{children}</FormContext.Provider>
</fieldset>
);
}

return (
<form {...rest} ref={setFormRef}>
<fieldset disabled={disabled} className={rest.className}>
<FormContext.Provider value={formContext}>{children}</FormContext.Provider>
</fieldset>
<button type="submit" disabled={disableEnterSubmit} aria-hidden={disableEnterSubmit} hidden />
</form>
);
});
13 changes: 11 additions & 2 deletions webapp/packages/core-blocks/src/FormControls/FormContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import { createContext } from 'react';

import type { IExecutor } from '@cloudbeaver/core-executor';
import type { IExecutor, SyncExecutor } from '@cloudbeaver/core-executor';

export type FormChangeValues = string | number | boolean | FileList | null | undefined;
export type FormChangeHandler = (value: FormChangeValues, name: string | undefined) => void;
Expand All @@ -19,9 +19,18 @@ export interface IChangeData {
}

export interface IFormContext {
changeExecutor: IExecutor<IChangeData>;
ref: HTMLFormElement | null;
onValidate: SyncExecutor;
onSubmit: SyncExecutor;
onChange: IExecutor<IChangeData>;
parent: IFormContext | null;
disableEnterSubmit: boolean;
setRef: (ref: HTMLFormElement | null) => void;
change: FormChangeHandler;
keyDown: KeyHandler;
validate: () => boolean;
reportValidity: () => boolean;
submit: (event?: SubmitEvent) => void;
}

export const FormContext = createContext<IFormContext | null>(null);
10 changes: 7 additions & 3 deletions webapp/packages/core-blocks/src/FormControls/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Loader } from '../Loader/Loader';
import { useTranslate } from '../localization/useTranslate';
import { s } from '../s';
import { useCombinedHandler } from '../useCombinedHandler';
import { useCombinedRef } from '../useCombinedRef';
import { useMergeRefs } from '../useMergeRefs';
import { useS } from '../useS';
import { useStateDelay } from '../useStateDelay';
Expand Down Expand Up @@ -97,7 +98,7 @@ export const InputField: InputFieldType = observer(
ref,
) {
const inputRef = useRef<HTMLInputElement | null>(null);
const mergedRef = useMergeRefs(inputRef, ref);
const mergedRef = useCombinedRef(inputRef, ref);
const capsLock = useCapsLockTracker();
const [passwordRevealed, setPasswordRevealed] = useState(false);
const translate = useTranslate();
Expand Down Expand Up @@ -137,9 +138,9 @@ export const InputField: InputFieldType = observer(
const handleKeyDown = useCombinedHandler(rest.onKeyDown, capsLock.handleKeyDown, context?.keyDown);

const passwordType = rest.type === 'password';
const uncontrolled = passwordType && !canShowPassword;
let uncontrolled = passwordType && !canShowPassword;

let value: any = valueControlled ?? defaultValue ?? undefined;
let value: any = valueControlled ?? undefined;

if (state && name !== undefined && name in state) {
value = state[name];
Expand All @@ -153,6 +154,8 @@ export const InputField: InputFieldType = observer(
description = translate('ui_capslock_on');
}

uncontrolled ||= value === undefined;

useLayoutEffect(() => {
if (uncontrolled && isNotNullDefined(value) && inputRef.current) {
inputRef.current.value = value;
Expand All @@ -176,6 +179,7 @@ export const InputField: InputFieldType = observer(
type={passwordRevealed ? 'text' : rest.type}
name={name}
value={uncontrolled ? undefined : value ?? ''}
defaultValue={defaultValue}
className={styles.input}
onChange={handleChange}
onBlur={handleBlur}
Expand Down
101 changes: 0 additions & 101 deletions webapp/packages/core-blocks/src/FormControls/SubmittingForm.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.
*/
import { useContext, useEffect, useRef } from 'react';

import { ExecutorInterrupter } from '@cloudbeaver/core-executor';

import { useExecutor } from '../useExecutor';
import { FormContext } from './FormContext';

export function useCustomInputValidation<T = void>(validation: (value: T) => string | null): React.RefObject<HTMLInputElement> {
const context = useContext(FormContext);
const inputRef = useRef<HTMLInputElement>(null);

useExecutor({
executor: context?.onValidate,
handlers: [
function validationHandler(_, context) {
if (!inputRef.current) {
return;
}

let value: T = undefined as unknown as T;

if (inputRef.current instanceof HTMLInputElement) {
value = inputRef.current.value as unknown as T;
}

const result = validation(value);

if (typeof result === 'string') {
inputRef.current.setCustomValidity(result);
ExecutorInterrupter.interrupt(context);
} else {
inputRef.current?.setCustomValidity('');
}
},
],
});

useEffect(() => {
const element = inputRef.current;
if (!element) {
return;
}

function resetValidationMessage() {
element?.setCustomValidity('');
}

element.addEventListener('input', resetValidationMessage);

return () => {
element?.removeEventListener('input', resetValidationMessage);
};
});

return inputRef;
}
Loading
Loading