Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "[examples] Add example Relay provider and export relay Feedback example component",
"packageName": "@nova/examples",
"email": "eloy.de.enige@gmail.com",
"dependentChangeType": "patch"
}
42 changes: 42 additions & 0 deletions packages/examples/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
# Nova examples

This package contains examples of nova components as well as related good testing approaches for them.

## Nova host integration canary

When configuring a host application, this package can be used as a light-weight canary package to test the host's Nova integration.

### GraphQL

#### Relay

- [Configure your project to have Relay.](https://relay.dev/docs/getting-started/installation-and-setup/)
- [Create a Relay environment.](https://relay.dev/docs/api-reference/relay-environment-provider/)
- Instantiate the Nova Relay example provider:

```tsx
import {
NovaExampleRelayGraphQLProvider,
NovaExampleRelayComponent,
} from "@nova/examples";
import { createRelayEnvironment } from "./myHostRelayEnvironment";

const environment = createRelayEnvironment();

const MyHostNovaIntegrationTest = () => {
return (
<NovaExampleRelayGraphQLProvider relayEnvironment={environment}>
<NovaExampleRelayComponent />
</NovaExampleRelayGraphQLProvider>
);
};
```

#### Apollo

TODO

### Eventing

TODO

### Commanding

TODO
16 changes: 15 additions & 1 deletion packages/examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-relay": "^18.0.0",
"relay-hooks": "^9.2.0",
"relay-runtime": "^18.0.0"
},
"devDependencies": {
Expand Down Expand Up @@ -57,5 +58,18 @@
"storybook": "^8.4.5",
"typescript": "^5.6.0"
},
"sideEffects": false
"sideEffects": false,
"access": "public",
"publishConfig": {
"main": "./lib/index",
"types": "./lib/index.d.ts",
"module": "./lib/index.mjs",
"exports": {
".": {
"types": "./lib/index.d.ts",
"import": "./lib/index.mjs",
"require": "./lib/index.js"
}
}
}
}
2 changes: 2 additions & 0 deletions packages/examples/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { NovaExampleRelayGraphQLProvider } from "./relay/NovaExampleRelayGraphQLProvider";
export { FeedbackContainer as NovaExampleRelayComponent } from "./relay/Feedback/FeedbackContainer";
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { NovaGraphQL } from "@nova/types";
import type RelayModernEnvironment from "relay-runtime/lib/store/RelayModernEnvironment";

import * as React from "react";
import {
useFragment,
useRefetchableFragment,
usePaginationFragment,
useSubscription,
RelayEnvironmentProvider,
} from "react-relay/hooks";
import { NovaGraphQLProvider } from "@nova/react";
import { useRelayLazyLoadQueryWithApolloAPI } from "./useRelayLazyLoadQueryWithApolloAPI";
import { useRelayMutationWithApolloAPI } from "./useRelayMutationWithApolloAPI";

const GraphQLInterface: NovaGraphQL = {
useFragment,
useRefetchableFragment,
usePaginationFragment,
useLazyLoadQuery: useRelayLazyLoadQueryWithApolloAPI,
useMutation: useRelayMutationWithApolloAPI,
useSubscription,
};

