@backstage-community/plugin-copilot-backend 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @backstage-community/plugin-copilot-backend
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 10cacf8: Backend updated for using the new /metrics API that Github Provides.
8
+ Added new tables to the database to store all metrics provided by Github.
9
+
10
+ New metrics output from the backend are currently made compatible with the
11
+ old format expected by the frontend in order to make minimum amount of changes
12
+ in this version.
13
+
14
+ The Backend router merges the old saved metrics with the new if the selected
15
+ date range overlaps both old and new metrics. Otherwise it selects from eiter
16
+ old or new.
17
+
18
+ It also fetches the maximum availible date range taking into account that
19
+ old metrics and/or new metrics are availible from the database.
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies [10cacf8]
24
+ - @backstage-community/plugin-copilot-common@0.6.0
25
+
26
+ ## 0.5.0
27
+
28
+ ### Minor Changes
29
+
30
+ - f5be5aa: Backstage version bump to v1.35.1
31
+
32
+ ### Patch Changes
33
+
34
+ - Updated dependencies [f5be5aa]
35
+ - @backstage-community/plugin-copilot-common@0.5.0
36
+
3
37
  ## 0.4.0
4
38
 
5
39
  ### Minor Changes
@@ -20,24 +20,24 @@ class GithubClient {
20
20
  async getCredentials() {
21
21
  return await GithubUtils.getGithubCredentials(this.config, this.copilotConfig);
22
22
  }
23
- async fetchEnterpriseCopilotUsage() {
24
- const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/usage`;
23
+ async fetchEnterpriseCopilotMetrics() {
24
+ const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/metrics`;
25
25
  return this.get(path);
26
26
  }
27
- async fetchEnterpriseTeamCopilotUsage(teamId) {
28
- const path = `/enterprises/${this.copilotConfig.enterprise}/team/${teamId}/copilot/usage`;
27
+ async fetchEnterpriseTeamCopilotMetrics(teamId) {
28
+ const path = `/enterprises/${this.copilotConfig.enterprise}/team/${teamId}/copilot/metrics`;
29
29
  return this.get(path);
30
30
  }
31
31
  async fetchEnterpriseTeams() {
32
32
  const path = `/enterprises/${this.copilotConfig.enterprise}/teams`;
33
33
  return this.get(path);
34
34
  }
35
- async fetchOrganizationCopilotUsage() {
36
- const path = `/orgs/${this.copilotConfig.organization}/copilot/usage`;
35
+ async fetchOrganizationCopilotMetrics() {
36
+ const path = `/orgs/${this.copilotConfig.organization}/copilot/metrics`;
37
37
  return this.get(path);
38
38
  }
39
- async fetchOrganizationTeamCopilotUsage(teamId) {
40
- const path = `/orgs/${this.copilotConfig.organization}/team/${teamId}/copilot/usage`;
39
+ async fetchOrganizationTeamCopilotMetrics(teamId) {
40
+ const path = `/orgs/${this.copilotConfig.organization}/team/${teamId}/copilot/metrics`;
41
41
  return this.get(path);
42
42
  }
43
43
  async fetchOrganizationTeams() {
@@ -1 +1 @@
1
- {"version":3,"file":"GithubClient.cjs.js","sources":["../../src/client/GithubClient.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResponseError } from '@backstage/errors';\nimport { Config } from '@backstage/config';\nimport { Metric, TeamInfo } from '@backstage-community/plugin-copilot-common';\nimport fetch from 'node-fetch';\nimport {\n CopilotConfig,\n CopilotCredentials,\n getCopilotConfig,\n getGithubCredentials,\n} from '../utils/GithubUtils';\n\ninterface GithubApi {\n fetchEnterpriseCopilotUsage: () => Promise<Metric[]>;\n fetchEnterpriseTeamCopilotUsage: (teamId: string) => Promise<Metric[]>;\n fetchEnterpriseTeams: () => Promise<TeamInfo[]>;\n fetchOrganizationCopilotUsage: () => Promise<Metric[]>;\n fetchOrganizationTeamCopilotUsage: (teamId: string) => Promise<Metric[]>;\n fetchOrganizationTeams: () => Promise<TeamInfo[]>;\n}\n\nexport class GithubClient implements GithubApi {\n constructor(\n private readonly copilotConfig: CopilotConfig,\n private readonly config: Config,\n ) {}\n\n static async fromConfig(config: Config) {\n const info = getCopilotConfig(config);\n return new GithubClient(info, config);\n }\n\n private async getCredentials(): Promise<CopilotCredentials> {\n return await getGithubCredentials(this.config, this.copilotConfig);\n }\n\n async fetchEnterpriseCopilotUsage(): Promise<Metric[]> {\n const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/usage`;\n return this.get(path);\n }\n\n async fetchEnterpriseTeamCopilotUsage(teamId: string): Promise<Metric[]> {\n const path = `/enterprises/${this.copilotConfig.enterprise}/team/${teamId}/copilot/usage`;\n return this.get(path);\n }\n\n async fetchEnterpriseTeams(): Promise<TeamInfo[]> {\n const path = `/enterprises/${this.copilotConfig.enterprise}/teams`;\n return this.get(path);\n }\n\n async fetchOrganizationCopilotUsage(): Promise<Metric[]> {\n const path = `/orgs/${this.copilotConfig.organization}/copilot/usage`;\n return this.get(path);\n }\n\n async fetchOrganizationTeamCopilotUsage(teamId: string): Promise<Metric[]> {\n const path = `/orgs/${this.copilotConfig.organization}/team/${teamId}/copilot/usage`;\n return this.get(path);\n }\n\n async fetchOrganizationTeams(): Promise<TeamInfo[]> {\n const path = `/orgs/${this.copilotConfig.organization}/teams`;\n return this.get(path);\n }\n\n private async get<T>(path: string): Promise<T> {\n const credentials = await this.getCredentials();\n const headers = path.startsWith('/enterprises')\n ? credentials.enterprise?.headers\n : credentials.organization?.headers;\n\n const response = await fetch(`${this.copilotConfig.apiBaseUrl}${path}`, {\n headers: {\n ...headers,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json() as Promise<T>;\n }\n}\n"],"names":["getCopilotConfig","getGithubCredentials","fetch","ResponseError"],"mappings":";;;;;;;;;;AAoCO,MAAM,YAAkC,CAAA;AAAA,EAC7C,WAAA,CACmB,eACA,MACjB,EAAA;AAFiB,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA;AAChB,EAEH,aAAa,WAAW,MAAgB,EAAA;AACtC,IAAM,MAAA,IAAA,GAAOA,6BAAiB,MAAM,CAAA;AACpC,IAAO,OAAA,IAAI,YAAa,CAAA,IAAA,EAAM,MAAM,CAAA;AAAA;AACtC,EAEA,MAAc,cAA8C,GAAA;AAC1D,IAAA,OAAO,MAAMC,gCAAA,CAAqB,IAAK,CAAA,MAAA,EAAQ,KAAK,aAAa,CAAA;AAAA;AACnE,EAEA,MAAM,2BAAiD,GAAA;AACrD,IAAA,MAAM,IAAO,GAAA,CAAA,aAAA,EAAgB,IAAK,CAAA,aAAA,CAAc,UAAU,CAAA,cAAA,CAAA;AAC1D,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,gCAAgC,MAAmC,EAAA;AACvE,IAAA,MAAM,OAAO,CAAgB,aAAA,EAAA,IAAA,CAAK,aAAc,CAAA,UAAU,SAAS,MAAM,CAAA,cAAA,CAAA;AACzE,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,oBAA4C,GAAA;AAChD,IAAA,MAAM,IAAO,GAAA,CAAA,aAAA,EAAgB,IAAK,CAAA,aAAA,CAAc,UAAU,CAAA,MAAA,CAAA;AAC1D,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,6BAAmD,GAAA;AACvD,IAAA,MAAM,IAAO,GAAA,CAAA,MAAA,EAAS,IAAK,CAAA,aAAA,CAAc,YAAY,CAAA,cAAA,CAAA;AACrD,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,kCAAkC,MAAmC,EAAA;AACzE,IAAA,MAAM,OAAO,CAAS,MAAA,EAAA,IAAA,CAAK,aAAc,CAAA,YAAY,SAAS,MAAM,CAAA,cAAA,CAAA;AACpE,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,sBAA8C,GAAA;AAClD,IAAA,MAAM,IAAO,GAAA,CAAA,MAAA,EAAS,IAAK,CAAA,aAAA,CAAc,YAAY,CAAA,MAAA,CAAA;AACrD,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAc,IAAO,IAA0B,EAAA;AAC7C,IAAM,MAAA,WAAA,GAAc,MAAM,IAAA,CAAK,cAAe,EAAA;AAC9C,IAAM,MAAA,OAAA,GAAU,KAAK,UAAW,CAAA,cAAc,IAC1C,WAAY,CAAA,UAAA,EAAY,OACxB,GAAA,WAAA,CAAY,YAAc,EAAA,OAAA;AAE9B,IAAM,MAAA,QAAA,GAAW,MAAMC,sBAAM,CAAA,CAAA,EAAG,KAAK,aAAc,CAAA,UAAU,CAAG,EAAA,IAAI,CAAI,CAAA,EAAA;AAAA,MACtE,OAAS,EAAA;AAAA,QACP,GAAG,OAAA;AAAA,QACH,MAAQ,EAAA,6BAAA;AAAA,QACR,sBAAwB,EAAA;AAAA;AAC1B,KACD,CAAA;AAED,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,MAAMC,oBAAc,CAAA,YAAA,CAAa,QAAQ,CAAA;AAAA;AAGjD,IAAA,OAAO,SAAS,IAAK,EAAA;AAAA;AAEzB;;;;"}
1
+ {"version":3,"file":"GithubClient.cjs.js","sources":["../../src/client/GithubClient.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResponseError } from '@backstage/errors';\nimport { Config } from '@backstage/config';\nimport {\n CopilotMetrics,\n TeamInfo,\n} from '@backstage-community/plugin-copilot-common';\nimport fetch from 'node-fetch';\nimport {\n CopilotConfig,\n CopilotCredentials,\n getCopilotConfig,\n getGithubCredentials,\n} from '../utils/GithubUtils';\n\ninterface GithubApi {\n fetchEnterpriseCopilotMetrics: () => Promise<CopilotMetrics[]>;\n fetchEnterpriseTeamCopilotMetrics: (\n teamId: string,\n ) => Promise<CopilotMetrics[]>;\n fetchOrganizationCopilotMetrics: () => Promise<CopilotMetrics[]>;\n fetchOrganizationTeamCopilotMetrics: (\n teamId: string,\n ) => Promise<CopilotMetrics[]>;\n\n fetchEnterpriseTeams: () => Promise<TeamInfo[]>;\n fetchOrganizationTeams: () => Promise<TeamInfo[]>;\n}\n\nexport class GithubClient implements GithubApi {\n constructor(\n private readonly copilotConfig: CopilotConfig,\n private readonly config: Config,\n ) {}\n\n static async fromConfig(config: Config) {\n const info = getCopilotConfig(config);\n return new GithubClient(info, config);\n }\n\n private async getCredentials(): Promise<CopilotCredentials> {\n return await getGithubCredentials(this.config, this.copilotConfig);\n }\n\n async fetchEnterpriseCopilotMetrics(): Promise<CopilotMetrics[]> {\n const path = `/enterprises/${this.copilotConfig.enterprise}/copilot/metrics`;\n return this.get(path);\n }\n\n async fetchEnterpriseTeamCopilotMetrics(\n teamId: string,\n ): Promise<CopilotMetrics[]> {\n const path = `/enterprises/${this.copilotConfig.enterprise}/team/${teamId}/copilot/metrics`;\n return this.get(path);\n }\n\n async fetchEnterpriseTeams(): Promise<TeamInfo[]> {\n const path = `/enterprises/${this.copilotConfig.enterprise}/teams`;\n return this.get(path);\n }\n\n async fetchOrganizationCopilotMetrics(): Promise<CopilotMetrics[]> {\n const path = `/orgs/${this.copilotConfig.organization}/copilot/metrics`;\n return this.get(path);\n }\n\n async fetchOrganizationTeamCopilotMetrics(\n teamId: string,\n ): Promise<CopilotMetrics[]> {\n const path = `/orgs/${this.copilotConfig.organization}/team/${teamId}/copilot/metrics`;\n return this.get(path);\n }\n\n async fetchOrganizationTeams(): Promise<TeamInfo[]> {\n const path = `/orgs/${this.copilotConfig.organization}/teams`;\n return this.get(path);\n }\n\n private async get<T>(path: string): Promise<T> {\n const credentials = await this.getCredentials();\n const headers = path.startsWith('/enterprises')\n ? credentials.enterprise?.headers\n : credentials.organization?.headers;\n\n const response = await fetch(`${this.copilotConfig.apiBaseUrl}${path}`, {\n headers: {\n ...headers,\n Accept: 'application/vnd.github+json',\n 'X-GitHub-Api-Version': '2022-11-28',\n },\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json() as Promise<T>;\n }\n}\n"],"names":["getCopilotConfig","getGithubCredentials","fetch","ResponseError"],"mappings":";;;;;;;;;;AA4CO,MAAM,YAAkC,CAAA;AAAA,EAC7C,WAAA,CACmB,eACA,MACjB,EAAA;AAFiB,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA;AAChB,EAEH,aAAa,WAAW,MAAgB,EAAA;AACtC,IAAM,MAAA,IAAA,GAAOA,6BAAiB,MAAM,CAAA;AACpC,IAAO,OAAA,IAAI,YAAa,CAAA,IAAA,EAAM,MAAM,CAAA;AAAA;AACtC,EAEA,MAAc,cAA8C,GAAA;AAC1D,IAAA,OAAO,MAAMC,gCAAA,CAAqB,IAAK,CAAA,MAAA,EAAQ,KAAK,aAAa,CAAA;AAAA;AACnE,EAEA,MAAM,6BAA2D,GAAA;AAC/D,IAAA,MAAM,IAAO,GAAA,CAAA,aAAA,EAAgB,IAAK,CAAA,aAAA,CAAc,UAAU,CAAA,gBAAA,CAAA;AAC1D,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,kCACJ,MAC2B,EAAA;AAC3B,IAAA,MAAM,OAAO,CAAgB,aAAA,EAAA,IAAA,CAAK,aAAc,CAAA,UAAU,SAAS,MAAM,CAAA,gBAAA,CAAA;AACzE,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,oBAA4C,GAAA;AAChD,IAAA,MAAM,IAAO,GAAA,CAAA,aAAA,EAAgB,IAAK,CAAA,aAAA,CAAc,UAAU,CAAA,MAAA,CAAA;AAC1D,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,+BAA6D,GAAA;AACjE,IAAA,MAAM,IAAO,GAAA,CAAA,MAAA,EAAS,IAAK,CAAA,aAAA,CAAc,YAAY,CAAA,gBAAA,CAAA;AACrD,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,oCACJ,MAC2B,EAAA;AAC3B,IAAA,MAAM,OAAO,CAAS,MAAA,EAAA,IAAA,CAAK,aAAc,CAAA,YAAY,SAAS,MAAM,CAAA,gBAAA,CAAA;AACpE,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAM,sBAA8C,GAAA;AAClD,IAAA,MAAM,IAAO,GAAA,CAAA,MAAA,EAAS,IAAK,CAAA,aAAA,CAAc,YAAY,CAAA,MAAA,CAAA;AACrD,IAAO,OAAA,IAAA,CAAK,IAAI,IAAI,CAAA;AAAA;AACtB,EAEA,MAAc,IAAO,IAA0B,EAAA;AAC7C,IAAM,MAAA,WAAA,GAAc,MAAM,IAAA,CAAK,cAAe,EAAA;AAC9C,IAAM,MAAA,OAAA,GAAU,KAAK,UAAW,CAAA,cAAc,IAC1C,WAAY,CAAA,UAAA,EAAY,OACxB,GAAA,WAAA,CAAY,YAAc,EAAA,OAAA;AAE9B,IAAM,MAAA,QAAA,GAAW,MAAMC,sBAAM,CAAA,CAAA,EAAG,KAAK,aAAc,CAAA,UAAU,CAAG,EAAA,IAAI,CAAI,CAAA,EAAA;AAAA,MACtE,OAAS,EAAA;AAAA,QACP,GAAG,OAAA;AAAA,QACH,MAAQ,EAAA,6BAAA;AAAA,QACR,sBAAwB,EAAA;AAAA;AAC1B,KACD,CAAA;AAED,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAM,MAAA,MAAMC,oBAAc,CAAA,YAAA,CAAa,QAAQ,CAAA;AAAA;AAGjD,IAAA,OAAO,SAAS,IAAK,EAAA;AAAA;AAEzB;;;;"}
@@ -27,13 +27,55 @@ class DatabaseHandler {
27
27
  if (!minDate?.day || !maxDate?.day) return void 0;
28
28
  return { minDate: minDate.day, maxDate: maxDate.day };
29
29
  }
30
+ async getPeriodRangeV2(type) {
31
+ const query = this.db("copilot_metrics").where("type", type);
32
+ const minDate = await query.orderBy("day", "asc").first("day");
33
+ const maxDate = await query.orderBy("day", "desc").first("day");
34
+ if (!minDate?.day || !maxDate?.day) return void 0;
35
+ return { minDate: minDate.day, maxDate: maxDate.day };
36
+ }
30
37
  async getTeams(type, startDate, endDate) {
31
- const result = await this.db("metrics").where("type", type).whereBetween("day", [startDate, endDate]).whereNotNull("team_name").distinct("team_name").orderBy("team_name", "asc").select("team_name");
38
+ const result = await this.db("copilot_metrics").where("type", type).whereBetween("day", [startDate, endDate]).whereNotNull("team_name").distinct("team_name").orderBy("team_name", "asc").select("team_name");
32
39
  return result.map((x) => x.team_name);
33
40
  }
34
41
  async batchInsert(metrics) {
35
42
  await this.db("metrics").insert(metrics).onConflict(["day", "type", "team_name"]).ignore();
36
43
  }
44
+ async batchInsertMetrics(metrics) {
45
+ await this.db("copilot_metrics").insert(metrics).onConflict(["day", "type", "team_name"]).ignore();
46
+ }
47
+ async batchInsertIdeCompletions(metrics) {
48
+ await this.db("ide_completions").insert(metrics).onConflict(["day", "type", "team_name"]).ignore();
49
+ }
50
+ async batchInsertIdeCompletionsLanguages(metrics) {
51
+ await this.db(
52
+ "ide_completions_language_users"
53
+ ).insert(metrics).onConflict(["day", "type", "team_name", "language"]).ignore();
54
+ }
55
+ async batchInsertIdeCompletionsEditors(metrics) {
56
+ await this.db(
57
+ "ide_completions_language_editors"
58
+ ).insert(metrics).onConflict(["day", "type", "team_name", "editor"]).ignore();
59
+ }
60
+ async batchInsertIdeCompletionsEditorModels(metrics) {
61
+ await this.db(
62
+ "ide_completions_language_editors_model"
63
+ ).insert(metrics).onConflict(["day", "type", "team_name", "editor", "model"]).ignore();
64
+ }
65
+ async batchInsertIdeCompletionsEditorModelLanguages(metrics) {
66
+ await this.db(
67
+ "ide_completions_language_editors_model_language"
68
+ ).insert(metrics).onConflict(["day", "type", "team_name", "editor", "model", "language"]).ignore();
69
+ }
70
+ async batchInsertIdeChats(metrics) {
71
+ await this.db("ide_chats").insert(metrics).onConflict(["day", "type", "team_name"]).ignore();
72
+ }
73
+ async batchInsertIdeChatEditors(metrics) {
74
+ await this.db("ide_chat_editors").insert(metrics).onConflict(["day", "type", "team_name", "editor"]).ignore();
75
+ }
76
+ async batchInsertIdeChatEditorModels(metrics) {
77
+ await this.db("ide_chat_editors_model").insert(metrics).onConflict(["day", "type", "team_name", "editor", "model"]).ignore();
78
+ }
37
79
  async getMostRecentDayFromMetrics(type, teamName) {
38
80
  try {
39
81
  const query = await this.db("metrics").where("type", type).where("team_name", teamName ?? null).orderBy("day", "desc").first("day");
@@ -42,13 +84,156 @@ class DatabaseHandler {
42
84
  return void 0;
43
85
  }
44
86
  }
87
+ async getMostRecentDayFromMetricsV2(type, teamName) {
88
+ try {
89
+ const query = this.db("copilot_metrics").where("type", type).where("team_name", teamName ?? null).orderBy("day", "desc").first("day");
90
+ const res = await query;
91
+ return res ? res.day : void 0;
92
+ } catch (e) {
93
+ return void 0;
94
+ }
95
+ }
96
+ async getEarliestDayFromMetricsV2(type, teamName) {
97
+ try {
98
+ const query = this.db("copilot_metrics").where("type", type).where("team_name", teamName ?? null).orderBy("day", "asc").first("day");
99
+ const res = await query;
100
+ return res ? res.day : void 0;
101
+ } catch (e) {
102
+ return void 0;
103
+ }
104
+ }
45
105
  async getMetrics(startDate, endDate, type, teamName) {
46
- console.log(startDate, endDate, type, teamName);
47
106
  if (teamName) {
48
107
  return await this.db("metrics").where("type", type).where("team_name", teamName).whereBetween("day", [startDate, endDate]);
49
108
  }
50
109
  return this.db("metrics").where("type", type).whereNull("team_name").whereBetween("day", [startDate, endDate]);
51
110
  }
111
+ async getMetricsV2(startDate, endDate, type, teamName) {
112
+ let query = this.db("copilot_metrics as cm").select(
113
+ "cm.day",
114
+ "cm.type",
115
+ "cm.team_name",
116
+ this.db.raw(
117
+ "CAST(MIN(cm.total_active_users) AS INTEGER) as total_active_users"
118
+ ),
119
+ this.db.raw(
120
+ "CAST(MIN(ide_chats.total_engaged_users) AS INTEGER) as total_active_chat_users"
121
+ ),
122
+ this.db.raw(
123
+ "CAST(SUM(icelm.total_code_suggestions) AS INTEGER) as total_suggestions_count"
124
+ ),
125
+ this.db.raw(
126
+ "CAST(SUM(icelm.total_code_acceptances) AS INTEGER) as total_acceptances_count"
127
+ ),
128
+ this.db.raw(
129
+ "CAST(SUM(icelm.total_code_lines_suggested) AS INTEGER) as total_lines_suggested"
130
+ ),
131
+ this.db.raw(
132
+ "CAST(SUM(icelm.total_code_lines_accepted) AS INTEGER) as total_lines_accepted"
133
+ ),
134
+ this.db.raw(
135
+ "CAST(SUM(icem.total_chats) AS INTEGER) as total_chat_turns"
136
+ ),
137
+ this.db.raw(
138
+ "CAST(SUM(icem.total_chat_copy_events) AS INTEGER) as total_chat_acceptances"
139
+ ),
140
+ this.db.raw("'' as breakdown")
141
+ ).join("ide_completions", (join) => {
142
+ join.on("ide_completions.day", "=", "cm.day").andOn("ide_completions.type", "=", "cm.type");
143
+ if (teamName) {
144
+ join.andOn(
145
+ "ide_completions.team_name",
146
+ "=",
147
+ this.db.raw("?", [teamName])
148
+ );
149
+ } else {
150
+ join.andOnNull("ide_completions.team_name");
151
+ }
152
+ }).join("ide_chats", (join) => {
153
+ join.on("ide_chats.day", "=", "cm.day").andOn("ide_chats.type", "=", "cm.type");
154
+ if (teamName) {
155
+ join.andOn("ide_chats.team_name", "=", this.db.raw("?", [teamName]));
156
+ } else {
157
+ join.andOnNull("ide_chats.team_name");
158
+ }
159
+ }).join(
160
+ this.db.raw(
161
+ `(SELECT day, type, team_name,
162
+ SUM(total_code_suggestions) as total_code_suggestions,
163
+ SUM(total_code_acceptances) as total_code_acceptances,
164
+ SUM(total_code_lines_suggested) as total_code_lines_suggested,
165
+ SUM(total_code_lines_accepted) as total_code_lines_accepted
166
+ FROM ide_completions_language_editors_model_language GROUP BY day, type, team_name)
167
+ as icelm`
168
+ ),
169
+ (join) => {
170
+ join.on("icelm.day", "=", "cm.day").andOn("icelm.type", "=", "cm.type");
171
+ if (teamName) {
172
+ join.andOn("icelm.team_name", "=", this.db.raw("?", [teamName]));
173
+ } else {
174
+ join.andOnNull("icelm.team_name");
175
+ }
176
+ }
177
+ ).join(
178
+ this.db.raw(
179
+ `(SELECT day, type, team_name, SUM(total_chats) as total_chats,
180
+ SUM(total_chat_copy_events) as total_chat_copy_events
181
+ FROM ide_chat_editors_model GROUP BY day, type, team_name) as icem`
182
+ ),
183
+ (join) => {
184
+ join.on("icem.day", "=", "cm.day").andOn("icem.type", "=", "cm.type");
185
+ if (teamName) {
186
+ join.andOn("icem.team_name", "=", this.db.raw("?", [teamName]));
187
+ } else {
188
+ join.andOnNull("icem.team_name");
189
+ }
190
+ }
191
+ ).where("cm.type", type).whereBetween("cm.day", [startDate, endDate]).groupBy("cm.day", "cm.type", "cm.team_name").orderBy("cm.day", "asc");
192
+ if (teamName) {
193
+ query = query.where("cm.team_name", teamName);
194
+ } else {
195
+ query = query.whereNull("cm.team_name");
196
+ }
197
+ return await query;
198
+ }
199
+ async getBreakdown(startDate, endDate, type, teamName) {
200
+ let query = this.db("copilot_metrics as cm").select(
201
+ "cm.day",
202
+ "icleml.editor as editor",
203
+ "icleml.language as language",
204
+ this.db.raw(
205
+ "CAST(SUM(icleml.total_engaged_users) AS INTEGER) as active_users"
206
+ ),
207
+ this.db.raw(
208
+ "CAST(SUM(icleml.total_code_lines_suggested) AS INTEGER) as lines_suggested"
209
+ ),
210
+ this.db.raw(
211
+ "CAST(SUM(icleml.total_code_lines_accepted) AS INTEGER) as lines_accepted"
212
+ ),
213
+ this.db.raw(
214
+ "CAST(SUM(icleml.total_code_suggestions) AS INTEGER) as suggestions_count"
215
+ ),
216
+ this.db.raw(
217
+ "CAST(SUM(icleml.total_code_acceptances) AS INTEGER) as acceptances_count"
218
+ )
219
+ ).join(
220
+ "ide_completions_language_editors_model_language as icleml",
221
+ (join) => {
222
+ join.on("icleml.day", "=", "cm.day").andOn("icleml.type", "=", "cm.type");
223
+ if (teamName) {
224
+ join.andOn("icleml.team_name", "=", this.db.raw("?", [teamName]));
225
+ } else {
226
+ join.andOnNull("icleml.team_name");
227
+ }
228
+ }
229
+ ).whereBetween("cm.day", [startDate, endDate]).where("icleml.model", "default").where("cm.type", type).groupBy("cm.day", "icleml.editor", "icleml.language").orderBy("cm.day", "asc");
230
+ if (teamName) {
231
+ query = query.where("cm.team_name", teamName);
232
+ } else {
233
+ query = query.whereNull("cm.team_name");
234
+ }
235
+ return await query;
236
+ }
52
237
  }
53
238
 
54
239
  exports.DatabaseHandler = DatabaseHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"DatabaseHandler.cjs.js","sources":["../../src/db/DatabaseHandler.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n resolvePackagePath,\n DatabaseService,\n} from '@backstage/backend-plugin-api';\nimport {\n Metric,\n MetricsType,\n PeriodRange,\n} from '@backstage-community/plugin-copilot-common';\nimport { Knex } from 'knex';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage-community/plugin-copilot-backend',\n 'migrations',\n);\n\ntype Options = {\n database: DatabaseService;\n};\n\nexport type MetricDbRow = Omit<Metric, 'breakdown'> & {\n breakdown: string;\n};\n\nexport class DatabaseHandler {\n static async create(options: Options): Promise<DatabaseHandler> {\n const { database } = options;\n const client = await database.getClient();\n\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n return new DatabaseHandler(client);\n }\n\n private constructor(private readonly db: Knex) {}\n\n async getPeriodRange(type: MetricsType): Promise<PeriodRange | undefined> {\n const query = this.db<MetricDbRow>('metrics').where('type', type);\n\n const minDate = await query.orderBy('day', 'asc').first('day');\n const maxDate = await query.orderBy('day', 'desc').first('day');\n\n if (!minDate?.day || !maxDate?.day) return undefined;\n\n return { minDate: minDate.day, maxDate: maxDate.day };\n }\n\n async getTeams(\n type: MetricsType,\n startDate: string,\n endDate: string,\n ): Promise<Array<string | undefined>> {\n const result = await this.db<MetricDbRow>('metrics')\n .where('type', type)\n .whereBetween('day', [startDate, endDate])\n .whereNotNull('team_name')\n .distinct('team_name')\n .orderBy('team_name', 'asc')\n .select('team_name');\n\n return result.map(x => x.team_name);\n }\n\n async batchInsert(metrics: MetricDbRow[]): Promise<void> {\n await this.db<MetricDbRow[]>('metrics')\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name'])\n .ignore();\n }\n\n async getMostRecentDayFromMetrics(\n type: MetricsType,\n teamName?: string,\n ): Promise<string | undefined> {\n try {\n const query = await this.db<MetricDbRow>('metrics')\n .where('type', type)\n .where('team_name', teamName ?? null)\n .orderBy('day', 'desc')\n .first('day');\n return query ? query.day : undefined;\n } catch (e) {\n return undefined;\n }\n }\n\n async getMetrics(\n startDate: string,\n endDate: string,\n type: MetricsType,\n teamName?: string,\n ): Promise<MetricDbRow[]> {\n console.log(startDate, endDate, type, teamName);\n if (teamName) {\n return await this.db<MetricDbRow>('metrics')\n .where('type', type)\n .where('team_name', teamName)\n .whereBetween('day', [startDate, endDate]);\n }\n return this.db<MetricDbRow>('metrics')\n .where('type', type)\n .whereNull('team_name')\n .whereBetween('day', [startDate, endDate]);\n }\n}\n"],"names":["resolvePackagePath"],"mappings":"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"}
1
+ {"version":3,"file":"DatabaseHandler.cjs.js","sources":["../../src/db/DatabaseHandler.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n resolvePackagePath,\n DatabaseService,\n} from '@backstage/backend-plugin-api';\nimport {\n Metric,\n MetricsType,\n PeriodRange,\n CopilotIdeCodeCompletions,\n CopilotIdeLanguages,\n CopilotMetrics,\n CopilotEditors,\n CopilotModels,\n CopilotLanguages,\n CopilotChats,\n CopilotChatEditors,\n CopilotChatModels,\n} from '@backstage-community/plugin-copilot-common';\nimport { Knex } from 'knex';\n\nconst migrationsDir = resolvePackagePath(\n '@backstage-community/plugin-copilot-backend',\n 'migrations',\n);\n\ntype Options = {\n database: DatabaseService;\n};\n\nexport type Breakdown = {\n day: string;\n editor: string;\n language: string;\n lines_accepted: number;\n lines_suggested: number;\n suggestions_count: number;\n acceptances_count: number;\n active_users: number;\n};\n\nexport type CopilotMetricsDb = Omit<\n CopilotMetrics,\n | 'date'\n | 'copilot_ide_code_completions'\n | 'copilot_ide_chat'\n | 'copilot_dotcom_chat'\n | 'copilot_dotcom_pull_requests'\n> & {\n day: string;\n /**\n * The type of the metrics data.\n * Can be 'enterprise', 'organization'.\n */\n type: MetricsType;\n\n /**\n * The name of the team, applicable when the metric is for a specific team.\n * When null, it indicates metrics for all teams, aggregated at the 'enterprise' or 'organization' level.\n */\n team_name?: string;\n};\n\nexport type CopilotIdeCodeCompletionsDb = Omit<\n CopilotIdeCodeCompletions,\n 'editors' | 'languages'\n> & {\n day: string;\n /**\n * The type of the metrics data.\n * Can be 'enterprise', 'organization'.\n */\n type: MetricsType;\n\n /**\n * The name of the team, applicable when the metric is for a specific team.\n * When null, it indicates metrics for all teams, aggregated at the 'enterprise' or 'organization' level.\n */\n team_name?: string;\n};\n\nexport type CopilotIdeCodeCompletionsLanguageDb = Omit<\n CopilotIdeLanguages,\n 'day' | 'name' | 'language'\n> & {\n day: string;\n /**\n * The type of the metrics data.\n * Can be 'enterprise', 'organization'.\n */\n type: MetricsType;\n\n /**\n * The name of the team, applicable when the metric is for a specific team.\n * When null, it indicates metrics for all teams, aggregated at the 'enterprise' or 'organization' level.\n */\n team_name?: string;\n language: string;\n};\n\nexport type CopilotIdeCodeCompletionsEditorsDb = Omit<\n CopilotEditors,\n 'day' | 'name' | 'models'\n> & {\n day: string;\n /**\n * The type of the metrics data.\n * Can be 'enterprise', 'organization'.\n */\n type: MetricsType;\n\n /**\n * The name of the team, applicable when the metric is for a specific team.\n * When null, it indicates metrics for all teams, aggregated at the 'enterprise' or 'organization' level.\n */\n team_name?: string;\n editor: string;\n};\n\nexport type CopilotIdeChatsDb = Omit<CopilotChats, 'day' | 'editors'> & {\n day: string;\n};\n\nexport type CopilotIdeChatsEditorsDb = Omit<\n CopilotChatEditors,\n 'day' | 'name' | 'models' | 'editor'\n> & {\n day: string;\n /**\n * The type of the metrics data.\n * Can be 'enterprise', 'organization'.\n */\n type: MetricsType;\n\n /**\n * The name of the team, applicable when the metric is for a specific team.\n * When null, it indicates metrics for all teams, aggregated at the 'enterprise' or 'organization' level.\n */\n team_name?: string;\n editor: string;\n};\n\nexport type CopilotIdeChatsEditorModelDb = Omit<\n CopilotChatModels,\n 'name' | 'day' | 'model' | 'editor'\n> & {\n day: string;\n /**\n * The type of the metrics data.\n * Can be 'enterprise', 'organization'.\n */\n type: MetricsType;\n\n /**\n * The name of the team, applicable when the metric is for a specific team.\n * When null, it indicates metrics for all teams, aggregated at the 'enterprise' or 'organization' level.\n */\n team_name?: string;\n editor: string;\n model: string;\n};\n\nexport type CopilotIdeCodeCompletionsEditorModelsDb = Omit<\n CopilotModels,\n 'day' | 'editor' | 'model' | 'name' | 'languages'\n> & {\n day: string;\n /**\n * The type of the metrics data.\n * Can be 'enterprise', 'organization'.\n */\n type: MetricsType;\n\n /**\n * The name of the team, applicable when the metric is for a specific team.\n * When null, it indicates metrics for all teams, aggregated at the 'enterprise' or 'organization' level.\n */\n team_name?: string;\n editor: string;\n model: string;\n};\n\nexport type CopilotIdeCodeCompletionsEditorModelLanguagesDb = Omit<\n CopilotLanguages,\n 'day' | 'editor' | 'model' | 'name' | 'language'\n> & {\n day: string;\n /**\n * The type of the metrics data.\n * Can be 'enterprise', 'organization'.\n */\n type: MetricsType;\n\n /**\n * The name of the team, applicable when the metric is for a specific team.\n * When null, it indicates metrics for all teams, aggregated at the 'enterprise' or 'organization' level.\n */\n team_name?: string;\n editor: string;\n model: string;\n language: string;\n};\n\nexport type MetricDbRow = Omit<Metric, 'breakdown'> & {\n breakdown: string;\n};\n\nexport class DatabaseHandler {\n static async create(options: Options): Promise<DatabaseHandler> {\n const { database } = options;\n const client = await database.getClient();\n\n if (!database.migrations?.skip) {\n await client.migrate.latest({\n directory: migrationsDir,\n });\n }\n\n return new DatabaseHandler(client);\n }\n\n private constructor(private readonly db: Knex) {}\n\n async getPeriodRange(type: MetricsType): Promise<PeriodRange | undefined> {\n const query = this.db<MetricDbRow>('metrics').where('type', type);\n\n const minDate = await query.orderBy('day', 'asc').first('day');\n const maxDate = await query.orderBy('day', 'desc').first('day');\n\n if (!minDate?.day || !maxDate?.day) return undefined;\n\n return { minDate: minDate.day, maxDate: maxDate.day };\n }\n\n async getPeriodRangeV2(type: MetricsType): Promise<PeriodRange | undefined> {\n const query = this.db('copilot_metrics').where('type', type);\n\n const minDate = await query.orderBy('day', 'asc').first('day');\n const maxDate = await query.orderBy('day', 'desc').first('day');\n\n if (!minDate?.day || !maxDate?.day) return undefined;\n\n return { minDate: minDate.day, maxDate: maxDate.day };\n }\n\n async getTeams(\n type: MetricsType,\n startDate: string,\n endDate: string,\n ): Promise<Array<string | undefined>> {\n const result = await this.db<MetricDbRow>('copilot_metrics')\n .where('type', type)\n .whereBetween('day', [startDate, endDate])\n .whereNotNull('team_name')\n .distinct('team_name')\n .orderBy('team_name', 'asc')\n .select('team_name');\n\n return result.map(x => x.team_name);\n }\n\n async batchInsert(metrics: MetricDbRow[]): Promise<void> {\n await this.db<MetricDbRow[]>('metrics')\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name'])\n .ignore();\n }\n\n async batchInsertMetrics(metrics: CopilotMetricsDb[]): Promise<void> {\n await this.db<CopilotMetricsDb[]>('copilot_metrics')\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name'])\n .ignore();\n }\n\n async batchInsertIdeCompletions(\n metrics: CopilotIdeCodeCompletionsDb[],\n ): Promise<void> {\n await this.db<CopilotIdeCodeCompletionsDb[]>('ide_completions')\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name'])\n .ignore();\n }\n\n async batchInsertIdeCompletionsLanguages(\n metrics: CopilotIdeCodeCompletionsLanguageDb[],\n ): Promise<void> {\n await this.db<CopilotIdeCodeCompletionsLanguageDb[]>(\n 'ide_completions_language_users',\n )\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name', 'language'])\n .ignore();\n }\n\n async batchInsertIdeCompletionsEditors(\n metrics: CopilotIdeCodeCompletionsEditorsDb[],\n ): Promise<void> {\n await this.db<CopilotIdeCodeCompletionsEditorsDb[]>(\n 'ide_completions_language_editors',\n )\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name', 'editor'])\n .ignore();\n }\n\n async batchInsertIdeCompletionsEditorModels(\n metrics: CopilotIdeCodeCompletionsEditorModelsDb[],\n ): Promise<void> {\n await this.db<CopilotIdeCodeCompletionsEditorModelsDb[]>(\n 'ide_completions_language_editors_model',\n )\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name', 'editor', 'model'])\n .ignore();\n }\n\n async batchInsertIdeCompletionsEditorModelLanguages(\n metrics: CopilotIdeCodeCompletionsEditorModelLanguagesDb[],\n ): Promise<void> {\n await this.db<CopilotIdeCodeCompletionsEditorModelLanguagesDb[]>(\n 'ide_completions_language_editors_model_language',\n )\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name', 'editor', 'model', 'language'])\n .ignore();\n }\n\n async batchInsertIdeChats(metrics: CopilotIdeChatsDb[]): Promise<void> {\n await this.db<CopilotIdeChatsDb[]>('ide_chats')\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name'])\n .ignore();\n }\n\n async batchInsertIdeChatEditors(\n metrics: CopilotIdeChatsEditorsDb[],\n ): Promise<void> {\n await this.db<CopilotIdeChatsEditorsDb[]>('ide_chat_editors')\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name', 'editor'])\n .ignore();\n }\n\n async batchInsertIdeChatEditorModels(\n metrics: CopilotIdeChatsEditorModelDb[],\n ): Promise<void> {\n await this.db<CopilotIdeChatsEditorModelDb[]>('ide_chat_editors_model')\n .insert(metrics)\n .onConflict(['day', 'type', 'team_name', 'editor', 'model'])\n .ignore();\n }\n\n async getMostRecentDayFromMetrics(\n type: MetricsType,\n teamName?: string,\n ): Promise<string | undefined> {\n try {\n const query = await this.db<MetricDbRow>('metrics')\n .where('type', type)\n .where('team_name', teamName ?? null)\n .orderBy('day', 'desc')\n .first('day');\n return query ? query.day : undefined;\n } catch (e) {\n return undefined;\n }\n }\n\n async getMostRecentDayFromMetricsV2(\n type: MetricsType,\n teamName?: string,\n ): Promise<string | undefined> {\n try {\n const query = this.db('copilot_metrics')\n .where('type', type)\n .where('team_name', teamName ?? null)\n .orderBy('day', 'desc')\n .first('day');\n const res = await query;\n return res ? res.day : undefined;\n } catch (e) {\n return undefined;\n }\n }\n\n async getEarliestDayFromMetricsV2(\n type: MetricsType,\n teamName?: string,\n ): Promise<string | undefined> {\n try {\n const query = this.db('copilot_metrics')\n .where('type', type)\n .where('team_name', teamName ?? null)\n .orderBy('day', 'asc')\n .first('day');\n const res = await query;\n return res ? res.day : undefined;\n } catch (e) {\n return undefined;\n }\n }\n\n async getMetrics(\n startDate: string,\n endDate: string,\n type: MetricsType,\n teamName?: string,\n ): Promise<MetricDbRow[]> {\n if (teamName) {\n return await this.db<MetricDbRow>('metrics')\n .where('type', type)\n .where('team_name', teamName)\n .whereBetween('day', [startDate, endDate]);\n }\n return this.db<MetricDbRow>('metrics')\n .where('type', type)\n .whereNull('team_name')\n .whereBetween('day', [startDate, endDate]);\n }\n\n async getMetricsV2(\n startDate: string,\n endDate: string,\n type: MetricsType,\n teamName?: string,\n ): Promise<MetricDbRow[]> {\n let query = this.db('copilot_metrics as cm')\n .select(\n 'cm.day',\n 'cm.type',\n 'cm.team_name',\n this.db.raw(\n 'CAST(MIN(cm.total_active_users) AS INTEGER) as total_active_users',\n ),\n this.db.raw(\n 'CAST(MIN(ide_chats.total_engaged_users) AS INTEGER) as total_active_chat_users',\n ),\n this.db.raw(\n 'CAST(SUM(icelm.total_code_suggestions) AS INTEGER) as total_suggestions_count',\n ),\n this.db.raw(\n 'CAST(SUM(icelm.total_code_acceptances) AS INTEGER) as total_acceptances_count',\n ),\n this.db.raw(\n 'CAST(SUM(icelm.total_code_lines_suggested) AS INTEGER) as total_lines_suggested',\n ),\n this.db.raw(\n 'CAST(SUM(icelm.total_code_lines_accepted) AS INTEGER) as total_lines_accepted',\n ),\n this.db.raw(\n 'CAST(SUM(icem.total_chats) AS INTEGER) as total_chat_turns',\n ),\n this.db.raw(\n 'CAST(SUM(icem.total_chat_copy_events) AS INTEGER) as total_chat_acceptances',\n ),\n this.db.raw(\"'' as breakdown\"),\n )\n .join('ide_completions', join => {\n join\n .on('ide_completions.day', '=', 'cm.day')\n .andOn('ide_completions.type', '=', 'cm.type');\n if (teamName) {\n join.andOn(\n 'ide_completions.team_name',\n '=',\n this.db.raw('?', [teamName]),\n );\n } else {\n join.andOnNull('ide_completions.team_name');\n }\n })\n .join('ide_chats', join => {\n join\n .on('ide_chats.day', '=', 'cm.day')\n .andOn('ide_chats.type', '=', 'cm.type');\n if (teamName) {\n join.andOn('ide_chats.team_name', '=', this.db.raw('?', [teamName]));\n } else {\n join.andOnNull('ide_chats.team_name');\n }\n })\n .join(\n this.db.raw(\n `(SELECT day, type, team_name,\n SUM(total_code_suggestions) as total_code_suggestions, \n SUM(total_code_acceptances) as total_code_acceptances, \n SUM(total_code_lines_suggested) as total_code_lines_suggested, \n SUM(total_code_lines_accepted) as total_code_lines_accepted \n FROM ide_completions_language_editors_model_language GROUP BY day, type, team_name) \n as icelm`,\n ),\n join => {\n join\n .on('icelm.day', '=', 'cm.day')\n .andOn('icelm.type', '=', 'cm.type');\n if (teamName) {\n join.andOn('icelm.team_name', '=', this.db.raw('?', [teamName]));\n } else {\n join.andOnNull('icelm.team_name');\n }\n },\n )\n .join(\n this.db.raw(\n `(SELECT day, type, team_name, SUM(total_chats) as total_chats, \n SUM(total_chat_copy_events) as total_chat_copy_events \n FROM ide_chat_editors_model GROUP BY day, type, team_name) as icem`,\n ),\n join => {\n join.on('icem.day', '=', 'cm.day').andOn('icem.type', '=', 'cm.type');\n if (teamName) {\n join.andOn('icem.team_name', '=', this.db.raw('?', [teamName]));\n } else {\n join.andOnNull('icem.team_name');\n }\n },\n )\n .where('cm.type', type)\n .whereBetween('cm.day', [startDate, endDate])\n .groupBy('cm.day', 'cm.type', 'cm.team_name')\n .orderBy('cm.day', 'asc');\n\n if (teamName) {\n query = query.where('cm.team_name', teamName);\n } else {\n query = query.whereNull('cm.team_name');\n }\n\n return await query;\n }\n\n async getBreakdown(\n startDate: string,\n endDate: string,\n type: MetricsType,\n teamName?: string,\n ): Promise<Breakdown[]> {\n let query = this.db<Breakdown>('copilot_metrics as cm')\n .select(\n 'cm.day',\n 'icleml.editor as editor',\n 'icleml.language as language',\n this.db.raw(\n 'CAST(SUM(icleml.total_engaged_users) AS INTEGER) as active_users',\n ),\n this.db.raw(\n 'CAST(SUM(icleml.total_code_lines_suggested) AS INTEGER) as lines_suggested',\n ),\n this.db.raw(\n 'CAST(SUM(icleml.total_code_lines_accepted) AS INTEGER) as lines_accepted',\n ),\n this.db.raw(\n 'CAST(SUM(icleml.total_code_suggestions) AS INTEGER) as suggestions_count',\n ),\n this.db.raw(\n 'CAST(SUM(icleml.total_code_acceptances) AS INTEGER) as acceptances_count',\n ),\n )\n .join(\n 'ide_completions_language_editors_model_language as icleml',\n join => {\n join\n .on('icleml.day', '=', 'cm.day')\n .andOn('icleml.type', '=', 'cm.type');\n if (teamName) {\n join.andOn('icleml.team_name', '=', this.db.raw('?', [teamName]));\n } else {\n join.andOnNull('icleml.team_name');\n }\n },\n )\n .whereBetween('cm.day', [startDate, endDate])\n .where('icleml.model', 'default')\n .where('cm.type', type)\n .groupBy('cm.day', 'icleml.editor', 'icleml.language')\n .orderBy('cm.day', 'asc');\n\n if (teamName) {\n query = query.where('cm.team_name', teamName);\n } else {\n query = query.whereNull('cm.team_name');\n }\n\n return await query;\n }\n}\n"],"names":["resolvePackagePath"],"mappings":"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"}
@@ -10,6 +10,7 @@ var TaskManagement = require('../task/TaskManagement.cjs.js');
10
10
  var GithubClient = require('../client/GithubClient.cjs.js');
11
11
  var validateQuery = require('./validation/validateQuery.cjs.js');
12
12
  var schema = require('./validation/schema.cjs.js');
13
+ var luxon = require('luxon');
13
14
 
14
15
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
15
16
 
@@ -55,11 +56,35 @@ async function createRouter(routerOptions, pluginOptions) {
55
56
  validateQuery.validateQuery(schema.metricsQuerySchema),
56
57
  async (req, res) => {
57
58
  const { startDate, endDate, type, team } = req.query;
58
- const result = await db.getMetrics(startDate, endDate, type, team);
59
- const metrics = result.map((metric) => ({
60
- ...metric,
61
- breakdown: JSON.parse(metric.breakdown)
62
- }));
59
+ let metrics = [];
60
+ const lastDayOfOldMetrics = await db.getMostRecentDayFromMetrics(
61
+ type,
62
+ team
63
+ );
64
+ if (startDate && lastDayOfOldMetrics && luxon.DateTime.fromISO(startDate) <= luxon.DateTime.fromJSDate(new Date(lastDayOfOldMetrics))) {
65
+ const result = await db.getMetrics(startDate, endDate, type, team);
66
+ metrics = result.map((metric) => ({
67
+ ...metric,
68
+ breakdown: JSON.parse(metric.breakdown)
69
+ }));
70
+ }
71
+ const firstDayOfNewMetrics = await db.getEarliestDayFromMetricsV2(
72
+ type,
73
+ team
74
+ );
75
+ if (endDate && firstDayOfNewMetrics && luxon.DateTime.fromISO(endDate) >= luxon.DateTime.fromJSDate(new Date(firstDayOfNewMetrics))) {
76
+ const result = await db.getMetricsV2(startDate, endDate, type, team);
77
+ const breakdown = await db.getBreakdown(startDate, endDate, type, team);
78
+ const newMetrics = result.map((metric) => ({
79
+ ...metric,
80
+ breakdown: breakdown.filter((day) => {
81
+ const metricDate = luxon.DateTime.fromJSDate(new Date(metric.day));
82
+ const dayDate = luxon.DateTime.fromJSDate(new Date(day.day));
83
+ return metricDate.equals(dayDate);
84
+ })
85
+ }));
86
+ metrics = [...metrics, ...newMetrics];
87
+ }
63
88
  return res.json(metrics);
64
89
  }
65
90
  );
@@ -68,10 +93,17 @@ async function createRouter(routerOptions, pluginOptions) {
68
93
  validateQuery.validateQuery(schema.periodRangeQuerySchema),
69
94
  async (req, res) => {
70
95
  const { type } = req.query;
71
- const result = await db.getPeriodRange(type);
72
- if (!result) {
96
+ const oldMetricRange = await db.getPeriodRange(type);
97
+ const newMetricRange = await db.getPeriodRangeV2(type);
98
+ if (!oldMetricRange && !newMetricRange) {
73
99
  throw new errors.NotFoundError();
74
100
  }
101
+ const minDate = oldMetricRange?.minDate || newMetricRange?.minDate;
102
+ const maxDate = newMetricRange?.maxDate || oldMetricRange?.maxDate;
103
+ if (!minDate || !maxDate) {
104
+ throw new errors.NotFoundError("Unable to determine metric date range");
105
+ }
106
+ const result = { minDate, maxDate };
75
107
  return res.json(result);
76
108
  }
77
109
  );
@@ -1 +1 @@
1
- {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport {\n DatabaseService,\n LoggerService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport { Metric } from '@backstage-community/plugin-copilot-common';\nimport { DatabaseHandler } from '../db/DatabaseHandler';\nimport TaskManagement from '../task/TaskManagement';\nimport { GithubClient } from '../client/GithubClient';\nimport { validateQuery } from './validation/validateQuery';\nimport {\n MetricsQuery,\n metricsQuerySchema,\n PeriodRangeQuery,\n periodRangeQuerySchema,\n TeamQuery,\n teamQuerySchema,\n} from './validation/schema';\n\n/**\n * Options for configuring the Copilot plugin.\n *\n * @public\n */\nexport interface PluginOptions {\n /**\n * Schedule configuration for the plugin.\n */\n schedule?: SchedulerServiceTaskScheduleDefinition;\n}\n\n/**\n * Options for configuring the router used by the Copilot plugin.\n *\n * @public\n */\nexport interface RouterOptions {\n /**\n * Logger service for the router.\n */\n logger: LoggerService;\n\n /**\n * Database service for the router.\n */\n database: DatabaseService;\n\n /**\n * Scheduler service for the router.\n */\n scheduler: SchedulerService;\n\n /**\n * Configuration for the router.\n */\n config: Config;\n}\n\nconst defaultSchedule: SchedulerServiceTaskScheduleDefinition = {\n frequency: { cron: '0 2 * * *' },\n timeout: { minutes: 15 },\n initialDelay: { minutes: 1 },\n scope: 'local',\n};\n\n/**\n * Creates an Express router configured based on the provided router options and plugin options.\n *\n * This function initializes the router with the appropriate middleware and routes based on the\n * configuration and options provided. It also schedules tasks if scheduling options are provided.\n *\n * @param routerOptions - Options for configuring the router, including services and configuration.\n * @returns A promise that resolves to an Express router instance.\n *\n * @public\n */\nexport async function createRouterFromConfig(routerOptions: RouterOptions) {\n const { config } = routerOptions;\n const pluginOptions: PluginOptions = {\n schedule: defaultSchedule,\n };\n if (config && config.has('copilot.schedule')) {\n pluginOptions.schedule =\n readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('copilot.schedule'),\n );\n }\n return createRouter(routerOptions, pluginOptions);\n}\n\n/** @private */\nasync function createRouter(\n routerOptions: RouterOptions,\n pluginOptions: PluginOptions,\n): Promise<express.Router> {\n const { logger, database, scheduler, config } = routerOptions;\n const { schedule } = pluginOptions;\n\n const db = await DatabaseHandler.create({ database });\n const api = await GithubClient.fromConfig(config);\n\n await scheduler.scheduleTask({\n id: 'copilot-metrics',\n ...(schedule ?? defaultSchedule),\n fn: async () =>\n await TaskManagement.create({ db, logger, api, config }).runAsync(),\n });\n\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n logger.info('PONG!');\n response.json({ status: 'ok' });\n });\n\n router.get(\n '/metrics',\n validateQuery(metricsQuerySchema),\n async (req, res) => {\n const { startDate, endDate, type, team } = req.query as MetricsQuery;\n\n const result = await db.getMetrics(startDate, endDate, type, team);\n\n const metrics: Metric[] = result.map(metric => ({\n ...metric,\n breakdown: JSON.parse(metric.breakdown),\n }));\n\n return res.json(metrics);\n },\n );\n\n router.get(\n '/metrics/period-range',\n validateQuery(periodRangeQuerySchema),\n async (req, res) => {\n const { type } = req.query as PeriodRangeQuery;\n const result = await db.getPeriodRange(type);\n\n if (!result) {\n throw new NotFoundError();\n }\n\n return res.json(result);\n },\n );\n\n router.get('/teams', validateQuery(teamQuerySchema), async (req, res) => {\n const { type, startDate, endDate } = req.query as TeamQuery;\n\n const result = await db.getTeams(type, startDate, endDate);\n\n if (!result) {\n throw new NotFoundError();\n }\n\n return res.json(result);\n });\n\n router.use(MiddlewareFactory.create({ config, logger }).error);\n return router;\n}\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","DatabaseHandler","GithubClient","TaskManagement","Router","express","validateQuery","metricsQuerySchema","periodRangeQuerySchema","NotFoundError","teamQuerySchema","MiddlewareFactory"],"mappings":";;;;;;;;;;;;;;;;;;AAgFA,MAAM,eAA0D,GAAA;AAAA,EAC9D,SAAA,EAAW,EAAE,IAAA,EAAM,WAAY,EAAA;AAAA,EAC/B,OAAA,EAAS,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,EACvB,YAAA,EAAc,EAAE,OAAA,EAAS,CAAE,EAAA;AAAA,EAC3B,KAAO,EAAA;AACT,CAAA;AAaA,eAAsB,uBAAuB,aAA8B,EAAA;AACzE,EAAM,MAAA,EAAE,QAAW,GAAA,aAAA;AACnB,EAAA,MAAM,aAA+B,GAAA;AAAA,IACnC,QAAU,EAAA;AAAA,GACZ;AACA,EAAA,IAAI,MAAU,IAAA,MAAA,CAAO,GAAI,CAAA,kBAAkB,CAAG,EAAA;AAC5C,IAAA,aAAA,CAAc,QACZ,GAAAA,qEAAA;AAAA,MACE,MAAA,CAAO,UAAU,kBAAkB;AAAA,KACrC;AAAA;AAEJ,EAAO,OAAA,YAAA,CAAa,eAAe,aAAa,CAAA;AAClD;AAGA,eAAe,YAAA,CACb,eACA,aACyB,EAAA;AACzB,EAAA,MAAM,EAAE,MAAA,EAAQ,QAAU,EAAA,SAAA,EAAW,QAAW,GAAA,aAAA;AAChD,EAAM,MAAA,EAAE,UAAa,GAAA,aAAA;AAErB,EAAA,MAAM,KAAK,MAAMC,+BAAA,CAAgB,MAAO,CAAA,EAAE,UAAU,CAAA;AACpD,EAAA,MAAM,GAAM,GAAA,MAAMC,yBAAa,CAAA,UAAA,CAAW,MAAM,CAAA;AAEhD,EAAA,MAAM,UAAU,YAAa,CAAA;AAAA,IAC3B,EAAI,EAAA,iBAAA;AAAA,IACJ,GAAI,QAAY,IAAA,eAAA;AAAA,IAChB,EAAI,EAAA,YACF,MAAMC,sBAAA,CAAe,MAAO,CAAA,EAAE,EAAI,EAAA,MAAA,EAAQ,GAAK,EAAA,MAAA,EAAQ,CAAA,CAAE,QAAS;AAAA,GACrE,CAAA;AAED,EAAA,MAAM,SAASC,uBAAO,EAAA;AACtB,EAAO,MAAA,CAAA,GAAA,CAAIC,wBAAQ,CAAA,IAAA,EAAM,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAI,CAAA,SAAA,EAAW,CAAC,CAAA,EAAG,QAAa,KAAA;AACrC,IAAA,MAAA,CAAO,KAAK,OAAO,CAAA;AACnB,IAAA,QAAA,CAAS,IAAK,CAAA,EAAE,MAAQ,EAAA,IAAA,EAAM,CAAA;AAAA,GAC/B,CAAA;AAED,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,UAAA;AAAA,IACAC,4BAAcC,yBAAkB,CAAA;AAAA,IAChC,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAA,MAAM,EAAE,SAAW,EAAA,OAAA,EAAS,IAAM,EAAA,IAAA,KAAS,GAAI,CAAA,KAAA;AAE/C,MAAA,MAAM,SAAS,MAAM,EAAA,CAAG,WAAW,SAAW,EAAA,OAAA,EAAS,MAAM,IAAI,CAAA;AAEjE,MAAM,MAAA,OAAA,GAAoB,MAAO,CAAA,GAAA,CAAI,CAAW,MAAA,MAAA;AAAA,QAC9C,GAAG,MAAA;AAAA,QACH,SAAW,EAAA,IAAA,CAAK,KAAM,CAAA,MAAA,CAAO,SAAS;AAAA,OACtC,CAAA,CAAA;AAEF,MAAO,OAAA,GAAA,CAAI,KAAK,OAAO,CAAA;AAAA;AACzB,GACF;AAEA,EAAO,MAAA,CAAA,GAAA;AAAA,IACL,uBAAA;AAAA,IACAD,4BAAcE,6BAAsB,CAAA;AAAA,IACpC,OAAO,KAAK,GAAQ,KAAA;AAClB,MAAM,MAAA,EAAE,IAAK,EAAA,GAAI,GAAI,CAAA,KAAA;AACrB,MAAA,MAAM,MAAS,GAAA,MAAM,EAAG,CAAA,cAAA,CAAe,IAAI,CAAA;AAE3C,MAAA,IAAI,CAAC,MAAQ,EAAA;AACX,QAAA,MAAM,IAAIC,oBAAc,EAAA;AAAA;AAG1B,MAAO,OAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA;AACxB,GACF;AAEA,EAAA,MAAA,CAAO,IAAI,QAAU,EAAAH,2BAAA,CAAcI,sBAAe,CAAG,EAAA,OAAO,KAAK,GAAQ,KAAA;AACvE,IAAA,MAAM,EAAE,IAAA,EAAM,SAAW,EAAA,OAAA,KAAY,GAAI,CAAA,KAAA;AAEzC,IAAA,MAAM,SAAS,MAAM,EAAA,CAAG,QAAS,CAAA,IAAA,EAAM,WAAW,OAAO,CAAA;AAEzD,IAAA,IAAI,CAAC,MAAQ,EAAA;AACX,MAAA,MAAM,IAAID,oBAAc,EAAA;AAAA;AAG1B,IAAO,OAAA,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,GACvB,CAAA;AAED,EAAO,MAAA,CAAA,GAAA,CAAIE,iCAAkB,MAAO,CAAA,EAAE,QAAQ,MAAO,EAAC,EAAE,KAAK,CAAA;AAC7D,EAAO,OAAA,MAAA;AACT;;;;"}
1
+ {"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport {\n DatabaseService,\n LoggerService,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n SchedulerService,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport {\n Metric,\n PeriodRange,\n} from '@backstage-community/plugin-copilot-common';\nimport { DatabaseHandler } from '../db/DatabaseHandler';\nimport TaskManagement from '../task/TaskManagement';\nimport { GithubClient } from '../client/GithubClient';\nimport { validateQuery } from './validation/validateQuery';\nimport {\n MetricsQuery,\n metricsQuerySchema,\n PeriodRangeQuery,\n periodRangeQuerySchema,\n TeamQuery,\n teamQuerySchema,\n} from './validation/schema';\nimport { DateTime } from 'luxon';\n\n/**\n * Options for configuring the Copilot plugin.\n *\n * @public\n */\nexport interface PluginOptions {\n /**\n * Schedule configuration for the plugin.\n */\n schedule?: SchedulerServiceTaskScheduleDefinition;\n}\n\n/**\n * Options for configuring the router used by the Copilot plugin.\n *\n * @public\n */\nexport interface RouterOptions {\n /**\n * Logger service for the router.\n */\n logger: LoggerService;\n\n /**\n * Database service for the router.\n */\n database: DatabaseService;\n\n /**\n * Scheduler service for the router.\n */\n scheduler: SchedulerService;\n\n /**\n * Configuration for the router.\n */\n config: Config;\n}\n\nconst defaultSchedule: SchedulerServiceTaskScheduleDefinition = {\n frequency: { cron: '0 2 * * *' },\n timeout: { minutes: 15 },\n initialDelay: { minutes: 1 },\n scope: 'local',\n};\n\n/**\n * Creates an Express router configured based on the provided router options and plugin options.\n *\n * This function initializes the router with the appropriate middleware and routes based on the\n * configuration and options provided. It also schedules tasks if scheduling options are provided.\n *\n * @param routerOptions - Options for configuring the router, including services and configuration.\n * @returns A promise that resolves to an Express router instance.\n *\n * @public\n */\nexport async function createRouterFromConfig(routerOptions: RouterOptions) {\n const { config } = routerOptions;\n const pluginOptions: PluginOptions = {\n schedule: defaultSchedule,\n };\n if (config && config.has('copilot.schedule')) {\n pluginOptions.schedule =\n readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('copilot.schedule'),\n );\n }\n return createRouter(routerOptions, pluginOptions);\n}\n\n/** @private */\nasync function createRouter(\n routerOptions: RouterOptions,\n pluginOptions: PluginOptions,\n): Promise<express.Router> {\n const { logger, database, scheduler, config } = routerOptions;\n const { schedule } = pluginOptions;\n\n const db = await DatabaseHandler.create({ database });\n const api = await GithubClient.fromConfig(config);\n\n await scheduler.scheduleTask({\n id: 'copilot-metrics',\n ...(schedule ?? defaultSchedule),\n fn: async () =>\n await TaskManagement.create({ db, logger, api, config }).runAsync(),\n });\n\n const router = Router();\n router.use(express.json());\n\n router.get('/health', (_, response) => {\n logger.info('PONG!');\n response.json({ status: 'ok' });\n });\n\n router.get(\n '/metrics',\n validateQuery(metricsQuerySchema),\n async (req, res) => {\n const { startDate, endDate, type, team } = req.query as MetricsQuery;\n let metrics: Metric[] = [];\n\n // if startDate is earlier than last date of MetricsV1, fetch the old data\n const lastDayOfOldMetrics = await db.getMostRecentDayFromMetrics(\n type,\n team,\n );\n\n if (\n startDate &&\n lastDayOfOldMetrics &&\n DateTime.fromISO(startDate) <=\n DateTime.fromJSDate(new Date(lastDayOfOldMetrics))\n ) {\n const result = await db.getMetrics(startDate, endDate, type, team);\n metrics = result.map(metric => ({\n ...metric,\n breakdown: JSON.parse(metric.breakdown),\n }));\n }\n\n // if endDate is later or equal to first day of new metrics, fetch those also and merge into metrics\n const firstDayOfNewMetrics = await db.getEarliestDayFromMetricsV2(\n type,\n team,\n );\n if (\n endDate &&\n firstDayOfNewMetrics &&\n DateTime.fromISO(endDate) >=\n DateTime.fromJSDate(new Date(firstDayOfNewMetrics))\n ) {\n const result = await db.getMetricsV2(startDate, endDate, type, team);\n const breakdown = await db.getBreakdown(startDate, endDate, type, team);\n\n const newMetrics = result.map(metric => ({\n ...metric,\n breakdown: breakdown.filter(day => {\n const metricDate = DateTime.fromJSDate(new Date(metric.day));\n const dayDate = DateTime.fromJSDate(new Date(day.day));\n return metricDate.equals(dayDate);\n }),\n }));\n\n // Merge new metrics with old metrics\n metrics = [...metrics, ...newMetrics];\n }\n\n return res.json(metrics);\n },\n );\n\n router.get(\n '/metrics/period-range',\n validateQuery(periodRangeQuerySchema),\n async (req, res) => {\n const { type } = req.query as PeriodRangeQuery;\n const oldMetricRange = await db.getPeriodRange(type);\n const newMetricRange = await db.getPeriodRangeV2(type);\n\n if (!oldMetricRange && !newMetricRange) {\n throw new NotFoundError();\n }\n\n // Determine the minDate, prioritizing oldMetricRange if available\n const minDate = oldMetricRange?.minDate || newMetricRange?.minDate;\n\n // Determine the maxDate, prioritizing newMetricRange if available\n const maxDate = newMetricRange?.maxDate || oldMetricRange?.maxDate;\n\n // Make sure both minDate and maxDate are defined\n if (!minDate || !maxDate) {\n throw new NotFoundError('Unable to determine metric date range');\n }\n\n const result: PeriodRange = { minDate, maxDate };\n\n return res.json(result);\n },\n );\n\n router.get('/teams', validateQuery(teamQuerySchema), async (req, res) => {\n const { type, startDate, endDate } = req.query as TeamQuery;\n\n const result = await db.getTeams(type, startDate, endDate);\n\n if (!result) {\n throw new NotFoundError();\n }\n\n return res.json(result);\n });\n\n router.use(MiddlewareFactory.create({ config, logger }).error);\n return router;\n}\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","DatabaseHandler","GithubClient","TaskManagement","Router","express","validateQuery","metricsQuerySchema","DateTime","periodRangeQuerySchema","NotFoundError","teamQuerySchema","MiddlewareFactory"],"mappings":"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"}
@@ -17,24 +17,81 @@ async function discoverEnterpriseMetrics({
17
17
  }
18
18
  const type = "enterprise";
19
19
  try {
20
- const metrics = await api.fetchEnterpriseCopilotUsage();
20
+ const copilotMetrics = await api.fetchEnterpriseCopilotMetrics();
21
21
  logger.info(
22
- `[discoverEnterpriseMetrics] Fetched ${metrics.length} metrics`
22
+ `[discoverEnterpriseMetrics] Fetched ${copilotMetrics.length} metrics`
23
23
  );
24
- const lastDay = await db.getMostRecentDayFromMetrics(type);
24
+ const lastDay = await db.getMostRecentDayFromMetricsV2(type);
25
25
  logger.info(`[discoverEnterpriseMetrics] Found last day: ${lastDay}`);
26
- const newMetrics = metricHelpers.filterNewMetrics(metrics, lastDay);
26
+ const newMetrics = metricHelpers.filterNewMetricsV2(
27
+ copilotMetrics,
28
+ lastDay
29
+ );
27
30
  logger.info(
28
31
  `[discoverEnterpriseMetrics] Found ${newMetrics.length} new metrics to insert`
29
32
  );
30
33
  if (newMetrics.length > 0) {
34
+ const coPilotMetrics = metricHelpers.filterBaseMetrics(newMetrics, type);
35
+ const ideCompletionsToInsert = metricHelpers.filterIdeCompletionMetrics(
36
+ newMetrics,
37
+ type
38
+ );
39
+ const ideCompletionsLanguagesToInsert = metricHelpers.filterIdeCompletionLanguageMetrics(newMetrics, type);
40
+ const ideCompletionsEditorsToInsert = metricHelpers.filterIdeCompletionEditorMetrics(
41
+ newMetrics,
42
+ type
43
+ );
44
+ const ideCompletionsEditorModelsToInsert = metricHelpers.filterIdeCompletionEditorModelMetrics(newMetrics, type);
45
+ const ideCompletionsEditorModelLanguagesToInsert = metricHelpers.filterIdeCompletionEditorModelLanguageMetrics(newMetrics, type);
46
+ const ideChats = metricHelpers.filterIdeChatMetrics(newMetrics, type);
47
+ const ideChatEditors = metricHelpers.filterIdeEditorMetrics(newMetrics, type);
48
+ const ideChatEditorModels = metricHelpers.filterIdeChatEditorModelMetrics(
49
+ newMetrics,
50
+ type
51
+ );
52
+ await batchInsert.batchInsertInChunks(coPilotMetrics, 30, async (chunk) => {
53
+ await db.batchInsertMetrics(chunk);
54
+ });
55
+ await batchInsert.batchInsertInChunks(ideCompletionsToInsert, 30, async (chunk) => {
56
+ await db.batchInsertIdeCompletions(chunk);
57
+ });
58
+ await batchInsert.batchInsertInChunks(
59
+ ideCompletionsLanguagesToInsert,
60
+ 30,
61
+ async (chunk) => {
62
+ await db.batchInsertIdeCompletionsLanguages(chunk);
63
+ }
64
+ );
65
+ await batchInsert.batchInsertInChunks(
66
+ ideCompletionsEditorsToInsert,
67
+ 30,
68
+ async (chunk) => {
69
+ await db.batchInsertIdeCompletionsEditors(chunk);
70
+ }
71
+ );
72
+ await batchInsert.batchInsertInChunks(
73
+ ideCompletionsEditorModelsToInsert,
74
+ 30,
75
+ async (chunk) => {
76
+ await db.batchInsertIdeCompletionsEditorModels(chunk);
77
+ }
78
+ );
31
79
  await batchInsert.batchInsertInChunks(
32
- metricHelpers.prepareMetricsForInsert(newMetrics, type),
80
+ ideCompletionsEditorModelLanguagesToInsert,
33
81
  30,
34
82
  async (chunk) => {
35
- await db.batchInsert(chunk);
83
+ await db.batchInsertIdeCompletionsEditorModelLanguages(chunk);
36
84
  }
37
85
  );
86
+ await batchInsert.batchInsertInChunks(ideChats, 30, async (chunk) => {
87
+ await db.batchInsertIdeChats(chunk);
88
+ });
89
+ await batchInsert.batchInsertInChunks(ideChatEditors, 30, async (chunk) => {
90
+ await db.batchInsertIdeChatEditors(chunk);
91
+ });
92
+ await batchInsert.batchInsertInChunks(ideChatEditorModels, 30, async (chunk) => {
93
+ await db.batchInsertIdeChatEditorModels(chunk);
94
+ });
38
95
  logger.info(
39
96
  "[discoverEnterpriseMetrics] Inserted new metrics into the database"
40
97
  );