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

Allow metadata editing #137

Merged
merged 4 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 5 additions & 4 deletions kratos-admin-ui/src/components/multiline/multiline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function MultilineEdit(props: MultilineEditProps) {
<>
{data.map((value, index) => {
return (
<div key={index} style={{
<div key={[index, value].join('-')} style={{
display: "flex",
alignItems: "center",
paddingBottom: 5
Expand Down Expand Up @@ -72,10 +72,11 @@ export function MultilineEdit(props: MultilineEditProps) {
size="small"
icon={<Delete12Filled></Delete12Filled>}
onClick={(event => {
setData(data.filter((arrayvalue, arrayIndex) => {
const filtered = data.filter((arrayvalue, arrayIndex) => {
return arrayIndex !== index
}))
props.dataChanged(data)
})
setData(filtered)
props.dataChanged(filtered)
})}
></Button>
</Tooltip>
Expand Down
142 changes: 117 additions & 25 deletions kratos-admin-ui/src/components/traits/edit-traits.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button, Checkbox, Field, Select, Text } from "@fluentui/react-components"
import { Identity, IdentityApi, IdentityState } from "@ory/kratos-client"
import { useEffect, useState } from "react"
import { SchemaField, SchemaService } from "../../service/schema-service";
import { FieldKind, SchemaField, SchemaService } from "../../service/schema-service";
import { getKratosConfig } from "../../config";
import { useHistory } from "react-router-dom";
import { RenderTraitField } from "./render-field";
Expand All @@ -28,7 +28,18 @@ export interface ValueObject {
state: IdentityState
traits: IdentityTraits;
publicMetadata: MetaData;
adminMedataData: MetaData;
adminMetadata: MetaData;
}

export function mapFieldKindToValueKey(fieldKind: FieldKind) {
switch (fieldKind) {
case "trait":
return "traits";
case "metadata_public":
return "publicMetadata";
case "metadata_admin":
return "adminMetadata"
}
}

function getButtonName(modi: Modi) {
Expand All @@ -43,10 +54,10 @@ async function fillTraits(identity: Identity, schemaFields: SchemaField[]): Prom
const map = await SchemaService.getTableDetailListModelFromKratosIdentity(identity);
const traits: IdentityTraits = {}

for (const [key, value] of Object.entries(map)) {
for (const [key, value] of Object.entries(map.traits)) {
if (key !== "key") {
schemaFields.forEach(f => {
if (f.name === key) {
if (f.name === key && f.fieldKind === "trait") {
if (f.parentName) {
if (!traits[f.parentName]) {
traits[f.parentName] = {}
Expand All @@ -62,7 +73,28 @@ async function fillTraits(identity: Identity, schemaFields: SchemaField[]): Prom
return traits;
}

async function fillMetadata(identity: Identity, schemaFields: SchemaField[], metadataKind: "metadata_public" | "metadata_admin"): Promise<IdentityTraits> {
const map = await SchemaService.getTableDetailListModelFromKratosIdentity(identity);
const metadata: MetaData = {}

for (const [key, value] of Object.entries(map[metadataKind])) {
if (key !== "key") {
schemaFields.forEach(f => {
if (f.name === key && f.fieldKind === metadataKind) {
if (f.parentName) {
if (!metadata[f.parentName]) {
metadata[f.parentName] = {}
}
metadata[f.parentName][f.name] = value
} else {
metadata[f.name] = value
}
}
});
}
}
return metadata;
}

async function performAction(modi: Modi, values: ValueObject, identity?: Identity, schemaId?: string): Promise<import("axios").AxiosResponse<Identity>> {
const kratosConfig = await getKratosConfig();
Expand All @@ -76,8 +108,8 @@ async function performAction(modi: Modi, values: ValueObject, identity?: Identit
schema_id: identity?.schema_id!,
traits: values.traits,
state: values.state,
metadata_public: identity?.metadata_public,
metadata_admin: identity?.metadata_admin
metadata_public: values.publicMetadata,
metadata_admin: values.adminMetadata
}
})
MessageService.Instance.dispatchMessage({
Expand All @@ -94,8 +126,8 @@ async function performAction(modi: Modi, values: ValueObject, identity?: Identit
createIdentityBody: {
schema_id: schemaId!,
traits: values.traits,
metadata_admin: kratosConfig.adminConfig.basePath,
metadata_public: kratosConfig.publicConfig.basePath
metadata_public: values.publicMetadata,
metadata_admin: values.adminMetadata
}
})
MessageService.Instance.dispatchMessage({
Expand Down Expand Up @@ -128,15 +160,15 @@ export function EditTraits(props: EditTraitsProps) {
valueObject = {
state: identity.state!,
traits: await fillTraits(identity, newSchemaFields),
publicMetadata: identity.metadata_public,
adminMedataData: identity.metadata_admin
publicMetadata: await fillMetadata(identity, newSchemaFields, "metadata_public"),
adminMetadata: await fillMetadata(identity, newSchemaFields, "metadata_admin")
}
} else if (props.schema && props.modi === "new") {
newSchemaFields = SchemaService.getSchemaFields(props.schema)
valueObject = {
state: "active",
traits: {},
adminMedataData: {},
adminMetadata: {},
Comment on lines -139 to +171
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked the code, i wasnt able to create a new identity with metadata - the values just get dropped. I think we have to include the code from the lines 163 and 164.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I am not able to reproduce that - on our Kratos v1.0.0 installation, creating and updating identities with public or admin metadata works well. Could you perhaps try it with this schema? https://gist.github.com/toonalbers/d4b4fcf2472404066de47061ca37cc3c

publicMetadata: {}
}
} else {
Expand All @@ -157,6 +189,10 @@ export function EditTraits(props: EditTraitsProps) {
prepare()
})

const traits = schemaFields && schemaFields.filter((elem, _key) => elem.fieldKind === "trait")
const publicMetadata = schemaFields && schemaFields.filter((elem, _key) => elem.fieldKind === "metadata_public")
const adminMetadata = schemaFields && schemaFields.filter((elem, _key) => elem.fieldKind === "metadata_admin")

return (
<div>
<Text
Expand Down Expand Up @@ -190,7 +226,6 @@ export function EditTraits(props: EditTraitsProps) {
></Checkbox>
}


<Text
as="h2"
style={{
Expand All @@ -199,19 +234,76 @@ export function EditTraits(props: EditTraitsProps) {
marginTop: 10
}}
>Custom Traits</Text>
{schemaFields && values && schemaFields.map((elem, key) => {
return (
<div key={key}>
<RenderTraitField
schemaField={elem}
fieldValues={values}
setValues={(values) => {
setValues(values)
}}
></RenderTraitField>
</div>
)
})}
{ values && traits?.length ? (
traits.map((elem, key) => {
return (
<div key={key}>
<RenderTraitField
schemaField={elem}
fieldValues={values}
setValues={(values) => {
setValues(values)
}}
></RenderTraitField>
</div>
)
})
) : (
<p>None</p>
)}

{ publicMetadata?.length ? (
<div>
<Text
as="h2"
style={{
display: "block",
fontSize: 20,
marginTop: 10
}}
>Public Metadata</Text>
{values && publicMetadata.map((elem, key) => {
return (
<div key={key}>
<RenderTraitField
schemaField={elem}
fieldValues={values}
setValues={(values) => {
setValues(values)
}}
></RenderTraitField>
</div>
)
}) }
</div>
) : ( null ) }

{ adminMetadata?.length ? (
<div>
<Text
as="h2"
style={{
display: "block",
fontSize: 20,
marginTop: 10
}}
>Admin Metadata</Text>
{values && adminMetadata.map((elem, key) => {
return (
<div key={key}>
<RenderTraitField
schemaField={elem}
fieldValues={values}
setValues={(values) => {
setValues(values)
}}
></RenderTraitField>
</div>
)
})}
</div>
) : ( null ) }

{!errorText || <div className="alert alert-danger" style={{ marginTop: 15 }}>{errorText}</div>}
<div style={{ marginTop: 20 }}>
<div style={{ display: "flex", gap: 20, marginBottom: 15 }}>
Expand Down
56 changes: 0 additions & 56 deletions kratos-admin-ui/src/components/traits/metadata-renderer.tsx

This file was deleted.

27 changes: 17 additions & 10 deletions kratos-admin-ui/src/components/traits/render-field.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Label } from "@fluentui/react-components"
import { SchemaField, mapSchemaDataType } from "../../service/schema-service"
import { ValueObject } from "./edit-traits"
import { ValueObject, mapFieldKindToValueKey } from "./edit-traits"
import { SingleField } from "./single-field"
import { MultilineEdit } from "../multiline/multiline"

Expand All @@ -11,17 +11,23 @@ interface RenderTraitFieldProps {
}

function getDefaultValue(schemaField: SchemaField, values: ValueObject): any[] {
const fieldKindKey = mapFieldKindToValueKey(schemaField.fieldKind);
if (schemaField.type === "array") {
let value = [];
if (schemaField.parentName) {
if (values.traits[schemaField.parentName] && values.traits[schemaField.parentName][schemaField.name]) {
return values.traits[schemaField.parentName][schemaField.name];
if (values[fieldKindKey][schemaField.parentName] && values[fieldKindKey][schemaField.parentName][schemaField.name]) {
value = values[fieldKindKey][schemaField.parentName][schemaField.name];
}
} else {
if (values.traits[schemaField.name]) {
return values.traits[schemaField.name];
if (values[fieldKindKey][schemaField.name]) {
value = values[fieldKindKey][schemaField.name];
}
}
return []

if (!(value instanceof Array)) {
return [value];
}
return value;
}
throw new Error("Should not be called as non array object!")
}
Expand All @@ -45,13 +51,14 @@ export function RenderTraitField(props: RenderTraitFieldProps) {
<MultilineEdit
defaultData={getDefaultValue(props.schemaField, props.fieldValues)}
dataChanged={(data) => {
const fieldKindKey = mapFieldKindToValueKey(props.schemaField.fieldKind);
if (props.schemaField.parentName) {
if (!props.fieldValues.traits[props.schemaField.parentName]) {
props.fieldValues.traits[props.schemaField.parentName] = {}
if (!props.fieldValues[fieldKindKey][props.schemaField.parentName]) {
props.fieldValues[fieldKindKey][props.schemaField.parentName] = {}
}
props.fieldValues.traits[props.schemaField.parentName][props.schemaField.name] = data
props.fieldValues[fieldKindKey][props.schemaField.parentName][props.schemaField.name] = data
} else {
props.fieldValues.traits[props.schemaField.name] = data
props.fieldValues[fieldKindKey][props.schemaField.name] = data
}
props.setValues(props.fieldValues)
}}
Expand Down
Loading