Compare commits

...

8 Commits

Author SHA1 Message Date
032fa6b898 Merge pull request 'Add pastDueUsers, newPaidToday, and cohort paidTotal to usage stats' (#314) from dean/usage-stats-pastdue-alltime into master
Reviewed-on: #314
2026-08-01 02:42:49 +00:00
Dean Wenstrand
81565b7676 Add pastDueUsers, newPaidToday, and cohort paidTotal to usage stats
All checks were successful
Tests / Tests (pull_request) Successful in 4m48s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 22:28:53 -04:00
d7b0b21430 Merge pull request 'Use BigInt for video export file sizes' (#310) from agent/video-export-bigint into master
Reviewed-on: #310
2026-07-27 19:35:17 +00:00
f173466b2f Merge pull request 'Add createShotShareLink for short share links' (#311) from dean/short-share-links-gql into master
Reviewed-on: #311
2026-07-27 19:35:11 +00:00
Dean Wenstrand
df69d154a5 Merge master into short-share-links-gql
All checks were successful
Tests / Tests (pull_request) Successful in 34s
gql master added Shot Lab video feed scopes and pagination
(operations-only: recorded_shots.gql + codegen output) while this branch
added createShotShareLink. The two touch disjoint parts of the generated
index.tsx, so the merge is textual only.

Verified rather than trusted: re-ran graphql-codegen after the merge and
src/index.tsx came back byte-identical (same md5), so the merged
generated file is exactly what codegen produces. Reverted prettier's
schema.gql reformat, which CI's own prettier undoes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:34:22 -04:00
0bceb5d300 Merge pull request 'Add Shot Lab video feed scopes and pagination' (#312) from t3code/shot-lab-video-sources into master
Reviewed-on: #312
2026-07-26 09:31:20 +00:00
f58048768c Add Shot Lab video feed scopes and pagination
All checks were successful
Tests / Tests (pull_request) Successful in 12s
2026-07-26 02:26:39 -07:00
179e8ed4e1 Add createShotShareLink for short share links
All checks were successful
Tests / Tests (pull_request) Successful in 11s
index.tsx is graphql-codegen output. schema.gql carries only the new
type, input and mutation field, deliberately left in the file's existing
formatting: package.json pins prettier as ^3.2.5, so a local install and
CI's yarn install can resolve different 3.x versions that disagree about
GraphQL union indentation. Committing a locally-reformatted schema.gql
makes CI's 'graphql-codegen causes no changes' check fail when its
prettier reformats it back.

The schema is verified semantically identical to the backend export via
lexicographic_sort_schema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 15:02:17 -07:00
5 changed files with 172 additions and 11 deletions

View File

@@ -265,6 +265,7 @@ export type CohortStatsGql = {
activated7d: Scalars["Int"]["output"];
activatedNotPaid7d: Scalars["Int"]["output"];
paid7d: Scalars["Int"]["output"];
paidTotal: Scalars["Int"]["output"];
signups: Scalars["Int"]["output"];
weekStart: Scalars["Date"]["output"];
};
@@ -315,6 +316,11 @@ export type CreatePoolHallInput = {
timezone?: InputMaybe<Scalars["String"]["input"]>;
};
export type CreateShotShareLinkInput = {
paddingSeconds?: InputMaybe<Scalars["Float"]["input"]>;
shotIds: Array<Scalars["Int"]["input"]>;
};
export type CreateSubscriptionResultGql = {
__typename?: "CreateSubscriptionResultGQL";
checkoutUrl: Scalars["String"]["output"];
@@ -2318,6 +2324,8 @@ export type FunnelStatsGql = {
freemiumEpoch: Scalars["Date"]["output"];
newPaid7d: Scalars["Int"]["output"];
newPaid30d: Scalars["Int"]["output"];
newPaidToday: Scalars["Int"]["output"];
pastDueUsers: Scalars["Int"]["output"];
payingUsers: Scalars["Int"]["output"];
retentionPrevActive: Scalars["Int"]["output"];
retentionReturned: Scalars["Int"]["output"];
@@ -2530,6 +2538,7 @@ export type Mutation = {
createPoolHall: PoolHall;
createPoolHallCamera: PoolHallCameraStreamCredentials;
createRuleSet: RuleSet;
createShotShareLink: ShareLinkGql;
createSubscription: CreateSubscriptionResultGql;
createUploadStream: CreateUploadStreamReturn;
deleteComment: Scalars["Boolean"]["output"];
@@ -2644,6 +2653,10 @@ export type MutationCreateRuleSetArgs = {
name: Scalars["String"]["input"];
};
export type MutationCreateShotShareLinkArgs = {
input: CreateShotShareLinkInput;
};
export type MutationCreateSubscriptionArgs = {
priceId: Scalars["String"]["input"];
};
@@ -3750,6 +3763,12 @@ export enum SessionCoachStateEnum {
Ready = "READY",
}
export type ShareLinkGql = {
__typename?: "ShareLinkGQL";
slug: Scalars["String"]["output"];
url: Scalars["String"]["output"];
};
export type ShotAnnotationGql = {
__typename?: "ShotAnnotationGQL";
createdAt?: Maybe<Scalars["DateTime"]["output"]>;
@@ -6874,8 +6893,10 @@ export type ReactToVideoMutation = {
};
export type GetShotLabVideosQueryVariables = Exact<{
userId: Scalars["Int"]["input"];
feedInput: VideoFeedInputGql;
includePrivate: IncludePrivateEnum;
limit?: Scalars["Int"]["input"];
after?: InputMaybe<Scalars["String"]["input"]>;
}>;
export type GetShotLabVideosQuery = {
@@ -6903,6 +6924,11 @@ export type GetShotLabVideosQuery = {
lastSegmentUploadedAt?: any | null;
} | null;
}>;
pageInfo: {
__typename?: "PageInfoGQL";
hasNextPage: boolean;
endCursor?: string | null;
};
};
};
@@ -7321,6 +7347,20 @@ export type SessionCoachGenerationFieldsFragment = {
} | null;
};
export type CreateShotShareLinkMutationVariables = Exact<{
shotIds: Array<Scalars["Int"]["input"]> | Scalars["Int"]["input"];
paddingSeconds?: InputMaybe<Scalars["Float"]["input"]>;
}>;
export type CreateShotShareLinkMutation = {
__typename?: "Mutation";
createShotShareLink: {
__typename?: "ShareLinkGQL";
slug: string;
url: string;
};
};
export type PlayerSummaryFieldsFragment = {
__typename?: "PlayerSummaryGQL";
clusterId: number;
@@ -8118,6 +8158,8 @@ export type GetUsageStatsQuery = {
activationCohort: number;
activationActivated: number;
payingUsers: number;
pastDueUsers: number;
newPaidToday: number;
newPaid7d: number;
newPaid30d: number;
retentionPrevActive: number;
@@ -8130,6 +8172,7 @@ export type GetUsageStatsQuery = {
activated7d: number;
paid7d: number;
activatedNotPaid7d: number;
paidTotal: number;
}>;
windows: Array<{
__typename?: "UsageStatsWindowGQL";
@@ -15140,12 +15183,18 @@ export type ReactToVideoMutationOptions = Apollo.BaseMutationOptions<
ReactToVideoMutationVariables
>;
export const GetShotLabVideosDocument = gql`
query GetShotLabVideos($userId: Int!, $limit: Int! = 25) {
query GetShotLabVideos(
$feedInput: VideoFeedInputGQL!
$includePrivate: IncludePrivateEnum!
$limit: Int! = 25
$after: String = null
) {
getFeedVideos(
limit: $limit
after: $after
includeCallersVideos: false
includePrivate: MINE
feedInput: { userId: $userId }
includePrivate: $includePrivate
feedInput: $feedInput
) {
videos {
id
@@ -15165,6 +15214,10 @@ export const GetShotLabVideosDocument = gql`
lastSegmentUploadedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
@@ -15181,8 +15234,10 @@ export const GetShotLabVideosDocument = gql`
* @example
* const { data, loading, error } = useGetShotLabVideosQuery({
* variables: {
* userId: // value for 'userId'
* feedInput: // value for 'feedInput'
* includePrivate: // value for 'includePrivate'
* limit: // value for 'limit'
* after: // value for 'after'
* },
* });
*/
@@ -15688,6 +15743,63 @@ export type RequestSessionCoachGenerationMutationOptions =
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>;
export const CreateShotShareLinkDocument = gql`
mutation CreateShotShareLink(
$shotIds: [Int!]!
$paddingSeconds: Float = null
) {
createShotShareLink(
input: { shotIds: $shotIds, paddingSeconds: $paddingSeconds }
) {
slug
url
}
}
`;
export type CreateShotShareLinkMutationFn = Apollo.MutationFunction<
CreateShotShareLinkMutation,
CreateShotShareLinkMutationVariables
>;
/**
* __useCreateShotShareLinkMutation__
*
* To run a mutation, you first call `useCreateShotShareLinkMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateShotShareLinkMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createShotShareLinkMutation, { data, loading, error }] = useCreateShotShareLinkMutation({
* variables: {
* shotIds: // value for 'shotIds'
* paddingSeconds: // value for 'paddingSeconds'
* },
* });
*/
export function useCreateShotShareLinkMutation(
baseOptions?: Apollo.MutationHookOptions<
CreateShotShareLinkMutation,
CreateShotShareLinkMutationVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
CreateShotShareLinkMutation,
CreateShotShareLinkMutationVariables
>(CreateShotShareLinkDocument, options);
}
export type CreateShotShareLinkMutationHookResult = ReturnType<
typeof useCreateShotShareLinkMutation
>;
export type CreateShotShareLinkMutationResult =
Apollo.MutationResult<CreateShotShareLinkMutation>;
export type CreateShotShareLinkMutationOptions = Apollo.BaseMutationOptions<
CreateShotShareLinkMutation,
CreateShotShareLinkMutationVariables
>;
export const VideoPlayerClustersDocument = gql`
query VideoPlayerClusters($videoId: Int!) {
videoPlayerClusters(videoId: $videoId) {
@@ -17148,6 +17260,8 @@ export const GetUsageStatsDocument = gql`
activationCohort
activationActivated
payingUsers
pastDueUsers
newPaidToday
newPaid7d
newPaid30d
retentionPrevActive
@@ -17159,6 +17273,7 @@ export const GetUsageStatsDocument = gql`
activated7d
paid7d
activatedNotPaid7d
paidTotal
}
totalUsers
totalVideos

View File

@@ -1,12 +1,19 @@
# Lightweight two-step picker data for Shot Lab's video-first source: the
# caller's recent videos (completed or still uploading), then that video's
# shots. The stream fields hint at "actively updating" without gating.
query GetShotLabVideos($userId: Int!, $limit: Int! = 25) {
# Lightweight two-step picker data for Shot Lab's video-first source: recent
# videos from the requested audience (mine, followed users, or public), then
# that video's shots. The stream fields hint at "actively updating" without
# gating.
query GetShotLabVideos(
$feedInput: VideoFeedInputGQL!
$includePrivate: IncludePrivateEnum!
$limit: Int! = 25
$after: String = null
) {
getFeedVideos(
limit: $limit
after: $after
includeCallersVideos: false
includePrivate: MINE
feedInput: { userId: $userId }
includePrivate: $includePrivate
feedInput: $feedInput
) {
videos {
id
@@ -26,6 +33,10 @@ query GetShotLabVideos($userId: Int!, $limit: Int! = 25) {
lastSegmentUploadedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}

View File

@@ -0,0 +1,8 @@
mutation CreateShotShareLink($shotIds: [Int!]!, $paddingSeconds: Float = null) {
createShotShareLink(
input: { shotIds: $shotIds, paddingSeconds: $paddingSeconds }
) {
slug
url
}
}

View File

@@ -7,6 +7,8 @@ query getUsageStats($days: Int! = 30) {
activationCohort
activationActivated
payingUsers
pastDueUsers
newPaidToday
newPaid7d
newPaid30d
retentionPrevActive
@@ -18,6 +20,7 @@ query getUsageStats($days: Int! = 30) {
activated7d
paid7d
activatedNotPaid7d
paidTotal
}
totalUsers
totalVideos

View File

@@ -214,6 +214,7 @@ type CohortStatsGQL {
activated7d: Int!
paid7d: Int!
activatedNotPaid7d: Int!
paidTotal: Int!
}
type CommentGQL {
@@ -249,6 +250,11 @@ input CreatePoolHallCameraInput {
claimTermsText: String = null
}
input CreateShotShareLinkInput {
shotIds: [Int!]!
paddingSeconds: Float = null
}
input CreatePoolHallInput {
name: String!
address: String = null
@@ -259,6 +265,11 @@ input CreatePoolHallInput {
timezone: String = null
}
input CreateShotShareLinkInput {
shotIds: [Int!]!
paddingSeconds: Float = null
}
type CreateSubscriptionResultGQL {
checkoutUrl: String!
sessionId: String!
@@ -459,6 +470,8 @@ type FunnelStatsGQL {
activationCohort: Int!
activationActivated: Int!
payingUsers: Int!
pastDueUsers: Int!
newPaidToday: Int!
newPaid7d: Int!
newPaid30d: Int!
retentionPrevActive: Int!
@@ -706,6 +719,7 @@ type Mutation {
cancelCameraClaimSession(claimSessionId: ID!): CameraClaimSession!
endCameraLease(leaseId: ID!): CameraLease!
extendCameraLease(leaseId: ID!, additionalMinutes: Int! = 60): CameraLease!
createShotShareLink(input: CreateShotShareLinkInput!): ShareLinkGQL!
finalizePlayerAssignments(
input: FinalizePlayerAssignmentsInput!
): [PlayerClusterGQL!]!
@@ -1453,6 +1467,11 @@ enum SessionCoachStateEnum {
FAILED
}
type ShareLinkGQL {
slug: String!
url: String!
}
type ShotAnnotationGQL {
shotId: Int!
type: ShotAnnotationTypeGQL!
@@ -1913,6 +1932,11 @@ type UserSubscriptionStatusGQL {
stripeSubscriptionId: String
}
type ShareLinkGQL {
slug: String!
url: String!
}
type VideoExportJobGQL {
id: Int!
videoId: Int!