Skip to content

Commit

Permalink
feat(inferencer): Ability to transform/ignore fields in inferencer co…
Browse files Browse the repository at this point in the history
…mponents (refinedev#3173)

* refactor: update component naming convention

* refactor(inferencer): log relation fetch attempts

* chore(examples): update multi tenancy example

* feat(inferencer): add `fieldTransformer` prop to inferencers

* chore(inferencer): remove unused param

* feat(inferencer): try to fetch relation from basic fields

* chore: add changeset

* chore: update snapshots
  • Loading branch information
aliemir authored Dec 9, 2022
1 parent cb23844 commit 15402d3
Show file tree
Hide file tree
Showing 21 changed files with 1,052 additions and 448 deletions.
7 changes: 7 additions & 0 deletions .changeset/tasty-cameras-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@pankod/refine-inferencer": minor
---

- Added `fieldTransformer` prop to inferencer components to let users transform or hide the field to be rendered.
- Hide networks errors caused by the relation detection process.
- Added the ability to detect relations from basic types like `"text"` and `"number"`.
17 changes: 11 additions & 6 deletions examples/multi-tenancy/appwrite/src/authProvider.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import { AppwriteException } from "@pankod/refine-appwrite";
import { AuthProvider } from "@pankod/refine-core";

import { appwriteClient } from "utility";
import { account } from "utility";

export const authProvider: AuthProvider = {
login: async ({ email, password }) => {
try {
await appwriteClient.account.createSession(email, password);
await account.createEmailSession(email, password);
return Promise.resolve();
} catch (e) {
return Promise.reject();
const { type, message, code } = e as AppwriteException;
return Promise.reject({
message,
name: `${code} - ${type}`,
});
}
},
logout: async () => {
await appwriteClient.account.deleteSession("current");
await account.deleteSession("current");

return "/";
},
checkError: () => Promise.resolve(),
checkAuth: async () => {
const session = await appwriteClient.account.getSession("current");
const session = await account.getSession("current");

if (session) {
return Promise.resolve();
Expand All @@ -28,7 +33,7 @@ export const authProvider: AuthProvider = {
},
getPermissions: () => Promise.resolve(),
getUserIdentity: async () => {
const user = await appwriteClient.account.get();
const user = await account.get();

if (user) {
return user;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
RcFile,
} from "@pankod/refine-antd";

import { appwriteClient, normalizeFile } from "utility";
import { appwriteClient, normalizeFile, storage } from "utility";
import { StoreContext } from "context/store";
import { useContext } from "react";

Expand Down Expand Up @@ -90,19 +90,17 @@ export const CreateProduct: React.FC<CreateProductProps> = ({
const rcFile = file as RcFile;

const { $id } =
await appwriteClient.storage.createFile(
await storage.createFile(
"default",
rcFile.name,
rcFile,
["role:all"],
["role:all"],
);

const url =
appwriteClient.storage.getFileView(
"default",
$id,
);
const url = storage.getFileView(
"default",
$id,
);

onSuccess?.(
{ url },
Expand Down
25 changes: 11 additions & 14 deletions examples/multi-tenancy/appwrite/src/components/product/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
RcFile,
} from "@pankod/refine-antd";

import { appwriteClient, normalizeFile } from "utility";
import { normalizeFile, storage } from "utility";

type EditProductProps = {
modalProps: ModalProps;
Expand Down Expand Up @@ -70,20 +70,17 @@ export const EditProduct: React.FC<EditProductProps> = ({
try {
const rcFile = file as RcFile;

const { $id } =
await appwriteClient.storage.createFile(
"default",
rcFile.name,
rcFile,
["role:all"],
["role:all"],
);
const { $id } = await storage.createFile(
"default",
rcFile.name,
rcFile,
["role:all"],
);

const url =
appwriteClient.storage.getFileView(
"default",
$id,
);
const url = storage.getFileView(
"default",
$id,
);

onSuccess?.({ url }, new XMLHttpRequest());
} catch (error) {
Expand Down
18 changes: 17 additions & 1 deletion examples/multi-tenancy/appwrite/src/components/sider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import {
ITreeMenu,
CanAccess,
useRouterContext,
useLogout,
useIsExistAuthentication,
useMenu,
} from "@pankod/refine-core";
import { AntdLayout, Menu, useMenu, Grid, Icons } from "@pankod/refine-antd";
import { AntdLayout, Menu, Grid, Icons } from "@pankod/refine-antd";
import { antLayoutSider, antLayoutSiderMobile } from "./styles";

import { StoreSelect } from "components/select";
Expand All @@ -17,6 +20,8 @@ export const CustomSider: React.FC = () => {
const { SubMenu } = Menu;
const { menuItems, selectedKey } = useMenu();
const breakpoint = Grid.useBreakpoint();
const { mutate: mutateLogout } = useLogout();
const isExistAuthentication = useIsExistAuthentication();

const isMobile =
typeof breakpoint.lg === "undefined" ? false : !breakpoint.lg;
Expand Down Expand Up @@ -66,6 +71,16 @@ export const CustomSider: React.FC = () => {
});
};

const logout = isExistAuthentication && (
<Menu.Item
key="logout"
onClick={() => mutateLogout()}
icon={<Icons.LogoutOutlined />}
>
Logout
</Menu.Item>
);

return (
<AntdLayout.Sider
collapsible
Expand All @@ -89,6 +104,7 @@ export const CustomSider: React.FC = () => {
<StoreSelect />
</Menu.Item>
{renderTreeView(menuItems, selectedKey)}
{logout}
</Menu>
</AntdLayout.Sider>
);
Expand Down
7 changes: 5 additions & 2 deletions examples/multi-tenancy/appwrite/src/utility/appwriteClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Appwrite } from "@pankod/refine-appwrite";
import { Account, Appwrite, Storage } from "@pankod/refine-appwrite";

const APPWRITE_URL = "https://refine.appwrite.org/v1";
const APPWRITE_PROJECT = "61caf74beffc8";
Expand All @@ -7,4 +7,7 @@ const appwriteClient = new Appwrite();

appwriteClient.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT);

export { appwriteClient };
const account = new Account(appwriteClient);
const storage = new Storage(appwriteClient);

export { appwriteClient, account, storage };
19 changes: 17 additions & 2 deletions packages/inferencer/src/create-inferencer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useResource } from "@pankod/refine-core";

import {
CreateInferencer,
InferencerComponentProps,
InferencerResultComponent,
InferField,
} from "@/types";
Expand Down Expand Up @@ -54,9 +55,11 @@ export const createInferencer: CreateInferencer = ({

const Inferencer = ({
resourceName,
fieldTransformer,
id,
}: {
resourceName?: string;
fieldTransformer?: InferencerComponentProps["fieldTransformer"];
id?: string | number;
}) => {
const { resource, resources } = useResource({
Expand All @@ -83,17 +86,27 @@ export const createInferencer: CreateInferencer = ({
})
.filter(Boolean);

return transform(
const transformed = transform(
inferred as InferField[],
resources,
resource,
record,
infer,
);

const customTransformedFields = fieldTransformer
? transformed.flatMap((field) => {
const result = fieldTransformer(field);

return result ? [result] : [];
})
: transformed;

return customTransformedFields;
}

return [];
}, [record, resources, resource]);
}, [record, resources, resource, fieldTransformer]);

const {
fields: results,
Expand Down Expand Up @@ -155,10 +168,12 @@ export const createInferencer: CreateInferencer = ({
const InferencerComponent: InferencerResultComponent = ({
name,
resource,
fieldTransformer,
id,
}) => {
return (
<Inferencer
fieldTransformer={fieldTransformer}
resourceName={resource ?? name}
key={resource ?? name}
id={id}
Expand Down
65 changes: 65 additions & 0 deletions packages/inferencer/src/field-transformers/basic-to-relation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { FieldTransformer, InferField } from "@/types";

export const basicToRelation: FieldTransformer = (
fields,
resources,
resource,
record,
) => {
const mapped: Array<InferField> = fields.map((field) => {
if (
!field.relation &&
(field.type === "text" ||
field.type === "richtext" ||
field.type === "number") &&
!field.canRelation
) {
// check if value is a valid id (regex)
// if multiple, check value by value
// take accessor into account (should be single only)
// valid id should be a valid uuid (meaning, lowercase alphanumeric with dashes)
const validUUIdRegex = /^[a-z0-9-]+$/;

const isValidUUID = (value: unknown) => {
return validUUIdRegex.test(`${value}`);
};

const isNotSelf = field.key.toLowerCase() !== "id";

const singleOrNoAccessor =
!field.accessor || typeof field.accessor === "string";

// in case of multiple accessors, we can't infer a relation
// or if the field is the id field
if (!singleOrNoAccessor || !isNotSelf) {
return field;
}

const valuesToCheck = field.multiple
? (record[field.key] as unknown[])
: [record[field.key]];

const allValid = valuesToCheck.every((value) => {
return isValidUUID(
field.accessor
? (value as Record<string, unknown>)[
field.accessor as string
]
: value,
);
});

if (allValid) {
return {
...field,
canRelation: true,
};
}

return field;
}
return field;
});

return mapped;
};
2 changes: 2 additions & 0 deletions packages/inferencer/src/field-transformers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { basicToRelation } from "./basic-to-relation";
import { imageByKey } from "./image-by-key";
import { relationByResource } from "./relation-by-resource";
import { relationToFieldable } from "./relation-to-fieldable";
Expand All @@ -6,4 +7,5 @@ export const defaultTransformers = [
imageByKey,
relationByResource,
relationToFieldable,
basicToRelation,
];
Loading

0 comments on commit 15402d3

Please sign in to comment.