Skip to content
Open
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
1,713 changes: 862 additions & 851 deletions connect-go/gen/proto/wg/cosmo/platform/v1/platform.pb.go

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion connect/src/wg/cosmo/platform/v1/platform_pb.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import {
} from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { PlainMessage } from '../../../types/index.js';
import { UnauthorizedError } from '../../errors/errors.js';
import { FeatureFlagRepository } from '../../repositories/FeatureFlagRepository.js';
import { FederatedGraphRepository } from '../../repositories/FederatedGraphRepository.js';
import { DefaultNamespace } from '../../repositories/NamespaceRepository.js';
import { SubgraphRepository } from '../../repositories/SubgraphRepository.js';
import { AnalyticsDashboardViewRepository } from '../../repositories/analytics/AnalyticsDashboardViewRepository.js';
import type { RouterOptions } from '../../routes.js';
import { convertToSubgraphType, enrichLogger, getLogger, handleError } from '../../util.js';
import { convertToSubgraphProto, enrichLogger, getLogger, handleError } from '../../util.js';

export function getFederatedGraphByName(
opts: RouterOptions,
Expand All @@ -27,13 +28,15 @@ export function getFederatedGraphByName(

const fedRepo = new FederatedGraphRepository(logger, opts.db, authContext.organizationId);
const subgraphRepo = new SubgraphRepository(logger, opts.db, authContext.organizationId);
const featureFlagRepo = new FeatureFlagRepository(logger, opts.db, authContext.organizationId);

req.namespace = req.namespace || DefaultNamespace;

const federatedGraph = await fedRepo.byName(req.name, req.namespace);
if (!federatedGraph) {
return {
subgraphs: [],
featureSubgraphs: [],
graphRequestToken: '',
response: {
code: EnumStatusCode.ERR_NOT_FOUND,
Expand All @@ -58,6 +61,16 @@ export function getFederatedGraphByName(
rbac: authContext.rbac,
});

// Feature subgraphs of the feature flags that match this federated graph
const { featureSubgraphs } = await featureFlagRepo.getFeatureSubgraphsByFederatedGraph({
federatedGraphId: federatedGraph.id,
namespaceId: federatedGraph.namespaceId,
fedGraphLabelMatchers: federatedGraph.labelMatchers,
limit: 0,
offset: 0,
rbac: authContext.rbac,
});

const routerRequestToken = await fedRepo.getGraphSignedToken({
federatedGraphId: federatedGraph.id,
organizationId: authContext.organizationId,
Expand All @@ -66,6 +79,7 @@ export function getFederatedGraphByName(
if (!routerRequestToken) {
return {
subgraphs: [],
featureSubgraphs: [],
graphRequestToken: '',
response: {
code: EnumStatusCode.ERR,
Expand Down Expand Up @@ -94,22 +108,8 @@ export function getFederatedGraphByName(
admissionWebhookUrl: federatedGraph.admissionWebhookURL,
routerCompatibilityVersion: federatedGraph.routerCompatibilityVersion,
},
subgraphs: list.map((g) => ({
id: g.id,
name: g.name,
routingURL: g.routingUrl,
lastUpdatedAt: g.lastUpdatedAt,
labels: g.labels,
targetId: g.targetId,
subscriptionUrl: g.subscriptionUrl,
namespace: g.namespace,
subscriptionProtocol: g.subscriptionProtocol,
isEventDrivenGraph: g.isEventDrivenGraph,
isV2Graph: g.isV2Graph,
websocketSubprotocol: g.websocketSubprotocol || '',
isFeatureSubgraph: g.isFeatureSubgraph,
type: convertToSubgraphType(g.type),
})),
subgraphs: list.map((g) => convertToSubgraphProto(g)),
featureSubgraphs: featureSubgraphs.map((g) => convertToSubgraphProto(g)),
graphRequestToken: routerRequestToken,
response: {
code: EnumStatusCode.OK,
Expand Down
12 changes: 11 additions & 1 deletion controlplane/src/core/repositories/FeatureFlagRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,13 +461,15 @@ export class FeatureFlagRepository {
limit,
offset,
query,
rbac,
}: {
federatedGraphId: string;
namespaceId: string;
fedGraphLabelMatchers: string[];
limit: number;
offset: number;
query?: string;
rbac?: RBACEvaluator;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}): Promise<{ featureSubgraphs: FeatureSubgraphDTO[]; totalCount: number }> {
const subgraphRepo = new SubgraphRepository(this.logger, this.db, this.organizationId);

Expand Down Expand Up @@ -495,6 +497,14 @@ export class FeatureFlagRepository {
conditions.push(isValidUuid(query) ? eq(subgraphs.id, query) : like(targets.name, `%${query}%`));
}

if (
rbac &&
(!applyIdpNamespaceGate(rbac, targets.namespaceId, conditions) ||
!this.applyRbacConditionsToQuery(rbac, conditions))
) {
return { featureSubgraphs: [], totalCount: 0 };
}

const baseSubgraphs = alias(subgraphs, 'base_subgraphs');
const baseTargets = alias(targets, 'base_targets');
const baseQuery = this.db
Expand Down Expand Up @@ -552,7 +562,7 @@ export class FeatureFlagRepository {
const pendingFeatureSubgraphs = featureSubgraphTargets.map((target) => target.targetId);
while (pendingFeatureSubgraphs.length > 0) {
const chunkOfIdsToFetch = pendingFeatureSubgraphs.splice(0, 100);
const chunkOfSubgraphs = await subgraphRepo.getSubgraphsByTargetIds(chunkOfIdsToFetch);
const chunkOfSubgraphs = await subgraphRepo.getSubgraphsByTargetIds(chunkOfIdsToFetch, rbac);
a.push(...chunkOfSubgraphs);
}

Expand Down
26 changes: 25 additions & 1 deletion controlplane/src/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { parse, visit } from 'graphql';
import { uid } from 'uid/secure';
import DOMPurify from 'isomorphic-dompurify';
import { LATEST_ROUTER_COMPATIBILITY_VERSION } from '@wundergraph/composition';
import { ProposalOrigin, SubgraphType } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { ProposalOrigin, Subgraph, SubgraphType } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
import { MemberRole, ProposalOrigin as ProposalOriginEnum, WebsocketSubprotocol } from '../db/models.js';
import {
AuthContext,
Expand All @@ -27,10 +27,12 @@ import {
Label,
LoginMethod,
NamespaceAccess,
PlainMessage,
ResponseMessage,
S3StorageOptions,
SOCIAL_LOGIN_PROVIDERS,
SocialLoginProvider,
SubgraphDTO,
} from '../types/index.js';
import { paginationDefaults } from './constants.js';
import {
Expand Down Expand Up @@ -700,6 +702,28 @@ export const convertToSubgraphType = (type: string) => {
}
};

/**
* Maps a subgraph (or feature subgraph) DTO to its proto representation.
*/
export function convertToSubgraphProto(subgraph: SubgraphDTO): PlainMessage<Subgraph> {
return {
id: subgraph.id,
name: subgraph.name,
routingURL: subgraph.routingUrl,
lastUpdatedAt: subgraph.lastUpdatedAt,
labels: subgraph.labels,
targetId: subgraph.targetId,
subscriptionUrl: subgraph.subscriptionUrl,
namespace: subgraph.namespace,
subscriptionProtocol: subgraph.subscriptionProtocol,
isEventDrivenGraph: subgraph.isEventDrivenGraph,
isV2Graph: subgraph.isV2Graph,
websocketSubprotocol: subgraph.websocketSubprotocol || '',
isFeatureSubgraph: subgraph.isFeatureSubgraph,
type: convertToSubgraphType(subgraph.type),
};
}

export function toProposalOriginEnum(value: ProposalOrigin): ProposalOriginEnum {
switch (value) {
case ProposalOrigin.EXTERNAL: {
Expand Down
2 changes: 2 additions & 0 deletions proto/wg/cosmo/platform/v1/platform.proto
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,8 @@ message GetFederatedGraphByNameResponse {
FederatedGraph graph = 2;
repeated Subgraph subgraphs = 3;
string graphRequestToken = 4;
// The feature subgraphs that are part of the feature flags which match this federated graph.
repeated Subgraph featureSubgraphs = 5;
}

message GetFederatedGraphSDLByNameRequest {
Expand Down
23 changes: 17 additions & 6 deletions studio/src/components/analytics/field-usage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export const FieldUsage = ({ usageData }: { usageData: GetFieldUsageResponse })
const organizationSlug = useCurrentOrganization()?.slug;
const slug = router.query.slug;

const subgraphs = useContext(GraphContext)?.subgraphs ?? [];
const graphContext = useContext(GraphContext);
const subgraphs = graphContext?.subgraphs ?? [];
const featureSubgraphs = graphContext?.featureSubgraphs ?? [];

const { range, dateRange } = useAnalyticsQueryState();

Expand Down Expand Up @@ -243,9 +245,21 @@ export const FieldUsage = ({ usageData }: { usageData: GetFieldUsageResponse })
<h2 className="text-lg font-semibold">Subgraphs: </h2>
<div className="mt-[2px] grid w-max grid-cols-3 gap-x-8">
{usageData.meta.subgraphIds.map((id) => {
const subgraph = subgraphs.find((s) => s.id === id);
const subgraph = [...subgraphs, ...featureSubgraphs].find((s) => s.id === id);
if (!subgraph) return null;

const content = (
<div className="flex items-start gap-x-1">
<CubeIcon className="mt-1.5 flex-shrink-0 break-all" />
{subgraph.name}
</div>
);

// Feature subgraphs are not part of the federated graph, so there is no page to link to
if (subgraph.isFeatureSubgraph) {
return <div key={id}>{content}</div>;
}

return (
<Link
key={id}
Expand All @@ -257,10 +271,7 @@ export const FieldUsage = ({ usageData }: { usageData: GetFieldUsageResponse })
})}
className="text-primary"
>
<div className="flex items-start gap-x-1">
<CubeIcon className="mt-1.5 flex-shrink-0 break-all" />
{subgraph.name}
</div>
{content}
</Link>
);
})}
Expand Down
2 changes: 2 additions & 0 deletions studio/src/components/layout/graph-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { buildUrl } from '@/lib/build-url';
export interface GraphContextProps {
graph: GetFederatedGraphByNameResponse['graph'];
subgraphs: GetFederatedGraphByNameResponse['subgraphs'];
featureSubgraphs: GetFederatedGraphByNameResponse['featureSubgraphs'];
graphs: GetFederatedGraphsResponse['graphs'];
graphRequestToken: string;
}
Expand Down Expand Up @@ -205,6 +206,7 @@ export const GraphLayout = ({ children }: LayoutProps) => {
return {
graph: data.graph,
subgraphs: data.subgraphs,
featureSubgraphs: data.featureSubgraphs,
graphRequestToken: data.graphRequestToken,
graphs: graphsData.graphs,
};
Expand Down
Loading