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
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
"fast-redact": "3.5.0",
"get-urls": "10.0.1",
"gqlmin": "0.3.2",
"graphql": "16.14.1",
"graphql": "17.0.1",
"graphql-scalars": "1.25.0",
"graphql-upload": "15.0.2",
"handlebars": "4.7.9",
Expand Down Expand Up @@ -279,7 +279,8 @@
]
},
"overrides": {
"url-regex-safe": "4.0.0"
"url-regex-safe": "4.0.0",
"graphql": "17.0.1"
},
"nyc": {
"reporter": [
Expand Down
6 changes: 3 additions & 3 deletions server/graphql/schemaV1.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
Returns true if a custom contribution to Open Collective can be submitted for contributions made to this account
"""
platformContributionAvailable: Boolean!
tiers(

Check notice on line 125 in server/graphql/schemaV1.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector - Schema v1

Field 'CollectiveInterface.tiers' is deprecated

Field 'CollectiveInterface.tiers' is deprecated

Check notice on line 125 in server/graphql/schemaV1.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector - Schema v1

Field 'CollectiveInterface.tiers' has deprecation reason '2025-12-05: Will be deleted soon. Use GraphQL v2'

Field 'CollectiveInterface.tiers' has deprecation reason '2025-12-05: Will be deleted soon. Use GraphQL v2'
id: Int
slug: String
slugs: [String]
Expand All @@ -131,9 +131,9 @@
When true (default), exclude tiers with types that are no longer supported. Use false for edit pages to show all tiers.
"""
onlyValid: Boolean = true
): [Tier]
): [Tier] @deprecated(reason: "2025-12-05: Will be deleted soon. Use GraphQL v2")
orders(status: OrderStatus): [OrderType]
stats: CollectiveStatsType
stats: CollectiveStatsType @deprecated(reason: "2025-07-10: Please use GraphQL V2")

Check notice on line 136 in server/graphql/schemaV1.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector - Schema v1

Field 'CollectiveInterface.stats' is deprecated

Field 'CollectiveInterface.stats' is deprecated

Check notice on line 136 in server/graphql/schemaV1.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector - Schema v1

Field 'CollectiveInterface.stats' has deprecation reason '2025-07-10: Please use GraphQL V2'

Field 'CollectiveInterface.stats' has deprecation reason '2025-07-10: Please use GraphQL V2'

"""
The list of expense types supported by this account
Expand Down Expand Up @@ -200,7 +200,7 @@
Describes the features enabled and available for this collective
"""
features: CollectiveFeatures!
plan: PlanType
plan: PlanType @deprecated(reason: "2026-04-02: Replaced by new pricing")

Check notice on line 203 in server/graphql/schemaV1.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector - Schema v1

Field 'CollectiveInterface.plan' is deprecated

Field 'CollectiveInterface.plan' is deprecated

Check notice on line 203 in server/graphql/schemaV1.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector - Schema v1

Field 'CollectiveInterface.plan' has deprecation reason '2026-04-02: Replaced by new pricing'

Field 'CollectiveInterface.plan' has deprecation reason '2026-04-02: Replaced by new pricing'
contributionPolicy: String

