Transform meetings page into context page with tabs

Add structured organization context with 5 markdown sections (Product, Architecture, Team, Processes, Customers) editable inline. Meetings are now a tab within the context page. Moved all GraphQL queries from hooks/graph/MeetingGraph.ts into colocated components following new best practices. Updated database schema, backend services, GraphQL resolvers, and MCP API to support the new context fields and structure.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-03-19 17:42:14 +01:00
parent c2a1843c67
commit a9ebeef0fa
25 changed files with 1156 additions and 380 deletions

View File

@@ -3846,7 +3846,11 @@ type Mutation {
# Input Types
input UpdateOrganizationContextInput {
organizationId: ID!
summary: String @goField(omittable: true)
product: String @goField(omittable: true)
architecture: String @goField(omittable: true)
team: String @goField(omittable: true)
processes: String @goField(omittable: true)
customers: String @goField(omittable: true)
}
input UpdateTrustCenterInput {
@@ -4753,7 +4757,11 @@ type UpdateOrganizationContextPayload {
type OrganizationContext {
organizationId: ID!
summary: String
product: String
architecture: String
team: String
processes: String
customers: String
}
type UpdateTrustCenterPayload {

View File

@@ -21,6 +21,10 @@ import (
func NewOrganizationContext(oc *coredata.OrganizationContext) *OrganizationContext {
return &OrganizationContext{
OrganizationID: oc.OrganizationID,
Summary: oc.Summary,
Product: oc.Product,
Architecture: oc.Architecture,
Team: oc.Team,
Processes: oc.Processes,
Customers: oc.Customers,
}
}

View File

@@ -2048,7 +2048,11 @@ func (r *mutationResolver) UpdateOrganizationContext(ctx context.Context, input
req := probo.UpdateOrganizationContextRequest{
OrganizationID: input.OrganizationID,
Summary: gqlutils.UnwrapOmittable(input.Summary),
Product: gqlutils.UnwrapOmittable(input.Product),
Architecture: gqlutils.UnwrapOmittable(input.Architecture),
Team: gqlutils.UnwrapOmittable(input.Team),
Processes: gqlutils.UnwrapOmittable(input.Processes),
Customers: gqlutils.UnwrapOmittable(input.Customers),
}
organizationContext, err := prb.Organizations.UpdateContext(ctx, req)
@@ -6387,7 +6391,7 @@ func (r *organizationResolver) Context(ctx context.Context, obj *types.Organizat
prb := r.ProboService(ctx, obj.ID.TenantID())
orgContext, err := prb.Organizations.GetContextSummary(ctx, obj.ID)
orgContext, err := prb.Organizations.GetContext(ctx, obj.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot load organization context", log.Error(err))
return nil, gqlutils.Internal(ctx)

View File

@@ -3241,3 +3241,43 @@ func (r *Resolver) UnarchiveDocumentTool(ctx context.Context, req *mcp.CallToolR
Document: types.NewDocument(document, profileIDs(approverPage)),
}, nil
}
func (r *Resolver) GetOrganizationContextTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetOrganizationContextInput) (*mcp.CallToolResult, types.GetOrganizationContextOutput, error) {
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionOrganizationContextGet)
prb := r.ProboService(ctx, input.OrganizationID)
orgContext, err := prb.Organizations.GetContext(ctx, input.OrganizationID)
if err != nil {
return nil, types.GetOrganizationContextOutput{}, fmt.Errorf("cannot get organization context: %w", err)
}
return nil, types.GetOrganizationContextOutput{
OrganizationContext: types.NewOrganizationContext(orgContext),
}, nil
}
func (r *Resolver) UpdateOrganizationContextTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateOrganizationContextInput) (*mcp.CallToolResult, types.UpdateOrganizationContextOutput, error) {
r.MustAuthorize(ctx, input.OrganizationID, probo.ActionOrganizationContextUpdate)
prb := r.ProboService(ctx, input.OrganizationID)
orgContext, err := prb.Organizations.UpdateContext(
ctx,
probo.UpdateOrganizationContextRequest{
OrganizationID: input.OrganizationID,
Product: &input.Product,
Architecture: &input.Architecture,
Team: &input.Team,
Processes: &input.Processes,
Customers: &input.Customers,
},
)
if err != nil {
return nil, types.UpdateOrganizationContextOutput{}, fmt.Errorf("cannot update organization context: %w", err)
}
return nil, types.UpdateOrganizationContextOutput{
OrganizationContext: types.NewOrganizationContext(orgContext),
}, nil
}

View File

@@ -6323,6 +6323,79 @@ components:
$ref: "#/components/schemas/GID"
description: Deleted applicability statement ID
OrganizationContext:
type: object
required:
- organization_id
properties:
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
product:
type: string
description: Product description
architecture:
type: string
description: Architecture description
team:
type: string
description: Team description
processes:
type: string
description: Processes description
customers:
type: string
description: Customers description
GetOrganizationContextInput:
type: object
required:
- organization_id
properties:
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
GetOrganizationContextOutput:
type: object
required:
- organization_context
properties:
organization_context:
$ref: "#/components/schemas/OrganizationContext"
UpdateOrganizationContextInput:
type: object
required:
- organization_id
properties:
organization_id:
$ref: "#/components/schemas/GID"
description: Organization ID
product:
type: string
description: Product description
architecture:
type: string
description: Architecture description
team:
type: string
description: Team description
processes:
type: string
description: Processes description
customers:
type: string
description: Customers description
UpdateOrganizationContextOutput:
type: object
required:
- organization_context
properties:
organization_context:
$ref: "#/components/schemas/OrganizationContext"
tools:
- name: listOrganizations
description: List all organizations the user has access to
@@ -7441,3 +7514,20 @@ tools:
$ref: "#/components/schemas/DeleteApplicabilityStatementInput"
outputSchema:
$ref: "#/components/schemas/DeleteApplicabilityStatementOutput"
- name: getOrganizationContext
description: Get the organization context containing structured sections about the company
hints:
readonly: true
idempotent: true
inputSchema:
$ref: "#/components/schemas/GetOrganizationContextInput"
outputSchema:
$ref: "#/components/schemas/GetOrganizationContextOutput"
- name: updateOrganizationContext
description: Update the organization context sections (product, architecture, team, processes, customers)
hints:
readonly: false
inputSchema:
$ref: "#/components/schemas/UpdateOrganizationContextInput"
outputSchema:
$ref: "#/components/schemas/UpdateOrganizationContextOutput"

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
package types
import (
"go.probo.inc/probo/pkg/coredata"
)
func NewOrganizationContext(oc *coredata.OrganizationContext) *OrganizationContext {
return &OrganizationContext{
OrganizationID: oc.OrganizationID,
Product: oc.Product,
Architecture: oc.Architecture,
Team: oc.Team,
Processes: oc.Processes,
Customers: oc.Customers,
}
}