Managing projects, spaces, and organizational structures in Lightdash.
Projects and spaces provide organizational structure:
interface Project {
organizationUuid: string;
projectUuid: string;
name: string;
type: ProjectType;
dbtConnection: DbtProjectConfig;
warehouseConnection?: WarehouseCredentials;
pinnedListUuid?: string;
upstreamProjectUuid?: string;
dbtVersion: DbtVersionOption;
schedulerTimezone: string;
createdByUserUuid: string | null;
organizationWarehouseCredentialsUuid?: string;
}
enum ProjectType {
DEFAULT = 'DEFAULT',
PREVIEW = 'PREVIEW',
}
interface ProjectSummary {
organizationUuid: string;
projectUuid: string;
name: string;
type: ProjectType;
dbtConnection: Pick<DbtProjectConfig, 'type'>;
warehouseConnection?: Pick<WarehouseCredentials, 'type'>;
}Example:
const project: Project = {
organizationUuid: 'org-uuid',
projectUuid: 'project-uuid',
name: 'Analytics Project',
type: ProjectType.DEFAULT,
dbtConnection: {
type: DbtProjectType.GITHUB,
repository: 'org/analytics-dbt',
branch: 'main',
project_sub_path: '/',
},
warehouseConnection: {
type: WarehouseTypes.BIGQUERY,
project: 'my-gcp-project',
dataset: 'analytics',
},
dbtVersion: SupportedDbtVersions.V1_10,
schedulerTimezone: 'UTC',
createdByUserUuid: 'user-uuid',
};enum DbtProjectType {
DBT = 'dbt',
GITHUB = 'github',
GITLAB = 'gitlab',
BITBUCKET = 'bitbucket',
AZURE_DEVOPS = 'azure_devops',
DBT_CLOUD_IDE = 'dbt_cloud_ide',
NONE = 'none',
MANIFEST = 'manifest',
}type DbtProjectConfig =
| DbtLocalProjectConfig
| DbtGithubProjectConfig
| DbtGitlabProjectConfig
| DbtBitBucketProjectConfig
| DbtAzureDevOpsProjectConfig
| DbtCloudIDEProjectConfig
| DbtNoneProjectConfig
| DbtManifestProjectConfig;
interface DbtLocalProjectConfig {
type: DbtProjectType.DBT;
profiles_dir?: string;
project_dir?: string;
target?: string;
}
interface DbtManifestProjectConfig {
type: DbtProjectType.MANIFEST;
manifest: string;
hideRefreshButton: boolean;
}
interface DbtGithubProjectConfig {
type: DbtProjectType.GITHUB;
repository: string;
branch: string;
project_sub_path: string;
host_domain?: string;
personal_access_token?: string;
}
interface DbtGitlabProjectConfig {
type: DbtProjectType.GITLAB;
repository: string;
branch: string;
project_sub_path: string;
host_domain?: string;
personal_access_token?: string;
}
// Similar interfaces for BitBucket, Azure DevOps, DBT Cloud IDEenum SupportedDbtVersions {
V1_4 = 'v1_4',
V1_5 = 'v1_5',
V1_6 = 'v1_6',
V1_7 = 'v1_7',
V1_8 = 'v1_8',
V1_9 = 'v1_9',
V1_10 = 'v1_10',
}
const DefaultSupportedDbtVersion: SupportedDbtVersions;
function getLatestSupportDbtVersion(): SupportedDbtVersions;
function isDbtVersion110OrHigher(version: SupportedDbtVersions): boolean;function isGitProjectType(type: DbtProjectType): boolean;
function maybeOverrideDbtConnection(
dbtConnection: DbtProjectConfig,
override?: Partial<DbtProjectConfig>
): DbtProjectConfig;
function maybeOverrideWarehouseConnection(
warehouseConnection: WarehouseCredentials,
override?: Partial<CreateWarehouseCredentials>
): CreateWarehouseCredentials;
function getProjectDirectory(
dbtConnection?: DbtProjectConfig
): string | undefined;Example:
import {
isGitProjectType,
DbtProjectType,
getLatestSupportDbtVersion,
} from '@lightdash/common';
if (isGitProjectType(project.dbtConnection.type)) {
console.log('Project uses git integration');
}
const latestVersion = getLatestSupportDbtVersion();
console.log(`Latest DBT version: ${latestVersion}`);interface Space {
organizationUuid: string;
uuid: string;
name: string;
isPrivate: boolean;
pinnedListUuid: string | null;
pinnedListOrder: number | null;
dashboards: SpaceDashboard[];
queries: SpaceQuery[];
access: SpaceShare[];
projectUuid: string;
slug: string;
groupsAccess: SpaceGroup[];
// Nested Spaces fields
childSpaces: Omit<SpaceSummary, 'userAccess'>[];
parentSpaceUuid: string | null;
path: string; // ltree path serialized as string
breadcrumbs?: {
name: string;
uuid: string;
}[];
}
interface SpaceSummary {
organizationUuid: string;
uuid: string;
name: string;
isPrivate: boolean;
pinnedListUuid: string | null;
pinnedListOrder: number | null;
projectUuid: string;
slug: string;
parentSpaceUuid: string | null;
path: string;
userAccess: SpaceShare | undefined;
access: string[];
chartCount: number;
dashboardCount: number;
}interface SpaceDashboard {
uuid: string;
name: string;
description?: string;
slug: string;
updatedAt: Date;
updatedByUser?: UpdatedByUser;
pinnedListOrder: number | null;
views: number;
firstViewedAt: Date | null;
validationErrors?: ValidationError[];
}
interface SpaceQuery {
uuid: string;
name: string;
description?: string;
slug: string;
updatedAt: Date;
updatedByUser?: UpdatedByUser;
spaceUuid: string;
pinnedListOrder: number | null;
views: number;
firstViewedAt: Date | null;
chartType: ChartType;
chartKind: ChartKind;
validationErrors?: ValidationError[];
}enum SpaceMemberRole {
VIEWER = 'viewer',
EDITOR = 'editor',
ADMIN = 'admin',
}
interface SpaceShare {
userUuid: string;
firstName: string;
lastName: string;
email: string;
role: SpaceMemberRole;
hasDirectAccess: boolean;
projectRole: ProjectMemberRole | undefined;
inheritedRole: OrganizationMemberRole | ProjectMemberRole | undefined;
inheritedFrom: 'organization' | 'project' | 'group' | 'space_group' | undefined;
}
interface SpaceGroup {
groupUuid: string;
groupName: string;
spaceRole: SpaceMemberRole;
}
interface AddSpaceUserAccess {
userUuid: string;
spaceRole: SpaceMemberRole;
}
interface AddSpaceGroupAccess {
groupUuid: string;
spaceRole: SpaceMemberRole;
}interface CreateSpace {
name: string;
isPrivate?: boolean;
access?: Pick<SpaceShare, 'userUuid' | 'role'>[];
parentSpaceUuid?: string;
}
interface UpdateSpace {
name: string;
isPrivate?: boolean;
}Example:
import { type CreateSpace, SpaceMemberRole } from '@lightdash/common';
const newSpace: CreateSpace = {
name: 'Sales Analytics',
isPrivate: true,
access: [
{
userUuid: 'user-1',
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
role: SpaceMemberRole.ADMIN,
hasDirectAccess: true,
},
{
userUuid: 'user-2',
firstName: 'Jane',
lastName: 'Smith',
email: 'jane@example.com',
role: SpaceMemberRole.EDITOR,
hasDirectAccess: true,
},
],
tags: ['sales', 'quarterly'],
};interface DbtProjectEnvironmentVariable {
key: string;
value: string;
}Used for setting DBT environment variables in project configuration.
interface UpdateSchedulerSettings {
schedulerTimezone?: string;
schedulerCron?: string;
schedulerEnabled?: boolean;
schedulerTarget?: 'dashboard' | 'csv';
}Configure scheduled deliveries for dashboards and reports.
import {
type Project,
type Space,
ProjectType,
DbtProjectType,
WarehouseTypes,
SupportedDbtVersions,
SpaceMemberRole,
} from '@lightdash/common';
// Create a project
const project: Project = {
organizationUuid: 'org-uuid',
projectUuid: 'project-uuid',
name: 'Analytics',
type: ProjectType.DEFAULT,
dbtConnection: {
type: DbtProjectType.GITHUB,
repository: 'company/analytics-dbt',
branch: 'main',
project_sub_path: '/',
personal_access_token: 'github_pat_xxx',
},
warehouseConnection: {
type: WarehouseTypes.BIGQUERY,
project: 'my-gcp-project',
dataset: 'analytics',
keyfileContents: { /* service account key */ },
startOfWeek: WeekDay.MONDAY,
},
dbtVersion: SupportedDbtVersions.V1_10,
};
// Create spaces for organization
const publicSpace: Space = {
organizationUuid: 'org-uuid',
uuid: 'space-1',
name: 'Public Dashboards',
projectUuid: 'project-uuid',
isPrivate: false,
slug: 'public-dashboards',
pinnedListUuid: null,
pinnedListOrder: null,
chartCount: 5,
dashboardCount: 3,
dashboards: [
{
uuid: 'dash-1',
name: 'Sales Overview',
slug: 'sales-overview',
updatedAt: new Date(),
pinnedListOrder: 0,
views: 150,
firstViewedAt: new Date('2024-01-01'),
},
],
queries: [
{
uuid: 'chart-1',
name: 'Monthly Revenue',
slug: 'monthly-revenue',
spaceUuid: 'space-1',
updatedAt: new Date(),
pinnedListOrder: 0,
views: 75,
firstViewedAt: new Date('2024-01-01'),
chartType: ChartType.CARTESIAN,
chartKind: ChartKind.LINE,
},
],
access: [], // Public space has no access restrictions
groupsAccess: [],
};
const privateSpace: Space = {
organizationUuid: 'org-uuid',
uuid: 'space-2',
name: 'Finance Reports',
projectUuid: 'project-uuid',
isPrivate: true,
slug: 'finance-reports',
pinnedListUuid: null,
pinnedListOrder: null,
chartCount: 8,
dashboardCount: 2,
dashboards: [],
queries: [],
access: [
{
userUuid: 'user-1',
firstName: 'Finance',
lastName: 'Manager',
email: 'finance@example.com',
role: SpaceMemberRole.ADMIN,
hasDirectAccess: true,
},
{
userUuid: 'user-2',
firstName: 'Finance',
lastName: 'Analyst',
email: 'analyst@example.com',
role: SpaceMemberRole.EDITOR,
hasDirectAccess: true,
},
],
groupsAccess: [
{
groupUuid: 'group-1',
groupName: 'Finance Team',
spaceRole: SpaceMemberRole.EDITOR,
},
],
};import { type Space, SpaceMemberRole } from '@lightdash/common';
function addUserToSpace(
space: Space,
userUuid: string,
role: SpaceMemberRole
): Space {
return {
...space,
access: [
...space.access,
{
userUuid,
firstName: 'New',
lastName: 'User',
email: 'newuser@example.com',
role,
hasDirectAccess: true,
},
],
};
}
function updateUserRole(
space: Space,
userUuid: string,
newRole: SpaceMemberRole
): Space {
return {
...space,
access: space.access.map(share =>
share.userUuid === userUuid
? { ...share, role: newRole }
: share
),
};
}
function removeUserFromSpace(space: Space, userUuid: string): Space {
return {
...space,
access: space.access.filter(share => share.userUuid !== userUuid),
};
}import { type Space, type SpaceDashboard } from '@lightdash/common';
function getSpaceContent(space: Space) {
return {
dashboards: space.dashboards.sort((a, b) => {
// Sort by pinned order, then by views
if (a.pinnedListOrder !== null && b.pinnedListOrder !== null) {
return a.pinnedListOrder - b.pinnedListOrder;
}
return b.views - a.views;
}),
charts: space.queries.sort((a, b) => {
if (a.pinnedListOrder !== null && b.pinnedListOrder !== null) {
return a.pinnedListOrder - b.pinnedListOrder;
}
return b.views - a.views;
}),
};
}
function getPopularContent(space: Space, limit: number = 5) {
const allContent = [
...space.dashboards.map(d => ({ ...d, type: 'dashboard' as const })),
...space.queries.map(q => ({ ...q, type: 'chart' as const })),
];
return allContent
.sort((a, b) => b.views - a.views)
.slice(0, limit);
}