Merged in feature/graphql-codegen-2023-08-08 (pull request #130)

Feature/graphql codegen 2023 08 08

Approved-by: Lorenz Padberg
This commit is contained in:
Ramon Wenger 2023-08-10 15:27:49 +00:00
commit ec7a4c9900
21 changed files with 17058 additions and 261 deletions

8
.graphqlrc Normal file
View File

@ -0,0 +1,8 @@
projects:
config:
schema: ./server/schema.graphql
includes: ./client/src/graphql/**
excludes: ./client/src/graphql/gql/public-client/**
public:
schema: ./server/schema-public.graphql
includes: ./client/src/graphql/gql/public-client/*.gql

17
client/codegen.ts Normal file
View File

@ -0,0 +1,17 @@
import { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
schema: ['../server/schema.graphql', './local.graphql'],
documents: ['src/**/*.vue'],
generates: {
'src/__generated__/': {
preset: 'client',
plugins: [],
presetConfig: {
useTypeImports: true
},
},
},
ignoreNoDocuments: true
};
export default config;

View File

@ -28,7 +28,7 @@ describe('Assignments', () => {
StudentSubmissions: { StudentSubmissions: {
studentSubmission, studentSubmission,
}, },
UpdateSubmissionFeedback({ input: { submissionFeedback } }) { UpdateSubmissionFeedbackWithText({ input: { submissionFeedback } }) {
return { return {
updateSubmissionFeedback: { updateSubmissionFeedback: {
successful: true, successful: true,

View File

@ -152,6 +152,11 @@ describe('Snapshot', () => {
slug: module.slug, slug: module.slug,
}, },
}, },
ModuleTitleQuery: {
module: {
title: module.title,
},
},
ModuleSnapshotsQuery: { ModuleSnapshotsQuery: {
module: { module: {
...module, ...module,

3
client/local.graphql Normal file
View File

@ -0,0 +1,3 @@
type ModuleNode {
inEditMode: Boolean!
}

14584
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,9 @@
"cypress:parallel:run": "cy2 run --parallel --record --config-file cypress.frontend.json --ci-build-id ", "cypress:parallel:run": "cy2 run --parallel --record --config-file cypress.frontend.json --ci-build-id ",
"currents": "cypress-cloud run --parallel --record --config-file cypress.frontend.ts", "currents": "cypress-cloud run --parallel --record --config-file cypress.frontend.ts",
"prettier": "prettier . --write", "prettier": "prettier . --write",
"prettier:check": "prettier . --check" "prettier:check": "prettier . --check",
"codegen": "graphql-codegen",
"codegen-watch": "graphql-codegen -w"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.5.10", "@apollo/client": "^3.5.10",
@ -41,6 +43,8 @@
"@babel/preset-stage-2": "^7.0.0", "@babel/preset-stage-2": "^7.0.0",
"@babel/preset-typescript": "^7.16.7", "@babel/preset-typescript": "^7.16.7",
"@babel/runtime": "^7.5.4", "@babel/runtime": "^7.5.4",
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/client-preset": "^4.1.0",
"@graphql-tools/jest-transform": "^1.2.2", "@graphql-tools/jest-transform": "^1.2.2",
"@graphql-tools/mock": "^8.6.5", "@graphql-tools/mock": "^8.6.5",
"@graphql-tools/schema": "^8.3.7", "@graphql-tools/schema": "^8.3.7",

View File

@ -0,0 +1,66 @@
import { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core';
import { FragmentDefinitionNode } from 'graphql';
import { Incremental } from './graphql';
export type FragmentType<TDocumentType extends DocumentTypeDecoration<any, any>> = TDocumentType extends DocumentTypeDecoration<
infer TType,
any
>
? [TType] extends [{ ' $fragmentName'?: infer TKey }]
? TKey extends string
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
: never
: never
: never;
// return non-nullable if `fragmentType` is non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
): TType;
// return nullable if `fragmentType` is nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null | undefined
): TType | null | undefined;
// return array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
): ReadonlyArray<TType>;
// return array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): ReadonlyArray<TType> | null | undefined;
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): TType | ReadonlyArray<TType> | null | undefined {
return fragmentType as any;
}
export function makeFragmentData<
F extends DocumentTypeDecoration<any, any>,
FT extends ResultOf<F>
>(data: FT, _fragment: F): FragmentType<F> {
return data as FragmentType<F>;
}
export function isFragmentReady<TQuery, TFrag>(
queryNode: DocumentTypeDecoration<TQuery, any>,
fragmentNode: TypedDocumentNode<TFrag>,
data: FragmentType<TypedDocumentNode<Incremental<TFrag>, any>> | null | undefined
): data is FragmentType<typeof fragmentNode> {
const deferredFields = (queryNode as { __meta__?: { deferredFields: Record<string, (keyof TFrag)[]> } }).__meta__
?.deferredFields;
if (!deferredFields) return true;
const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined;
const fragName = fragDef?.name?.value;
const fields = (fragName && deferredFields[fragName]) || [];
return fields.length > 0 && fields.every(field => data && field in data);
}

82
client/src/__generated__/gql.ts generated Normal file
View File

@ -0,0 +1,82 @@
/* eslint-disable */
import * as types from './graphql';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
/**
* Map of all GraphQL operations in the project.
*
* This map has several performance disadvantages:
* 1. It is not tree-shakeable, so it will include all operations in the project.
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
* 3. It does not support dead code elimination, so it will add unused operations.
*
* Therefore it is highly recommended to use the babel or swc plugin for production.
*/
const documents = {
"\n query ReadOnlyQuery {\n me {\n readOnly\n selectedClass {\n readOnly\n }\n }\n }\n ": types.ReadOnlyQueryDocument,
"\n query ModuleTitleQuery($slug: String) {\n module(slug: $slug) {\n title\n }\n }\n ": types.ModuleTitleQueryDocument,
"\n fragment SnapshotListItem on SnapshotNode {\n id\n title\n created\n mine\n shared\n creator\n }\n": types.SnapshotListItemFragmentDoc,
"\n fragment SnapshotTitle on SnapshotNode {\n title\n }\n": types.SnapshotTitleFragmentDoc,
"\n query ModuleEditModeQuery($slug: String) {\n module(slug: $slug) {\n inEditMode @client\n slug\n }\n }\n": types.ModuleEditModeQueryDocument,
"\n query ChapterQuery($id: ID!) {\n chapter(id: $id) {\n path\n }\n }\n ": types.ChapterQueryDocument,
"\n query ContentBlockQuery($id: ID!) {\n contentBlock(id: $id) {\n path\n }\n }\n ": types.ContentBlockQueryDocument,
"\n query ModuleSnapshotsQuery($slug: String!) {\n module(slug: $slug) {\n id\n title\n metaTitle\n slug\n topic {\n title\n }\n snapshots {\n ...SnapshotListItem\n }\n }\n }\n ": types.ModuleSnapshotsQueryDocument,
"\n query ModuleSolutions($slug: String) {\n module(slug: $slug) {\n solutionsEnabled\n slug\n }\n }\n": types.ModuleSolutionsDocument,
};
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*
*
* @example
* ```ts
* const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
* ```
*
* The query argument is unknown!
* Please regenerate the types.
*/
export function graphql(source: string): unknown;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ReadOnlyQuery {\n me {\n readOnly\n selectedClass {\n readOnly\n }\n }\n }\n "): (typeof documents)["\n query ReadOnlyQuery {\n me {\n readOnly\n selectedClass {\n readOnly\n }\n }\n }\n "];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ModuleTitleQuery($slug: String) {\n module(slug: $slug) {\n title\n }\n }\n "): (typeof documents)["\n query ModuleTitleQuery($slug: String) {\n module(slug: $slug) {\n title\n }\n }\n "];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment SnapshotListItem on SnapshotNode {\n id\n title\n created\n mine\n shared\n creator\n }\n"): (typeof documents)["\n fragment SnapshotListItem on SnapshotNode {\n id\n title\n created\n mine\n shared\n creator\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment SnapshotTitle on SnapshotNode {\n title\n }\n"): (typeof documents)["\n fragment SnapshotTitle on SnapshotNode {\n title\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ModuleEditModeQuery($slug: String) {\n module(slug: $slug) {\n inEditMode @client\n slug\n }\n }\n"): (typeof documents)["\n query ModuleEditModeQuery($slug: String) {\n module(slug: $slug) {\n inEditMode @client\n slug\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ChapterQuery($id: ID!) {\n chapter(id: $id) {\n path\n }\n }\n "): (typeof documents)["\n query ChapterQuery($id: ID!) {\n chapter(id: $id) {\n path\n }\n }\n "];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ContentBlockQuery($id: ID!) {\n contentBlock(id: $id) {\n path\n }\n }\n "): (typeof documents)["\n query ContentBlockQuery($id: ID!) {\n contentBlock(id: $id) {\n path\n }\n }\n "];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ModuleSnapshotsQuery($slug: String!) {\n module(slug: $slug) {\n id\n title\n metaTitle\n slug\n topic {\n title\n }\n snapshots {\n ...SnapshotListItem\n }\n }\n }\n "): (typeof documents)["\n query ModuleSnapshotsQuery($slug: String!) {\n module(slug: $slug) {\n id\n title\n metaTitle\n slug\n topic {\n title\n }\n snapshots {\n ...SnapshotListItem\n }\n }\n }\n "];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ModuleSolutions($slug: String) {\n module(slug: $slug) {\n solutionsEnabled\n slug\n }\n }\n"): (typeof documents)["\n query ModuleSolutions($slug: String) {\n module(slug: $slug) {\n solutionsEnabled\n slug\n }\n }\n"];
export function graphql(source: string) {
return (documents as any)[source] ?? {};
}
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never;

2334
client/src/__generated__/graphql.ts generated Normal file

File diff suppressed because it is too large Load Diff

2
client/src/__generated__/index.ts generated Normal file
View File

@ -0,0 +1,2 @@
export * from "./fragment-masking";
export * from "./gql";

View File

@ -48,7 +48,7 @@ export default {
apollo: { apollo: {
me: { me: {
query: gql` query: gql`
query { query ReadOnlyQuery {
me { me {
readOnly readOnly
selectedClass { selectedClass {

View File

@ -9,9 +9,26 @@
<script setup lang="ts"> <script setup lang="ts">
import BackLink from '@/components/BackLink.vue'; import BackLink from '@/components/BackLink.vue';
import { getModule } from '@/graphql/queries'; import { useQuery } from '@vue/apollo-composable';
import { useRoute } from 'vue-router';
import { computed } from 'vue';
import { graphql } from '@/__generated__';
const { module } = getModule(); const route = useRoute();
const { result } = useQuery(
graphql(`
query ModuleTitleQuery($slug: String) {
module(slug: $slug) {
title
}
}
`),
{
slug: route.params.slug as string,
}
);
const module = computed(() => result.value?.module || { title: '' });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -51,11 +51,23 @@ import SHARE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/share.gql';
import UPDATE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/update.gql'; import UPDATE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/update.gql';
import DELETE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/delete.gql'; import DELETE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/delete.gql';
import SNAPSHOTS_QUERY from 'gql/queries/moduleSnapshots.gql'; import SNAPSHOTS_QUERY from 'gql/queries/moduleSnapshots.gql';
import gql from 'graphql-tag';
import PenIcon from '@/components/icons/PenIcon.vue'; import PenIcon from '@/components/icons/PenIcon.vue';
import TrashIcon from '@/components/icons/TrashIcon.vue'; import TrashIcon from '@/components/icons/TrashIcon.vue';
import { removeAtIndex } from '@/graphql/immutable-operations'; import { removeAtIndex } from '@/graphql/immutable-operations';
import { matomoTrackEvent } from '@/helpers/matomo-client'; import { matomoTrackEvent } from '@/helpers/matomo-client';
import { graphql } from '@/__generated__';
export const SnapshotListItemFragment = graphql(`
fragment SnapshotListItem on SnapshotNode {
shared
}
`);
export const SnapshotTitleListItem = graphql(`
fragment SnapshotTitle on SnapshotNode {
title
}
`);
export default { export default {
props: { props: {
@ -113,11 +125,7 @@ export default {
const { id, title } = snapshot; const { id, title } = snapshot;
store.writeFragment({ store.writeFragment({
id, id,
fragment: gql` fragment: SnapshotTitleListItem,
fragment SnapshotFragment on SnapshotNode {
title
}
`,
data: { data: {
title, title,
__typename: 'SnapshotNode', __typename: 'SnapshotNode',
@ -206,11 +214,7 @@ export default {
) { ) {
store.writeFragment({ store.writeFragment({
id, id,
fragment: gql` fragment: SnapshotListItemFragment,
fragment SnapshotFragment on SnapshotNode {
shared
}
`,
data: { data: {
shared, shared,
__typename: 'SnapshotNode', __typename: 'SnapshotNode',

View File

@ -1,4 +1,4 @@
mutation UpdateSubmissionFeedback($input: UpdateSubmissionFeedbackInput!) { mutation UpdateSubmissionFeedbackWithText($input: UpdateSubmissionFeedbackInput!) {
updateSubmissionFeedback(input: $input) { updateSubmissionFeedback(input: $input) {
successful successful
updatedSubmissionFeedback { updatedSubmissionFeedback {

View File

@ -1,5 +1,5 @@
#import "../fragments/instrumentParts.gql" #import "../fragments/instrumentParts.gql"
query InstrumentQuery($id: ID!) { query InstrumentQueryById($id: ID!) {
instrument(id: $id) { instrument(id: $id) {
...InstrumentParts ...InstrumentParts
} }

View File

@ -1,5 +1,5 @@
#import "../fragments/roomEntryParts.gql" #import "../fragments/roomEntryParts.gql"
query RoomEntryQuery($id: ID!) { query RoomEntryByIdQuery($id: ID!) {
roomEntry(id: $id) { roomEntry(id: $id) {
...RoomEntryParts ...RoomEntryParts
} }

View File

@ -3,6 +3,7 @@ import ME_QUERY from './gql/queries/meQuery.gql';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { computed } from 'vue'; import { computed } from 'vue';
import { useQuery } from '@vue/apollo-composable'; import { useQuery } from '@vue/apollo-composable';
import { graphql } from '@/__generated__/gql';
export function moduleQuery() { export function moduleQuery() {
return { return {
@ -15,9 +16,10 @@ export function moduleQuery() {
const getModule = () => { const getModule = () => {
const route = useRoute(); const route = useRoute();
const { result } = useQuery(MODULE_DETAILS_QUERY, { const query = graphql(MODULE_DETAILS_QUERY, {
slug: route.params.slug, slug: route.params.slug
}); });
const { result } = useQuery(query);
const module = computed(() => result.value?.module || {}); const module = computed(() => result.value?.module || {});
return { module }; return { module };

View File

@ -12,7 +12,6 @@ import ModuleSubNavigation from '@/components/modules/ModuleSubNavigation.vue';
import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql'; import MODULE_DETAILS_QUERY from '@/graphql/gql/queries/modules/moduleDetailsQuery.gql';
import { matomoTrackPageView } from '@/helpers/matomo-client'; import { matomoTrackPageView } from '@/helpers/matomo-client';
export default { export default {
components: { components: {
ModuleNavigation, ModuleNavigation,

View File

@ -22,52 +22,52 @@
</div> </div>
</template> </template>
<script> <script setup lang="ts">
import { computed, ref } from 'vue';
import { useQuery } from '@vue/apollo-composable';
import SnapshotListItem from '@/components/modules/SnapshotListItem.vue'; import SnapshotListItem from '@/components/modules/SnapshotListItem.vue';
import MODULE_SNAPSHOTS_QUERY from '@/graphql/gql/queries/moduleSnapshots.gql';
import SnapshotTeamMenu from '@/components/modules/SnapshotTeamMenu.vue'; import SnapshotTeamMenu from '@/components/modules/SnapshotTeamMenu.vue';
import { useRoute } from 'vue-router';
import { graphql } from '@/__generated__';
const defaultModule = { topic: {}, snapshots: [] }; const defaultModule = { topic: {}, snapshots: [] };
export default { const selectedLink = ref('mine');
components: {
SnapshotTeamMenu,
SnapshotListItem,
},
data() {
return {
module: defaultModule,
selectedLink: 'mine',
};
},
computed: { const route = useRoute();
snapshots() {
if (this.selectedLink === 'mine') { const { result } = useQuery(
return this.module.snapshots.filter((snapshot) => snapshot.mine); graphql(`
} else { query ModuleSnapshotsQuery($slug: String!) {
return this.module.snapshots.filter((snapshot) => snapshot.shared); module(slug: $slug) {
id
title
metaTitle
slug
topic {
title
}
snapshots {
...SnapshotListItem
}
} }
}, }
}, `),
{
slug: route.params.slug as string,
}
);
apollo: { const module = computed(() => result.value?.module || defaultModule);
module: {
query: MODULE_SNAPSHOTS_QUERY, const snapshots = computed(() => {
variables() { if (selectedLink.value === 'mine') {
return { return module.value.snapshots.filter((snapshot) => snapshot.mine);
slug: this.$route.params.slug, } else {
}; return module.value.snapshots.filter((snapshot) => snapshot.shared);
}, }
error(error, vm, key, type, options) { });
console.log(error, vm, key, type, options);
},
update(data) {
return data.module || defaultModule;
},
},
},
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -1,72 +0,0 @@
<template>
<div>
<button @click="makeQuery">Normal Query</button>
<pre>
{{ module }}
</pre>
</div>
</template>
<script>
import gql from 'graphql-tag';
// import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql';
// import MODULE_PARTS from '@/graphql/gql/fragments/moduleParts.gql';
const moduleParts = gql`
fragment ModuleParts on ModuleNode {
id
title
}
`;
const moduleQuery = gql`
${moduleParts}
query ModuleQuery {
module(slug: "lohn-und-budget") {
...ModuleParts
}
}
`;
const moduleAndObjectivesQuery = gql`
${moduleParts}
query ModuleQuery {
module(slug: "lohn-und-budget") {
...ModuleParts
objectiveGroups {
edges {
node {
id
}
}
}
}
}
`;
export default {
data() {
return {
objectiveGroups: {},
module: {},
};
},
methods: {
makeQuery() {
console.log('click');
this.$apollo.query({ query: moduleQuery });
},
},
apollo: {
// me: ME_QUERY
// objectiveGroups: gql,
module: moduleAndObjectivesQuery,
},
};
</script>
<style scoped lang="scss">
@import 'styles/helpers';
</style>