-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathProjectListContext.ts
More file actions
131 lines (112 loc) · 3.67 KB
/
ProjectListContext.ts
File metadata and controls
131 lines (112 loc) · 3.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import React, { useContext, useState } from "react";
import { useQuery } from "react-query";
import { z } from "zod";
const ProjectEntrySchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().optional(),
registryPath: z.string(),
});
const ProjectsListSchema = z.object({
default: z.string().optional(),
projects: z.array(ProjectEntrySchema),
});
type ProjectsListType = z.infer<typeof ProjectsListSchema>;
interface ProjectsListContextInterface {
projectsListPromise: Promise<any>;
isCustom: boolean;
}
const ProjectListContext = React.createContext<
ProjectsListContextInterface | undefined
>(undefined);
class ProjectListError extends Error {
constructor(message?: string | undefined) {
super(message);
this.name = "FeastProjectListError";
}
}
const projectListExampleString = `
\`\`\`json
{
"projects": [
{
"name": "Credit Score Project",
"description": "Project for credit scoring team and associated models.",
"id": "credit_score_project",
"registryPath": "/registry.json"
}
]
}
\`\`\`
`;
const anticipatedProjectListErrors = (
err: Error,
isCustomProjectList: boolean,
) => {
const isSyntaxError = err.stack?.indexOf("SyntaxError") === 0;
// Straight up not a JSON
if (isSyntaxError) {
const message = `Unable to properly parse Project List JSON. Check that your project list is formatted properly.`;
return new ProjectListError(message);
}
// Some sort of 404
const isFailedToFetch = err.message.indexOf("Failed to fetch") > -1;
if (isFailedToFetch) {
const followUpMessage = isCustomProjectList
? "Check that the promise in your Feast UI configuration is set up properly."
: "Did you create a `project-list.json` file in the `/public/` directory? e.g." +
projectListExampleString;
const message = "Failed to fetch Project List JSON. " + followUpMessage;
return new ProjectListError(message);
}
return null;
};
const useLoadProjectsList = () => {
const projectListPromise = useContext(ProjectListContext);
// Use setState to surface errors in Error Boundaries
// https://github.com/facebook/react/issues/14981#issuecomment-468460187
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, setQueryError] = useState(undefined);
return useQuery(
"feast-projects-list",
() => {
return projectListPromise?.projectsListPromise
.catch((e) => {
const anticipatedError = anticipatedProjectListErrors(
e,
projectListPromise.isCustom,
);
setQueryError(() => {
if (anticipatedError) {
throw anticipatedError;
} else {
throw new Error(e);
}
});
})
.then<ProjectsListType>((json) => {
try {
const configs = ProjectsListSchema.parse(json);
return configs;
} catch (e) {
// If a json object is returned, but
// does not adhere to our anticipated
// format.
setQueryError(() => {
throw new ProjectListError(
`Error parsing project list JSON. JSON Object does not match expected type for a Feast project list. A project list JSON file should look like
${projectListExampleString}
Zod (our parser) returned the following: \n\n${e}`,
);
});
throw new Error("Zod Error");
}
});
},
{
enabled: !!projectListPromise?.projectsListPromise,
},
);
};
export { ProjectListContext, ProjectsListSchema, useLoadProjectsList };
export type { ProjectsListType, ProjectsListContextInterface };