This PR introduces many improvements over the new profiling story
feature, with new tests and some refactor with main :
- Added use-context-selector for getting value faster in display fields
and created useRecordFieldValue() hook and RecordValueSetterEffect to
synchronize states
- Added performance test command in CI
- Refactored ExpandableList drill-downs with FieldFocusContext
- Refactored field button icon logic into getFieldButtonIcon util
- Added RelationFieldDisplay perf story
- Added RecordTableCell perf story
- First split test of useField.. hook with useRelationFieldDisplay()
- Fixed problem with set cell soft focus
- Isolated logic between display / soft focus and edit mode in the
related components to optimize performances for display mode.
- Added warmupRound config for performance story decorator
- Added variance in test reporting
This PR introduces a Profiling feature for our story book tests.
It also implements a new CI job : front-sb-test-performance, that only
runs stories suffixed with `.perf.stories.tsx`
## How it works
It allows to wrap any component into an array of React Profiler
components that will run tests many times to have the most replicable
average render time possible.
It is simply used by calling the new `getProfilingStory` util.
Internally it creates a defined number of tests, separated by an
arbitrary waiting time to allow the CPU to give more stable results.
It will do 3 warm-up and 3 finishing runs of tests because the first and
last renders are always a bit erratic, so we want to measure only the
runs in-between.
On the UI side it gives a table of results :
<img width="515" alt="image"
src="https://github.com/twentyhq/twenty/assets/26528466/273d2d91-26da-437a-890e-778cb6c1f993">
On the programmatic side, it stores the result in a div that can then be
parsed by the play fonction of storybook, to expect a defined threshold.
```tsx
play: async ({ canvasElement }) => {
await findByTestId(
canvasElement,
'profiling-session-finished',
{},
{ timeout: 60000 },
);
const profilingReport = getProfilingReportFromDocument(canvasElement);
if (!isDefined(profilingReport)) {
return;
}
const p95result = profilingReport?.total.p95;
expect(
p95result,
`Component render time is more than p95 threshold (${p95ThresholdInMs}ms)`,
).toBeLessThan(p95ThresholdInMs);
},
```
When writing to the normalized cache (record), it's crucial to use _refs
for relationships to avoid many problems. Essentially, we only deal with
level 0 and generate all fields to be comfortable with their defaults.
When writing in queries (which should be very rare, the only cases are
prefetch and the case of activities due to the nested query; I've
reduced this to a single file for activities
usePrepareFindManyActivitiesQuery 🙂), it's important to use queryFields
to avoid bugs. I've implemented them on the side of query generation and
record generation.
When doing an updateOne / createOne, etc., it's necessary to distinguish
between optimistic writing (which we actually want to do with _refs) and
the server response without refs. This allows for a clean write in the
optimistic cache without worrying about nesting (as the first point).
To simplify the whole activities part, write to the normalized cache
first. Then, base queries on it in an idempotent manner. This way,
there's no need to worry about the current page or action. The
normalized cache is up-to-date, so I update the queries. Same idea as
for optimisticEffects, actually.
Finally, I've triggered optimisticEffects rather than the manual update
of many queries.
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
* refactor: apply relation optimistic effects on record update
Related to #3509
* refactor: remove need to pass relation id field to create and update mutations
* fix: fix tests
* fix: fix SingleEntitySelect glitch
* fix: fix usePersistField tests
* fix: fix wrong import after rebase
* fix: fix several tests
* fix: fix test types