Skip to content

Commit fa8b27f

Browse files
cdxkerskeptrunedev
authored andcommitted
feature/bugfix: fixed redirects of shopify onboarding flow from user install.
1 parent 2ff50a5 commit fa8b27f

9 files changed

Lines changed: 167 additions & 145 deletions

File tree

clients/trieve-shopify-extension/app/auth.ts

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,73 @@ import { authenticate } from "app/shopify.server";
33
import { StrongTrieveKey, TrieveKey } from "./types";
44
import { TrieveSDK, CreateApiUserResponse } from "trieve-ts-sdk";
55
import { getTrieveBaseUrlEnv } from "./env.server";
6+
import { buildAdminApiFetcherForServer } from "./loaders/serverLoader";
7+
import { AdminApiCaller } from "./loaders";
8+
import { AppInstallData } from "./routes/app.setup";
69

710
export const validateTrieveAuth = async <S extends boolean = true>(
811
request: LoaderFunctionArgs["request"],
912
strict: S = true as S,
1013
): Promise<S extends true ? StrongTrieveKey : TrieveKey> => {
11-
let { admin, session } = await authenticate.admin(request);
14+
const setAppMetafields = async (
15+
adminApi: AdminApiCaller,
16+
trieveKey: TrieveKey,
17+
) => {
18+
const response = await adminApi<AppInstallData>(`
19+
#graphql
20+
query {
21+
currentAppInstallation {
22+
id
23+
}
24+
}
25+
`);
26+
27+
if (response.error) {
28+
throw response.error;
29+
}
30+
31+
const appId = response.data;
32+
33+
await adminApi(
34+
`#graphql
35+
mutation CreateAppDataMetafield($metafieldsSetInput: [MetafieldsSetInput!]!) {
36+
metafieldsSet(metafields: $metafieldsSetInput) {
37+
metafields {
38+
id
39+
namespace
40+
key
41+
}
42+
userErrors {
43+
field
44+
message
45+
}
46+
}
47+
}
48+
`,
49+
{
50+
variables: {
51+
metafieldsSetInput: [
52+
{
53+
namespace: "trieve",
54+
key: "dataset_id",
55+
value: trieveKey.currentDatasetId,
56+
type: "single_line_text_field",
57+
ownerId: appId.currentAppInstallation.id,
58+
},
59+
{
60+
namespace: "trieve",
61+
key: "api_key",
62+
value: trieveKey.key,
63+
type: "single_line_text_field",
64+
ownerId: appId.currentAppInstallation.id,
65+
},
66+
],
67+
},
68+
},
69+
);
70+
};
71+
72+
const { admin, session } = await authenticate.admin(request);
1273

1374
let key = await prisma.apiKey.findFirst({
1475
where: {
@@ -61,6 +122,70 @@ export const validateTrieveAuth = async <S extends boolean = true>(
61122
createdAt: new Date(),
62123
}
63124
});
125+
126+
const trieve = new TrieveSDK({
127+
baseUrl: getTrieveBaseUrlEnv(),
128+
apiKey: key.key,
129+
datasetId: key.currentDatasetId ? key.currentDatasetId : undefined,
130+
organizationId: key.organizationId,
131+
omitCredentials: true,
132+
});
133+
let datasetId = trieve.datasetId;
134+
135+
let shopDataset = await trieve
136+
.getDatasetByTrackingId(session.shop)
137+
.catch(() => {
138+
return null;
139+
});
140+
141+
const fetcher = buildAdminApiFetcherForServer(
142+
session.shop,
143+
session.accessToken!,
144+
);
145+
146+
if ((!datasetId || !shopDataset) && trieve.organizationId) {
147+
if (!shopDataset) {
148+
shopDataset = await trieve.createDataset({
149+
dataset_name: session.shop,
150+
tracking_id: session.shop,
151+
server_configuration: {
152+
SYSTEM_PROMPT:
153+
"[[personality]]\nYou are a friendly, helpful, and knowledgeable ecommerce sales associate. Your communication style is warm, patient, and enthusiastic without being pushy. You're approachable and conversational while maintaining professionalism. You balance being personable with being efficient, understanding that customers value both connection and their time. You're solution-oriented and genuinely interested in helping customers find the right products for their needs.\n\n[[goal]]\nYour primary goal is to help customers find products that genuinely meet their needs while providing an exceptional shopping experience. You aim to:\n1. Understand customer requirements through thoughtful questions\n2. Provide relevant product recommendations based on customer preferences\n3. Offer detailed, accurate information about products\n4. Address customer concerns and objections respectfully\n5. Guide customers through the purchasing process\n6. Encourage sales without being pushy or manipulative\n7. Create a positive impression that builds long-term customer loyalty\n\n[[response structure]]\n1. Begin with a warm greeting and acknowledgment of the customer's query or concern\n2. Ask clarifying questions if needed to better understand their requirements\n3. Provide concise, relevant information that directly addresses their needs\n4. Include specific product recommendations when appropriate, with brief explanations of why they might be suitable\n5. Address any potential concerns proactively\n6. Close with a helpful next step or question that moves the conversation forward\n7. Keep responses conversational yet efficient, balancing thoroughness with respect for the customer's time.\n",
154+
RAG_PROMPT:
155+
"You may use the retrieved context to help you respond. When discussing products, prioritize information from the provided product data while using your general knowledge to create helpful, natural responses. If a customer asks about products or specifications not mentioned in the context, acknowledge the limitation and offer to check for more information rather than inventing details.",
156+
},
157+
});
158+
159+
console.log("created dataset for shop");
160+
161+
key = await prisma.apiKey.update({
162+
data: {
163+
currentDatasetId: shopDataset.id,
164+
},
165+
where: {
166+
shop: `${session.shop}`,
167+
},
168+
});
169+
} else {
170+
key = await prisma.apiKey.update({
171+
data: {
172+
currentDatasetId: shopDataset.id,
173+
},
174+
where: {
175+
shop: `${session.shop}`,
176+
},
177+
});
178+
}
179+
180+
if (key.currentDatasetId && key.key && session) {
181+
setAppMetafields(fetcher, {
182+
id: key.id,
183+
key: key.key,
184+
organizationId: key.organizationId,
185+
currentDatasetId: key.currentDatasetId,
186+
} as TrieveKey);
187+
}
188+
}
64189
}
65190

66191
return {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
2+
import { validateTrieveAuth } from "app/auth";
3+
4+
export const loader = async ({ request }: LoaderFunctionArgs) => {
5+
const url = new URL(request.url);
6+
// call validateTrieveAuth here to ensure the user is authenticated
7+
// The first time this gets called it will create a new dataset,
8+
// calling it here prevents race conditions.
9+
await validateTrieveAuth(request);
10+
11+
throw redirect(`/app?${url.searchParams.toString()}`);
12+
};

clients/trieve-shopify-extension/app/routes/app._dashboard._index.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { useTrieve } from "app/context/trieveContext";
2-
import { useNavigate, useSubmit } from "@remix-run/react";
2+
import { useSubmit } from "@remix-run/react";
33
import {
44
Card,
55
Text,
6-
Badge,
76
Button,
87
SkeletonBodyText,
98
DescriptionList,
@@ -38,11 +37,6 @@ import { ActionFunctionArgs } from "@remix-run/node";
3837
import { authenticate } from "app/shopify.server";
3938
import { PlanView } from "app/components/PlanView";
4039

41-
const currencyFormatter = new Intl.NumberFormat("en-US", {
42-
style: "currency",
43-
currency: "USD",
44-
});
45-
4640
const load: Loader = async ({ adminApiFetcher, queryClient }) => {
4741
await queryClient.ensureQueryData(lastStepIdQuery(adminApiFetcher));
4842
return;

clients/trieve-shopify-extension/app/routes/app._dashboard.chat.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { Grid, Page, Tabs } from "@shopify/polaris";
1+
import { Grid, Page } from "@shopify/polaris";
22
import { ChatAverageRating } from "app/components/analytics/chat/ChatAverageRating";
33
import { ChatConversionRate } from "app/components/analytics/chat/ChatConversionRate";
44
import { ChatRevenue } from "app/components/analytics/chat/ChatRevenue";
5-
import { ChatUserJourneyFunnel } from "app/components/analytics/chat/ChatUserJourneyFunnel";
65
import { MessagesPerUser } from "app/components/analytics/chat/MessagesPerUser";
76
import { PopularChatsTable } from "app/components/analytics/chat/PopularChatsTable";
87
import { PopularSuggestedQueries } from "app/components/analytics/chat/PopularSuggestedQueries";

clients/trieve-shopify-extension/app/routes/app._dashboard.chats.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
AdvancedTableComponent,
2020
Filter,
2121
} from "../components/analytics/AdvancedTableComponent";
22-
import { Text, Badge, ChoiceList, IndexFiltersProps, RangeSlider } from "@shopify/polaris";
22+
import { Badge, ChoiceList, IndexFiltersProps, RangeSlider } from "@shopify/polaris";
2323
import { DateRangePicker } from "../components/analytics/DateRangePicker";
2424
import { ComponentNameSelect } from "../components/analytics/ComponentNameSelect";
2525
import { allChatsQuery } from "app/queries/analytics/chat";

clients/trieve-shopify-extension/app/routes/app._dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
1919
import { shopDatasetQuery } from "app/queries/shopDataset";
2020

2121
export const loader = async (args: LoaderFunctionArgs) => {
22-
const { session } = await authenticate.admin(args.request);
2322
const key = await validateTrieveAuth(args.request, false);
2423
if (!key.currentDatasetId) {
2524
console.log("No dataset selected, redirecting to /app/setup");
@@ -28,6 +27,7 @@ export const loader = async (args: LoaderFunctionArgs) => {
2827

2928
const trieve = sdkFromKey(key);
3029

30+
const { session } = await authenticate.admin(args.request);
3131
const dataset = await trieve.getDatasetByTrackingId(session.shop);
3232
const organization = await trieve.getOrganizationById(
3333
dataset.organization_id,
Lines changed: 9 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,20 @@
11
import { LoaderFunctionArgs, redirect } from "@remix-run/node";
2-
import { sdkFromKey, validateTrieveAuth } from "app/auth";
2+
import { validateTrieveAuth } from "app/auth";
33
import {
44
defaultCrawlOptions,
55
ExtendedCrawlOptions,
66
} from "app/components/DatasetSettings";
7-
import { getTrieveBaseUrlEnv } from "app/env.server";
8-
import { AdminApiCaller } from "app/loaders";
97
import { buildAdminApiFetcherForServer } from "app/loaders/serverLoader";
108
import { sendChunks } from "app/processors/getProducts";
11-
import { trackCustomerEvent } from "app/processors/shopifyTrackers";
129
import { authenticate } from "app/shopify.server";
13-
import { TrieveKey } from "app/types";
14-
import { TrieveSDK } from "trieve-ts-sdk";
1510

1611
export type AppInstallData = {
1712
currentAppInstallation: { id: string };
1813
};
1914

2015
export const loader = async (args: LoaderFunctionArgs) => {
21-
const setAppMetafields = async (
22-
adminApi: AdminApiCaller,
23-
trieveKey: TrieveKey,
24-
) => {
25-
const response = await adminApi<AppInstallData>(`
26-
#graphql
27-
query {
28-
currentAppInstallation {
29-
id
30-
}
31-
}
32-
`);
33-
34-
if (response.error) {
35-
throw response.error;
36-
}
37-
38-
const appId = response.data;
39-
40-
await adminApi(
41-
`#graphql
42-
mutation CreateAppDataMetafield($metafieldsSetInput: [MetafieldsSetInput!]!) {
43-
metafieldsSet(metafields: $metafieldsSetInput) {
44-
metafields {
45-
id
46-
namespace
47-
key
48-
}
49-
userErrors {
50-
field
51-
message
52-
}
53-
}
54-
}
55-
`,
56-
{
57-
variables: {
58-
metafieldsSetInput: [
59-
{
60-
namespace: "trieve",
61-
key: "dataset_id",
62-
value: trieveKey.currentDatasetId,
63-
type: "single_line_text_field",
64-
ownerId: appId.currentAppInstallation.id,
65-
},
66-
{
67-
namespace: "trieve",
68-
key: "api_key",
69-
value: trieveKey.key,
70-
type: "single_line_text_field",
71-
ownerId: appId.currentAppInstallation.id,
72-
},
73-
],
74-
},
75-
},
76-
);
77-
};
78-
79-
8016
const { session } = await authenticate.admin(args.request);
81-
82-
let key = await validateTrieveAuth(args.request, false);
83-
const trieve = sdkFromKey(key);
84-
85-
let datasetId = trieve.datasetId;
86-
87-
let shopDataset = await trieve
88-
.getDatasetByTrackingId(session.shop)
89-
.catch(() => {
90-
return null;
91-
});
92-
93-
const fetcher = buildAdminApiFetcherForServer(
94-
session.shop,
95-
session.accessToken!,
96-
);
97-
98-
if ((!datasetId || !shopDataset) && trieve.organizationId) {
99-
if (!shopDataset) {
100-
shopDataset = await trieve.createDataset({
101-
dataset_name: session.shop,
102-
tracking_id: session.shop,
103-
server_configuration: {
104-
SYSTEM_PROMPT:
105-
"[[personality]]\nYou are a friendly, helpful, and knowledgeable ecommerce sales associate. Your communication style is warm, patient, and enthusiastic without being pushy. You're approachable and conversational while maintaining professionalism. You balance being personable with being efficient, understanding that customers value both connection and their time. You're solution-oriented and genuinely interested in helping customers find the right products for their needs.\n\n[[goal]]\nYour primary goal is to help customers find products that genuinely meet their needs while providing an exceptional shopping experience. You aim to:\n1. Understand customer requirements through thoughtful questions\n2. Provide relevant product recommendations based on customer preferences\n3. Offer detailed, accurate information about products\n4. Address customer concerns and objections respectfully\n5. Guide customers through the purchasing process\n6. Encourage sales without being pushy or manipulative\n7. Create a positive impression that builds long-term customer loyalty\n\n[[response structure]]\n1. Begin with a warm greeting and acknowledgment of the customer's query or concern\n2. Ask clarifying questions if needed to better understand their requirements\n3. Provide concise, relevant information that directly addresses their needs\n4. Include specific product recommendations when appropriate, with brief explanations of why they might be suitable\n5. Address any potential concerns proactively\n6. Close with a helpful next step or question that moves the conversation forward\n7. Keep responses conversational yet efficient, balancing thoroughness with respect for the customer's time.\n",
106-
RAG_PROMPT:
107-
"You may use the retrieved context to help you respond. When discussing products, prioritize information from the provided product data while using your general knowledge to create helpful, natural responses. If a customer asks about products or specifications not mentioned in the context, acknowledge the limitation and offer to check for more information rather than inventing details.",
108-
},
109-
});
110-
111-
key = await prisma.apiKey.update({
112-
data: {
113-
currentDatasetId: shopDataset.id,
114-
},
115-
where: {
116-
shop: `${session.shop}`,
117-
},
118-
});
119-
} else {
120-
key = await prisma.apiKey.update({
121-
data: {
122-
currentDatasetId: shopDataset.id,
123-
},
124-
where: {
125-
shop: `${session.shop}`,
126-
},
127-
});
128-
}
129-
130-
if (key.currentDatasetId && key.key && session) {
131-
setAppMetafields(fetcher, key);
132-
}
133-
}
17+
let key = await validateTrieveAuth(args.request, true);
13418

13519
let crawlSettings = await prisma.crawlSettings.findFirst({
13620
where: {
@@ -148,19 +32,11 @@ export const loader = async (args: LoaderFunctionArgs) => {
14832
},
14933
});
15034
}
151-
152-
datasetId = shopDataset?.id;
153-
if (!datasetId) {
154-
throw new Response("Error choosing default dataset, need to create one", {
155-
status: 500,
156-
});
157-
}
158-
15935
const crawlOptions = crawlSettings.crawlSettings as ExtendedCrawlOptions;
16036

16137
await prisma.crawlSettings.upsert({
16238
create: {
163-
datasetId: datasetId,
39+
datasetId: key.currentDatasetId,
16440
shop: session.shop,
16541
crawlSettings: crawlOptions,
16642
},
@@ -169,16 +45,19 @@ export const loader = async (args: LoaderFunctionArgs) => {
16945
},
17046
where: {
17147
datasetId_shop: {
172-
datasetId: datasetId,
48+
datasetId: key.currentDatasetId,
17349
shop: session.shop,
17450
},
17551
},
17652
});
17753

178-
sendChunks(datasetId ?? "", key, fetcher, session, crawlOptions).catch(
54+
const fetcher = buildAdminApiFetcherForServer(
55+
session.shop,
56+
session.accessToken!,
57+
);
58+
sendChunks(key.currentDatasetId ?? "", key, fetcher, session, crawlOptions).catch(
17959
console.error,
18060
);
18161

182-
trieve.datasetId = datasetId;
18362
return redirect("/app");
18463
};

0 commit comments

Comments
 (0)