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
103 changes: 55 additions & 48 deletions packages/app/src/actions/data/templates/applyTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import {
PlotDefinitionWithoutSource,
} from "@common/db/schema/template";
import { plotDefinitionSchema } from "@common/db/schema/plot";
import {
actionError,
actionResult,
runActionServer,
} from "@/lib/actions/utils";

type ApplyTemplateParams = {
projectId: string;
Expand All @@ -29,61 +34,63 @@ export async function applyTemplateAction({
plotDefinition,
folderId,
}: ApplyTemplateParams) {
const user = await getServerUser();
const userProject = await getProjectForUser(user, projectId);
return runActionServer(async () => {
const user = await getServerUser();
const userProject = await getProjectForUser(user, projectId);

if (!userProject) {
throw new Error("Project not found");
}
if (!userProject) {
return actionError("Project not found");
}

// Create query
const newQuery = await createQuery({
name: queryName,
projectId,
folderId: folderId || null,
definition: Object.fromEntries(
Object.entries(queryDefinition).map(([key, value]) => [
key,
{
...value,
filters: {},
},
]),
),
});
// Create query
const newQuery = await createQuery({
name: queryName,
projectId,
folderId: folderId || null,
definition: Object.fromEntries(
Object.entries(queryDefinition).map(([key, value]) => [
key,
{
...value,
filters: {},
},
]),
),
});

revalidateQueryCache(newQuery.id, projectId);
revalidateQueryCache(newQuery.id, projectId);

let newPlot = null;
let url = `/projects/${projectId}/data/queries/${newQuery.id}`;
let newPlot = null;
let url = `/projects/${projectId}/data/queries/${newQuery.id}`;

// Create plot if provided
if (plotName && plotDefinition) {
const plotDefinitionWithSource = plotDefinitionSchema.parse({
...plotDefinition,
dataSource: {
isQuery: true,
queryId: newQuery.id,
},
});
// Create plot if provided
if (plotName && plotDefinition) {
const plotDefinitionWithSource = plotDefinitionSchema.parse({
...plotDefinition,
dataSource: {
isQuery: true,
queryId: newQuery.id,
},
});

newPlot = await createPlot({
name: plotName,
projectId,
folderId: folderId || null,
definition: plotDefinitionWithSource,
sourceQueryId: newQuery.id,
});
newPlot = await createPlot({
name: plotName,
projectId,
folderId: folderId || null,
definition: plotDefinitionWithSource,
sourceQueryId: newQuery.id,
});

url = `/projects/${projectId}/data/plots/${newPlot.id}`;
url = `/projects/${projectId}/data/plots/${newPlot.id}`;

revalidateTag(`project-${projectId}-plots`);
revalidateTag(`project-${projectId}-data`);
}
revalidateTag(`project-${projectId}-plots`);
revalidateTag(`project-${projectId}-data`);
}

return {
query: newQuery,
plot: newPlot,
url,
};
return actionResult({
query: newQuery,
plot: newPlot,
url,
});
});
}
165 changes: 116 additions & 49 deletions packages/app/src/actions/data/templates/createTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import {
QueryWithoutFilters,
PlotDefinitionWithoutSource,
} from "@common/db/schema/template";
import {
runActionServer,
actionError,
actionResult,
} from "@/lib/actions/utils";

interface CreateTemplateParams {
name: string;
Expand All @@ -31,64 +36,126 @@ export async function createTemplateAction({
projectId,
tagIds = [],
}: CreateTemplateParams) {
const user = await getServerUser();
return runActionServer(async () => {
const user = await getServerUser();

if (!user) {
throw new Error("User not authenticated");
}
if (!user) {
return actionError("User not authenticated");
}

let queryDefinition: QueryWithoutFilters | null = null;
let plotDefinition: PlotDefinitionWithoutSource | null = null;
let queryDefinition: QueryWithoutFilters | null = null;
let plotDefinition: PlotDefinitionWithoutSource | null = null;

// Get query definition if provided
if (queryId) {
const query = await getQueryForProject(projectId, queryId);
if (!query || !query.definition) {
throw new Error("query not found or has no definition");
}
// Get query definition if provided
if (queryId) {
const query = await getQueryForProject(projectId, queryId);
if (!query || !query.definition) {
return actionError("Query not found or has no definition");
}

// Remove filters from the definition for the template
queryDefinition = Object.fromEntries(
Object.entries(query.definition).map(([tableName, tableDef]) => [
tableName,
{
...tableDef,
filters: undefined, // Remove filters
},
]),
) as Record<TableName, any>;
}

// Get plot definition if provided
if (plotId) {
const plot = await getPlotForProject(projectId, plotId);
if (!plot || !plot.definition) {
throw new Error("Plot not found or has no definition");
// Remove filters from the definition for the template
queryDefinition = Object.fromEntries(
Object.entries(query.definition).map(([tableName, tableDef]) => [
tableName,
{
...tableDef,
filters: undefined, // Remove filters
},
]),
) as Record<TableName, any>;
}

// Remove data source from the definition for the template
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
const { dataSource, ...plotDefWithoutSource } = plot.definition;
// Get plot definition if provided
if (plotId) {
const plot = await getPlotForProject(projectId, plotId);
if (!plot || !plot.definition) {
return actionError("Plot not found or has no definition");
}

plotDefinition = plotDefWithoutSource;
}
// If this is a plot template and we have a queryId (from plot.sourceQueryId),
// make sure we have the query definition
if (!queryDefinition && queryId) {
const query = await getQueryForProject(projectId, queryId);
if (!query || !query.definition) {
return actionError("Query not found or has no definition");
}

// Create the template
const newTemplate = await createTemplate({
name,
ownerId: user.id,
queryDefinition: queryDefinition || ({} as QueryWithoutFilters),
plotDefinition: plotDefinition || null,
scope,
});
// Remove filters from the definition for the template
queryDefinition = Object.fromEntries(
Object.entries(query.definition).map(([tableName, tableDef]) => [
tableName,
{
...tableDef,
filters: undefined, // Remove filters
},
]),
) as Record<TableName, any>;
}

// If we still don't have a query definition, try to get it from the plot's definition.dataSource
if (
!queryDefinition &&
plot.definition.dataSource?.isQuery &&
plot.definition.dataSource.queryId
) {
const query = await getQueryForProject(
projectId,
plot.definition.dataSource.queryId,
);
if (!query || !query.definition) {
return actionError("Query not found or has no definition");
}

// Remove filters from the definition for the template
queryDefinition = Object.fromEntries(
Object.entries(query.definition).map(([tableName, tableDef]) => [
tableName,
{
...tableDef,
filters: undefined, // Remove filters
},
]),
) as Record<TableName, any>;
}

// Ensure we have a query definition for plot templates
if (!queryDefinition) {
return actionError(
"Plot templates must have an associated query definition",
);
}

// Create template tags if provided
if (tagIds.length > 0) {
await createTemplateTags(newTemplate.id, tagIds);
}
// Only require query definition for plots that use queries
if (plot.definition.dataSource?.isQuery === true && !queryDefinition) {
return actionError(
"Plot templates that use queries must have an associated query definition",
);
}

// Revalidate user's templates cache
revalidateTag(`user-${user.id}-templates`);
// Remove data source from the definition for the template
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
const { dataSource, ...plotDefWithoutSource } = plot.definition;

return newTemplate;
plotDefinition = plotDefWithoutSource;
}

// Create the template
const newTemplate = await createTemplate({
name,
ownerId: user.id,
queryDefinition: queryDefinition || ({} as QueryWithoutFilters),
plotDefinition: plotDefinition || null,
scope,
});

// Create template tags if provided
if (tagIds.length > 0) {
await createTemplateTags(newTemplate.id, tagIds);
}

// Revalidate user's templates cache
revalidateTag(`user-${user.id}-templates`);

return actionResult(newTemplate);
});
}
9 changes: 0 additions & 9 deletions packages/app/src/actions/data/templates/readTemplates.ts

This file was deleted.

27 changes: 27 additions & 0 deletions packages/app/src/actions/data/templates/searchTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use server";

import { getServerUser } from "@/lib/auth";
import { searchGlobalTagsWithUsageCounts } from "@/db/crud/tag";

interface SearchTagsParams {
searchTerm: string;
limit?: number;
}

export async function searchTagsAction({
searchTerm,
limit = 20,
}: SearchTagsParams) {
const user = await getServerUser();

if (!user) {
throw new Error("User not authenticated");
}

if (!searchTerm.trim()) {
return [];
}

const tags = await searchGlobalTagsWithUsageCounts(searchTerm.trim(), limit);
return tags;
}
Loading