export const NovaExampleRelayGraphQLProvider: React.FC<
React.PropsWithChildren<{ relayEnvironment: RelayModernEnvironment }>
> = ({ children, relayEnvironment }) => {
return (
<RelayEnvironmentProvider environment={relayEnvironment}>
<NovaGraphQLProvider graphql={GraphQLInterface}>
{children}
</NovaGraphQLProvider>
</RelayEnvironmentProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* NOTE: Taken from TMP packages/frameworks/frameworks-relay/src/react/hooks
*
* If needing to make changes, please make them in the upstream frameworks package.
*/

import { useQuery } from "relay-hooks";
import type {
FetchPolicy,
GraphQLTaggedNode,
OperationType,
} from "relay-runtime";

/**
* Hook that wraps useQuery hook from "relay-hooks" library and returns a Nova API compatible response.
* We can't use original Relay useLazyLoadQuery, since its implementation is heavily relies on Suspense.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* We can't use original Relay useLazyLoadQuery, since its implementation is heavily relies on Suspense.
* We can't use original Relay useLazyLoadQuery, since its implementation heavily relies on Suspense.

*/
export function useRelayLazyLoadQueryWithApolloAPI<
TQuery extends OperationType,
>(
query: GraphQLTaggedNode,
variables: TQuery["variables"],
options?: { fetchPolicy?: FetchPolicy },
): { error?: Error; data?: TQuery["response"] } {
const result = useQuery(query, variables, options);
return {
...result,
error: result.error ?? undefined,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* NOTE: Taken from TMP packages/frameworks/frameworks-relay/src/react/hooks
*
* If needing to make changes, please make them in the upstream frameworks package.
*/

import { useMutation as useRelayMutation } from "react-relay/hooks";
import type { GraphQLTaggedNode, MutationParameters } from "relay-runtime";

type MutationCommitter<TMutation extends MutationParameters> = (options: {
variables: { [name: string]: unknown };
context?: { [name: string]: unknown };
optimisticResponse?: unknown | null;
onCompleted?: (response: unknown) => void;
}) => Promise<{ errors?: Error[]; data?: TMutation["response"] }>;

/**
* Hook that wraps original Relay useMutation hook and returns mutation committer,
* that returns a promise. This is required for alignment with Apollo client
* useMutation hook API for smoother migration of production code to Relay.
* @param mutation Mutation document.
* @returns Array containing mutation committer function and a flag
* indicating whether mutation is "in flight".
*/
export function useRelayMutationWithApolloAPI<
Copy link
Contributor

Choose a reason for hiding this comment

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

With this change we start having two different implementation of Nova relay based environment, the other one exists in packages\nova-react-test-utils\src\relay\nova-relay-graphql.ts. We shouldn't, it's going to be confusing as hell why certain things work in Storybook differently than in imported env from examples package.

In 1JS (and in Outlook) nova env based on relay is using suspending useLazyLoadQuery and we accepted the consequence that Nova interface is not exactly followed (as we never return error) and TBH I am now surprised Teams went in different direction. Seems we need to discuss and align on this in a forum.

For now I will approve and merge the PR, to allow you to share this with folks, but without this need I would block it until we aligned, so please follow up on it

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed, and yesterday I was reminded of this PR that I recall was attempting to align some of this #117

@sergey-stoyan Can you make sure to join a next nova sync together with @sjwilczynski so we can discuss this topic?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure, let's discuss during the next sync

TMutation extends MutationParameters,
>(mutation: GraphQLTaggedNode): [MutationCommitter<TMutation>, boolean] {
const [commitMutation, isMutationInFlight] = useRelayMutation(mutation);
return [
(options) => {
return new Promise((resolve, reject) => {
commitMutation({
variables: options.variables,
onCompleted: (response, errors) => {
resolve(
errors
? {
data: response,
errors: errors.map((e) => new Error(e.message)),
}
: { data: response },
);
},
onError: (error) => {
reject(error);
},
});
});
},
isMutationInFlight,
];
}
15 changes: 15 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1694,6 +1694,13 @@
resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.4.tgz#a04d63f4d1bf5540a41b01a921c9a7fddc3bd1ca"
integrity sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==

"@restart/hooks@^0.4.9":
version "0.4.16"
resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb"
integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==
dependencies:
dequal "^2.0.3"

"@rushstack/node-core-library@3.45.5":
version "3.45.5"
resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.45.5.tgz#00f92143cc21c3ad94fcd81ba168a40ac8cb77f2"
Expand Down Expand Up @@ -7014,6 +7021,14 @@ relay-compiler@^18.0.0:
resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-18.1.0.tgz#ae6f82a3c967d6e2bb35c4e7971acae0b6a36e47"
integrity sha512-DGWIORIij0TIys/Xd5W/iToqTv7UcN6fqCPDqwYhLnfU7ukdK8rpk7f6RCYvhyZG71e7a1b3TLGOQY8A3XE3yg==

relay-hooks@^9.2.0:
version "9.2.0"
resolved "https://registry.yarnpkg.com/relay-hooks/-/relay-hooks-9.2.0.tgz#7785b7ac8ebafb9a8d0b045a2e622668168fe1c0"
integrity sha512-6y4RjF0sGt85kxRM87eQlHFl4utBI2fXfFM7TNJ+CiYGaH7e65hTYsh65j/8B5IzGp93hevG0yVaeY/Zeapahg==
dependencies:
"@restart/hooks" "^0.4.9"
fbjs "^3.0.2"

relay-runtime@12.0.0:
version "12.0.0"
resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-12.0.0.tgz#1e039282bdb5e0c1b9a7dc7f6b9a09d4f4ff8237"
Expand Down