diff --git a/src/lib/db/project-store.ts b/src/lib/db/project-store.ts index 5eadd68b93..a66e83dc03 100644 --- a/src/lib/db/project-store.ts +++ b/src/lib/db/project-store.ts @@ -107,7 +107,7 @@ class ProjectStore implements IProjectStore { } let selectColumns = [ this.db.raw( - 'projects.id, projects.name, projects.description, projects.health, projects.updated_at, count(features.name) FILTER (WHERE features.archived_at is null) AS number_of_features', + 'projects.id, projects.name, projects.description, projects.health, projects.updated_at, projects.created_at, count(features.name) FILTER (WHERE features.archived_at is null) AS number_of_features', ), ] as (string | Raw)[]; @@ -164,6 +164,7 @@ class ProjectStore implements IProjectStore { featureCount: Number(row.number_of_features) || 0, memberCount: Number(row.number_of_users) || 0, updatedAt: row.updated_at, + createdAt: row.created_at, mode: 'open', defaultStickiness: 'default', }; diff --git a/src/lib/openapi/spec/health-overview-schema.ts b/src/lib/openapi/spec/health-overview-schema.ts index 2353d427c7..d6bcfa225d 100644 --- a/src/lib/openapi/spec/health-overview-schema.ts +++ b/src/lib/openapi/spec/health-overview-schema.ts @@ -91,6 +91,13 @@ export const healthOverviewSchema = { description: 'When the project was last updated.', example: '2023-04-19T08:15:14.000Z', }, + createdAt: { + type: 'string', + format: 'date-time', + nullable: true, + description: 'When the project was last updated.', + example: '2023-04-19T08:15:14.000Z', + }, favorite: { type: 'boolean', description: diff --git a/src/lib/openapi/spec/project-overview-schema.ts b/src/lib/openapi/spec/project-overview-schema.ts index 0832e30232..c1b29188fc 100644 --- a/src/lib/openapi/spec/project-overview-schema.ts +++ b/src/lib/openapi/spec/project-overview-schema.ts @@ -98,6 +98,12 @@ export const projectOverviewSchema = { nullable: true, example: '2023-02-10T08:36:35.262Z', }, + createdAt: { + type: 'string', + format: 'date-time', + nullable: true, + example: '2023-02-10T08:36:35.262Z', + }, favorite: { type: 'boolean', example: true, diff --git a/src/lib/services/project-service.ts b/src/lib/services/project-service.ts index 144df1602e..6d8cac7d3b 100644 --- a/src/lib/services/project-service.ts +++ b/src/lib/services/project-service.ts @@ -840,6 +840,7 @@ export default class ProjectService { health: project.health || 0, favorite: favorite, updatedAt: project.updatedAt, + createdAt: project.createdAt, environments, features, members, diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index 3a24ebd9f7..a7a3f78138 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -193,6 +193,7 @@ export interface IProjectOverview { health: number; favorite?: boolean; updatedAt?: Date; + createdAt: Date | undefined; stats?: IProjectStats; mode: ProjectMode; diff --git a/src/test/e2e/api/admin/project/projects.e2e.test.ts b/src/test/e2e/api/admin/project/projects.e2e.test.ts index c83093d480..e5089d1997 100644 --- a/src/test/e2e/api/admin/project/projects.e2e.test.ts +++ b/src/test/e2e/api/admin/project/projects.e2e.test.ts @@ -42,3 +42,19 @@ test('Should ONLY return default project', async () => { expect(body.projects).toHaveLength(1); expect(body.projects[0].id).toBe('default'); }); + +test('response should include created_at', async () => { + const { body } = await app.request + .get('/api/admin/projects') + .expect('Content-Type', /json/) + .expect(200); + expect(body.projects[0].createdAt).toBeDefined(); +}); + +test('response for default project should include created_at', async () => { + const { body } = await app.request + .get('/api/admin/projects/default') + .expect('Content-Type', /json/) + .expect(200); + expect(body.createdAt).toBeDefined(); +}); diff --git a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap index 159f949b83..e5b3ee4dc0 100644 --- a/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap +++ b/src/test/e2e/api/openapi/__snapshots__/openapi.e2e.test.ts.snap @@ -2758,6 +2758,13 @@ The provider you choose for your addon dictates what properties the \`parameters "additionalProperties": false, "description": "An overview of a project's stats and its health as described in the documentation on [technical debt](https://docs.getunleash.io/reference/technical-debt)", "properties": { + "createdAt": { + "description": "When the project was last updated.", + "example": "2023-04-19T08:15:14.000Z", + "format": "date-time", + "nullable": true, + "type": "string", + }, "defaultStickiness": { "description": "A default stickiness for the project affecting the default stickiness value for variants and Gradual Rollout strategy", "example": "userId", @@ -2851,6 +2858,13 @@ The provider you choose for your addon dictates what properties the \`parameters "example": 2, "type": "number", }, + "createdAt": { + "description": "When the project was last updated.", + "example": "2023-04-19T08:15:14.000Z", + "format": "date-time", + "nullable": true, + "type": "string", + }, "defaultStickiness": { "description": "A default stickiness for the project affecting the default stickiness value for variants and Gradual Rollout strategy", "example": "userId", @@ -3780,6 +3794,12 @@ The provider you choose for your addon dictates what properties the \`parameters "additionalProperties": false, "description": "A high-level overview of a project. It contains information such as project statistics, the name of the project, what members and what features it contains, etc.", "properties": { + "createdAt": { + "example": "2023-02-10T08:36:35.262Z", + "format": "date-time", + "nullable": true, + "type": "string", + }, "defaultStickiness": { "description": "A default stickiness for the project affecting the default stickiness value for variants and Gradual Rollout strategy", "example": "userId",