"""
Expand Down
2 changes: 1 addition & 1 deletion server/graphql/schemaV2.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3890,7 +3890,7 @@
): OAuthApplicationCollection

"\n Address. This field is public for hosts, otherwise:\n - Users can see the addresses of the collectives they're admin of; if they are not an admin they can only see the country that the org belong to.\n - Hosts can see the address of organizations submitting expenses to their collectives.\n "
location: Location @deprecated(reason: "2026-04-22: This field has moved to the Organization type")
location: Location

Check warning on line 3893 in server/graphql/schemaV2.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector - Schema v2

Field 'Host.location' is no longer deprecated

Field 'Host.location' is no longer deprecated

Check notice on line 3893 in server/graphql/schemaV2.graphql

View workflow job for this annotation

GitHub Actions / GraphQL Inspector - Schema v2

Deprecation reason was removed from field 'Host.location'

Deprecation reason was removed from field 'Host.location'
categories: [String]!
stats: AccountStats

Expand Down
11 changes: 9 additions & 2 deletions server/graphql/v1/CollectiveInterface.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ export const CollectiveInterfaceType = new GraphQLInterfaceType({
},
tiers: {
type: new GraphQLList(TierType),
deprecationReason: '2025-12-05: Will be deleted soon. Use GraphQL v2',
args: {
id: { type: GraphQLInt },
slug: { type: GraphQLString },
Expand All @@ -368,7 +369,10 @@ export const CollectiveInterfaceType = new GraphQLInterfaceType({
status: { type: OrderStatusType },
},
},
stats: { type: CollectiveStatsType },
stats: {
type: CollectiveStatsType,
deprecationReason: '2025-07-10: Please use GraphQL V2',
},
supportedExpenseTypes: {
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))),
description: 'The list of expense types supported by this account',
Expand Down Expand Up @@ -448,7 +452,10 @@ export const CollectiveInterfaceType = new GraphQLInterfaceType({
type: new GraphQLNonNull(GraphQLCollectiveFeatures),
description: 'Describes the features enabled and available for this collective',
},
plan: { type: PlanType },
plan: {
type: PlanType,
deprecationReason: '2026-04-02: Replaced by new pricing',
},
contributionPolicy: { type: GraphQLString },
categories: {
type: new GraphQLNonNull(new GraphQLList(GraphQLString)),
Expand Down
12 changes: 9 additions & 3 deletions server/graphql/v2/collection/WebhookCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import models from '../../../models';
import { checkRemoteUserCanUseWebhooks } from '../../common/scope-check';
import { Forbidden } from '../../errors';
import { fetchAccountWithReference, GraphQLAccountReferenceInput } from '../input/AccountReferenceInput';
import { CollectionArgs, CollectionFields, GraphQLCollection } from '../interface/Collection';
import {
COLLECTION_DEFAULT_LIMIT,
COLLECTION_DEFAULT_OFFSET,
CollectionArgs,
CollectionFields,
GraphQLCollection,
} from '../interface/Collection';
import { GraphQLWebhook } from '../object/Webhook';

export const GraphQLWebhookCollection = new GraphQLObjectType({
Expand Down Expand Up @@ -33,10 +39,10 @@ export const WebhookCollectionResolver = async (args, req: express.Request) => {

// Check Pagination arguments
if (args.limit <= 0) {
args.limit = CollectionArgs.limit.defaultValue;
args.limit = COLLECTION_DEFAULT_LIMIT;
}
if (args.offset <= 0) {
args.offset = CollectionArgs.offset.defaultValue;
args.offset = COLLECTION_DEFAULT_OFFSET;
}

// Check and Fetch account
Expand Down
2 changes: 1 addition & 1 deletion server/graphql/v2/input/AccountUpdateInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const GraphQLAccountUpdateInput = new GraphQLInputObjectType({
timezone: {
description: 'Timezone of the Event (TZ database format, e.g. UTC or Europe/Berlin)',
type: GraphQLString,
default: 'UTC',
default: { value: 'UTC' },
},
privateInstructions: {
type: GraphQLString,
Expand Down
2 changes: 1 addition & 1 deletion server/graphql/v2/input/EventCreateInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const GraphQLEventCreateInput = new GraphQLInputObjectType({
timezone: {
description: 'Timezone of the Event (TZ database format, e.g. UTC or Europe/Berlin)',
type: new GraphQLNonNull(GraphQLString),
default: 'UTC',
default: { value: 'UTC' },
},
...AccountImagesInputFields,
}),
Expand Down
8 changes: 2 additions & 6 deletions server/graphql/v2/interface/AccountWithContributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { GraphQLContributorCollection } from '../collection/ContributorCollectio
import { GraphQLTierCollection } from '../collection/TierCollection';
import { GraphQLAccountType, GraphQLMemberRole, GraphQLTierType } from '../enum';

import { CollectionArgs } from './Collection';
import { CollectionArgs, collectionLimitArg } from './Collection';

export const AccountWithContributionsFields = {
totalFinancialContributors: {
Expand Down Expand Up @@ -53,11 +53,7 @@ export const AccountWithContributionsFields = {
type: new GraphQLNonNull(GraphQLTierCollection),
args: {
...CollectionArgs,
limit: {
type: new GraphQLNonNull(GraphQLInt),
description: 'The number of results to fetch',
defaultValue: 100,
},
limit: collectionLimitArg(100),
onlyValid: {
type: GraphQLBoolean,
description:
Expand Down
39 changes: 20 additions & 19 deletions server/graphql/v2/interface/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,36 @@ const GraphQLCollection = new GraphQLInterfaceType({
fields: () => CollectionFields,
});

export const COLLECTION_DEFAULT_LIMIT = 10;
export const COLLECTION_DEFAULT_OFFSET = 0;

export const collectionLimitArg = (defaultLimit = COLLECTION_DEFAULT_LIMIT) => ({
type: new GraphQLNonNull(GraphQLInt),
description: 'The number of results to fetch (default 10, max 1000)',
default: { value: defaultLimit },
});

export const collectionOffsetArg = (defaultOffset = COLLECTION_DEFAULT_OFFSET) => ({
type: new GraphQLNonNull(GraphQLInt),
description: 'The offset to use to fetch',
default: { value: defaultOffset },
});

/**
* Types to use as arguments for fields that return types
* that implement the Collection interface.
*/
const CollectionArgs = {
limit: {
type: new GraphQLNonNull(GraphQLInt),
description: 'The number of results to fetch (default 10, max 1000)',
defaultValue: 10,
},
offset: {
type: new GraphQLNonNull(GraphQLInt),
description: 'The offset to use to fetch',
defaultValue: 0,
},
limit: collectionLimitArg(),
offset: collectionOffsetArg(),
};

/**
* A helper to return `CollectionArgs` with custom defaults
*/
export const getCollectionArgs = ({ limit = 10, offset = 0 }) => ({
limit: {
...CollectionArgs.limit,
defaultValue: limit ?? CollectionArgs.limit.defaultValue,
},
offset: {
...CollectionArgs.offset,
defaultValue: offset ?? CollectionArgs.offset.defaultValue,
},
export const getCollectionArgs = ({ limit = COLLECTION_DEFAULT_LIMIT, offset = COLLECTION_DEFAULT_OFFSET } = {}) => ({
limit: collectionLimitArg(limit ?? COLLECTION_DEFAULT_LIMIT),
offset: collectionOffsetArg(offset ?? COLLECTION_DEFAULT_OFFSET),
});

export interface CollectionReturnType<T = unknown> {
Expand Down
1 change: 0 additions & 1 deletion server/graphql/v2/object/Host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1506,7 +1506,6 @@ export const GraphQLHost = new GraphQLObjectType({
},
...mapValues(
pick(getOrganizationFields(), [
'location',
'accountingCategories',
'contributionAccountingCategoryRules',
'hasMoneyManagement',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import assert from 'assert';

Check failure on line 1 in server/graphql/v2/query/collection/ActivitiesCollectionQuery.ts

View workflow job for this annotation

GitHub Actions / lint

Run autofix to sort these imports!

import { GraphQLList, GraphQLNonNull } from 'graphql';
import { GraphQLBoolean } from 'graphql/type';
Expand All @@ -24,12 +24,12 @@
CHRONOLOGICAL_ORDER_INPUT_DEFAULT_VALUE,
GraphQLChronologicalOrderInput,
} from '../../input/ChronologicalOrderInput';
import { CollectionArgs, CollectionReturnType } from '../../interface/Collection';
import { CollectionArgs, CollectionReturnType, collectionLimitArg } from '../../interface/Collection';

const IGNORED_ACTIVITIES: string[] = [ActivityTypes.COLLECTIVE_TRANSACTION_CREATED]; // This activity is creating a lot of noise, is usually covered already by orders/expenses activities and is not properly categorized (see https://github.com/opencollective/opencollective/issues/5903)

const ActivitiesCollectionArgs = {
limit: { ...CollectionArgs.limit, defaultValue: 100 },
limit: collectionLimitArg(100),
offset: CollectionArgs.offset,
individual: {
type: GraphQLAccountReferenceInput,
Expand Down
7 changes: 4 additions & 3 deletions server/graphql/v2/query/collection/CommunityQuery.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'assert';

import type Express from 'express';
import { GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString } from 'graphql';
import { GraphQLList, GraphQLNonNull, GraphQLString } from 'graphql';
import { sql } from 'kysely';
import { isNil } from 'lodash';

Expand All @@ -18,6 +18,7 @@ import { GraphQLCommunityRelationType } from '../../enum/CommunityRelationType';
import { fetchAccountWithReference, GraphQLAccountReferenceInput } from '../../input/AccountReferenceInput';
import { getAmountRangeQuery, GraphQLAmountRangeInput } from '../../input/AmountRangeInput';
import { GraphQLOrderByInput } from '../../input/OrderByInput';
import { collectionLimitArg, collectionOffsetArg } from '../../interface/Collection';

const DEFAULT_LIMIT = 100;

Expand Down Expand Up @@ -255,8 +256,8 @@ const CommunityQuery = {
type: GraphQLAmountRangeInput,
description: 'Only return accounts that expended within this amount range',
},
limit: { type: new GraphQLNonNull(GraphQLInt), defaultValue: DEFAULT_LIMIT },
offset: { type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 },
limit: collectionLimitArg(DEFAULT_LIMIT),
offset: collectionOffsetArg(0),
},
async resolve(_: void, args, req: Express.Request) {
enforceScope(req, 'host');
Expand Down
4 changes: 2 additions & 2 deletions server/graphql/v2/query/collection/OrdersCollectionQuery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import assert from 'assert';

Check failure on line 1 in server/graphql/v2/query/collection/OrdersCollectionQuery.ts

View workflow job for this annotation

GitHub Actions / lint

Run autofix to sort these imports!

import express from 'express';
import { GraphQLBoolean, GraphQLEnumType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString } from 'graphql';
Expand Down Expand Up @@ -52,7 +52,7 @@
GraphQLPaymentMethodReferenceInput,
} from '../../input/PaymentMethodReferenceInput';
import { getDatabaseIdFromTierReference, GraphQLTierReferenceInput } from '../../input/TierReferenceInput';
import { CollectionArgs, CollectionReturnType } from '../../interface/Collection';
import { CollectionArgs, CollectionReturnType, collectionLimitArg } from '../../interface/Collection';
import { UncategorizedValue } from '../../object/AccountingCategory';

/**
Expand Down Expand Up @@ -153,7 +153,7 @@
};

export const OrdersCollectionArgs = {
limit: { ...CollectionArgs.limit, defaultValue: 100 },
limit: collectionLimitArg(100),
offset: CollectionArgs.offset,
accountingCategory: {
type: new GraphQLList(GraphQLString),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import express from 'express';

Check failure on line 1 in server/graphql/v2/query/collection/TransactionGroupCollectionQuery.ts

View workflow job for this annotation

GitHub Actions / lint

Run autofix to sort these imports!
import { GraphQLNonNull } from 'graphql';
import { GraphQLDateTime } from 'graphql-scalars';
import { isNil } from 'lodash';
Expand All @@ -13,12 +13,12 @@
import { GraphQLTransactionKind } from '../../enum/TransactionKind';
import { GraphQLTransactionType } from '../../enum/TransactionType';
import { fetchAccountWithReference, GraphQLAccountReferenceInput } from '../../input/AccountReferenceInput';
import { CollectionArgs, CollectionReturnType } from '../../interface/Collection';
import { CollectionArgs, CollectionReturnType, collectionLimitArg } from '../../interface/Collection';

import { getTransactionKindPriorityCase } from './TransactionsCollectionQuery';

export const TransactionGroupCollectionArgs = {
limit: { ...CollectionArgs.limit, defaultValue: 100 },
limit: collectionLimitArg(100),
offset: CollectionArgs.offset,
type: {
type: GraphQLTransactionType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
GraphQLPayoutMethodReferenceInput,
} from '../../input/PayoutMethodReferenceInput';
import { GraphQLVirtualCardReferenceInput } from '../../input/VirtualCardReferenceInput';
import { CollectionArgs } from '../../interface/Collection';
import { CollectionArgs, collectionLimitArg } from '../../interface/Collection';

const oneDayInSeconds = 60 * 60 * 24;

Expand All @@ -84,7 +84,7 @@ export const getTransactionKindPriorityCase = tableName => `
END`;

export const TransactionsCollectionArgs = {
limit: { ...CollectionArgs.limit, defaultValue: 100 },
limit: collectionLimitArg(100),
offset: CollectionArgs.offset,
type: {
type: GraphQLTransactionType,
Expand Down
2 changes: 1 addition & 1 deletion test/server/graphql/v2/mutation/AccountMutations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ describe('server/graphql/v2/mutation/AccountMutations', () => {
);

expect(result.errors).to.exist;
expect(result.errors[0].message).to.match(/Variable "\$key" got invalid value "anInvalidKey\!"/);
expect(result.errors[0].message).to.match(/Variable "\$key" has invalid value.*anInvalidKey!/);
});

it('can set nested values', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ describe('server/graphql/v2/mutation/HostApplicationMutations', () => {
const result = await graphqlQueryV2(REMOVE_HOST_MUTATION, {});
expect(result.errors).to.exist;
expect(result.errors[0].message).to.equal(
'Variable "$account" of required type "AccountReferenceInput!" was not provided.',
'Variable "$account" has invalid value: Expected a value of non-null type "AccountReferenceInput!" to be provided.',
);
});

Expand Down
2 changes: 1 addition & 1 deletion test/server/graphql/v2/mutation/RootMutations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('server/graphql/v2/mutation/RootMutations', () => {
const result = await callMoveExpenseMutation({ expenses: [] }, rootUser);
expect(result.errors).to.exist;
expect(result.errors[0].message).to.equal(
'Variable "$destinationAccount" of required type "AccountReferenceInput!" was not provided.',
'Variable "$destinationAccount" has invalid value: Expected a value of non-null type "AccountReferenceInput!" to be provided.',
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1404,7 +1404,7 @@ describe('server/graphql/v2/collection/ExpenseCollection', () => {
const result = await graphqlQueryV2(expensesQuery, queryParams);
expect(result.errors).to.exist;
// GraphQL validation error happens before resolver
expect(result.errors[0].message).to.include('Variable "$activity" got invalid value');
expect(result.errors[0].message).to.include('Variable "$activity" has invalid value');
});

it('Throws error when multiple invalid activity types are provided', async () => {
Expand All @@ -1415,7 +1415,7 @@ describe('server/graphql/v2/collection/ExpenseCollection', () => {
const result = await graphqlQueryV2(expensesQuery, queryParams);
expect(result.errors).to.exist;
// GraphQL validation error happens before resolver
expect(result.errors[0].message).to.include('Variable "$activity" got invalid value');
expect(result.errors[0].message).to.include('Variable "$activity" has invalid value');
});

it('Throws error when activity type is not an expense-related activity', async () => {
Expand Down
4 changes: 3 additions & 1 deletion test/server/paymentProviders/opencollective/giftcard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,9 @@ describe('server/paymentProviders/opencollective/giftcard', () => {
// call graphql mutation
const gqlResult = await utils.graphqlQuery(createGiftCardsMutation, args, user1);
expect(gqlResult.errors[0]).to.exist;
expect(gqlResult.errors[0].toString()).to.contain('"$currency" of required type "String!" was not provided.');
expect(gqlResult.errors[0].message).to.contain(
'Variable "$currency" has invalid value: Expected a value of non-null type "String!" to be provided.',
);
}); /** End of "should fail creating a gift card because there is no currency defined" */

it('should fail creating a gift card because there is no amount or monthlyLimitPerMember defined', async () => {
Expand Down
Loading