Skip to content

Commit e578941

Browse files
authored
fix: format identifier values on save (#484)
1 parent fc9abda commit e578941

File tree

7 files changed

+193
-16
lines changed

7 files changed

+193
-16
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
steps:
1212
- name: Checkout
13-
uses: actions/checkout@v2
13+
uses: actions/checkout@v3
1414
- name: Setup Node
1515
uses: actions/setup-node@v3
1616
with:

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 3.4.2
4+
5+
* Format identifier values on save (needed when not modified in the form)
6+
37
## 3.4.1
48

59
* Fix empty array in form would trigger a file upload

src/EditGuesser.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,42 @@ const dataProvider: ApiPlatformAdminDataProvider = {
5959
};
6060

6161
describe('<EditGuesser />', () => {
62+
test('renders default fields', async () => {
63+
render(
64+
<AdminContext dataProvider={dataProvider}>
65+
<SchemaAnalyzerContext.Provider value={hydraSchemaAnalyzer}>
66+
<EditGuesser resource="users" id="/users/123" />
67+
</SchemaAnalyzerContext.Provider>
68+
</AdminContext>,
69+
);
70+
71+
await waitFor(() => {
72+
expect(screen.queryAllByRole('tab')).toHaveLength(0);
73+
expect(screen.queryByText('resources.users.fields.id')).toBeVisible();
74+
expect(screen.queryByLabelText('resources.users.fields.id')).toHaveValue(
75+
123,
76+
);
77+
expect(screen.queryByText('resources.users.fields.fieldA')).toBeVisible();
78+
expect(
79+
screen.queryByLabelText('resources.users.fields.fieldA *'),
80+
).toHaveValue('fieldA value');
81+
expect(screen.queryByText('resources.users.fields.fieldB')).toBeVisible();
82+
expect(
83+
screen.queryByLabelText('resources.users.fields.fieldB *'),
84+
).toHaveValue('fieldB value');
85+
expect(
86+
screen.queryByText('resources.users.fields.deprecatedField'),
87+
).not.toBeInTheDocument();
88+
expect(
89+
screen.queryByText('resources.users.fields.body'),
90+
).not.toBeInTheDocument();
91+
expect(screen.queryByText('resources.users.fields.title')).toBeVisible();
92+
expect(
93+
screen.queryByLabelText('resources.users.fields.title'),
94+
).toHaveValue('Title');
95+
});
96+
});
97+
6298
test('renders with custom fields', async () => {
6399
render(
64100
<AdminContext dataProvider={dataProvider}>

src/EditGuesser.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { Field, Resource } from '@api-platform/api-doc-parser';
1818

1919
import InputGuesser from './InputGuesser.js';
2020
import Introspecter from './Introspecter.js';
21+
import getIdentifierValue from './getIdentifierValue.js';
2122
import useMercureSubscription from './useMercureSubscription.js';
2223
import useDisplayOverrideCode from './useDisplayOverrideCode.js';
2324
import type {
@@ -102,6 +103,19 @@ export const IntrospectedEditGuesser = ({
102103
if (transform) {
103104
data = transform(values);
104105
}
106+
// Identifiers need to be formatted in case they have not been modified in the form.
107+
Object.entries(values).forEach(([key, value]) => {
108+
const identifierValue = getIdentifierValue(
109+
schemaAnalyzer,
110+
resource,
111+
fields,
112+
key,
113+
value,
114+
);
115+
if (identifierValue !== value) {
116+
data[key] = identifierValue;
117+
}
118+
});
105119
try {
106120
const response = await update(
107121
resource,
@@ -157,16 +171,17 @@ export const IntrospectedEditGuesser = ({
157171
}
158172
},
159173
[
160-
update,
174+
fields,
161175
hasFileField,
162-
resource,
163176
id,
164177
mutationOptions,
165178
notify,
166179
redirect,
167180
redirectTo,
181+
resource,
168182
schemaAnalyzer,
169183
transform,
184+
update,
170185
],
171186
);
172187

src/InputGuesser.tsx

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
} from 'react-admin';
3030
import isPlainObject from 'lodash.isplainobject';
3131
import Introspecter from './Introspecter.js';
32+
import getIdentifierValue, { isIdentifier } from './getIdentifierValue.js';
3233
import type {
3334
InputGuesserProps,
3435
IntrospectedInputGuesserProps,
@@ -126,19 +127,15 @@ export const IntrospectedInputGuesser = ({
126127
);
127128
}
128129

129-
if (['integer_id', 'id'].includes(fieldType) || field.name === 'id') {
130-
const prefix = `/${props.resource}/`;
131-
132-
format = (value: string | number) => {
133-
if (typeof value === 'string' && value.indexOf(prefix) === 0) {
134-
const id = value.substring(prefix.length);
135-
if (['integer_id', 'integer'].includes(fieldType)) {
136-
return parseInt(id, 10);
137-
}
138-
return id;
139-
}
140-
return value;
141-
};
130+
if (isIdentifier(field, fieldType)) {
131+
format = (value: string | number) =>
132+
getIdentifierValue(
133+
schemaAnalyzer,
134+
props.resource,
135+
fields,
136+
field.name,
137+
value,
138+
);
142139
}
143140

144141
const formatEmbedded = (value: string | object | null) => {

src/getIdentifierValue.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Field } from '@api-platform/api-doc-parser';
2+
import getIdentifierValue from './getIdentifierValue.js';
3+
import {
4+
getFiltersParametersFromSchema,
5+
getOrderParametersFromSchema,
6+
} from './schemaAnalyzer.js';
7+
import type { SchemaAnalyzer } from './types.js';
8+
9+
const schemaAnalyzer: SchemaAnalyzer = {
10+
getFiltersParametersFromSchema,
11+
getOrderParametersFromSchema,
12+
getFieldNameFromSchema: () => 'fieldName',
13+
getSubmissionErrors: () => null,
14+
getFieldType: (field) => {
15+
if (field.name === 'stringId') {
16+
return 'id';
17+
}
18+
if (field.name === 'intId') {
19+
return 'integer_id';
20+
}
21+
22+
return 'text';
23+
},
24+
};
25+
26+
test('Get identifier from a non string value', () => {
27+
expect(getIdentifierValue(schemaAnalyzer, 'foo', [], 'description', 46)).toBe(
28+
46,
29+
);
30+
});
31+
32+
test('Get identifier from a non prefixed value', () => {
33+
expect(
34+
getIdentifierValue(schemaAnalyzer, 'foo', [], 'description', 'lorem'),
35+
).toBe('lorem');
36+
});
37+
38+
test('Get identifier from a not found field', () => {
39+
expect(
40+
getIdentifierValue(schemaAnalyzer, 'foo', [], 'id', '/foo/fooId'),
41+
).toBe('/foo/fooId');
42+
});
43+
44+
test('Get identifier from a non identifier field', () => {
45+
expect(
46+
getIdentifierValue(
47+
schemaAnalyzer,
48+
'foo',
49+
[new Field('description')],
50+
'description',
51+
'/foo/fooId',
52+
),
53+
).toBe('/foo/fooId');
54+
});
55+
56+
test('Get identifier from an identifier field', () => {
57+
expect(
58+
getIdentifierValue(
59+
schemaAnalyzer,
60+
'foo',
61+
[new Field('stringId')],
62+
'stringId',
63+
'/foo/fooId',
64+
),
65+
).toBe('fooId');
66+
});
67+
68+
test('Get identifier from an "id" field', () => {
69+
expect(
70+
getIdentifierValue(
71+
schemaAnalyzer,
72+
'foo',
73+
[new Field('id')],
74+
'id',
75+
'/foo/fooId',
76+
),
77+
).toBe('fooId');
78+
});
79+
80+
test('Get identifier from an integer identifier field', () => {
81+
expect(
82+
getIdentifierValue(
83+
schemaAnalyzer,
84+
'foo',
85+
[new Field('intId')],
86+
'intId',
87+
'/foo/76',
88+
),
89+
).toBe(76);
90+
});

src/getIdentifierValue.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Field } from '@api-platform/api-doc-parser';
2+
import type { SchemaAnalyzer } from './types.js';
3+
4+
export const isIdentifier = (field: Field, fieldType: string) =>
5+
['integer_id', 'id'].includes(fieldType) || field.name === 'id';
6+
7+
const getIdentifierValue = (
8+
schemaAnalyzer: SchemaAnalyzer,
9+
resource: string,
10+
fields: Field[],
11+
fieldName: string,
12+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13+
value: any,
14+
) => {
15+
const prefix = `/${resource}/`;
16+
17+
if (typeof value === 'string' && value.indexOf(prefix) === 0) {
18+
const field = fields.find((fieldObj) => fieldObj.name === fieldName);
19+
if (!field) {
20+
return value;
21+
}
22+
const fieldType = schemaAnalyzer.getFieldType(field);
23+
if (isIdentifier(field, fieldType)) {
24+
const id = value.substring(prefix.length);
25+
if (['integer_id', 'integer'].includes(fieldType)) {
26+
return parseInt(id, 10);
27+
}
28+
return id;
29+
}
30+
}
31+
32+
return value;
33+
};
34+
35+
export default getIdentifierValue;

0 commit comments

Comments
 (0)