Skip to content

Commit 5199bbc

Browse files
committed
Merge remote-tracking branch 'origin/main' into ops-3975
2 parents 925f488 + ead1221 commit 5199bbc

26 files changed

Lines changed: 2275 additions & 42 deletions

.github/workflows/chromatic.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424

2525
- name: Cache node_modules
2626
id: node-modules-cache
27-
uses: actions/cache@v5.0.3
27+
uses: actions/cache@v5.0.4
2828
with:
2929
path: node_modules
3030
key: node-modules-cache-${{ hashFiles('package-lock.json', '.npmrc') }}
@@ -36,7 +36,7 @@ jobs:
3636

3737
- name: Run Chromatic
3838
if: env.CHROMATIC_PROJECT_TOKEN
39-
uses: chromaui/action@v15.3.0
39+
uses: chromaui/action@v16.0.0
4040
with:
4141
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
4242
onlyChanged: true # 👈 Required option to enable TurboSnap

.github/workflows/ci.yml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- uses: actions/checkout@v6.0.2
1818
- name: Lookup node_modules cache
1919
id: node-modules-cache
20-
uses: actions/cache@v5.0.3
20+
uses: actions/cache@v5.0.4
2121
with:
2222
path: node_modules
2323
key: node-modules-cache-${{ hashFiles('package-lock.json', '.npmrc') }}
@@ -34,7 +34,7 @@ jobs:
3434
- uses: actions/checkout@v6.0.2
3535
- name: Restore node_modules cache
3636
id: node-modules-cache
37-
uses: actions/cache/restore@v5.0.3
37+
uses: actions/cache/restore@v5.0.4
3838
with:
3939
path: node_modules
4040
key: node-modules-cache-${{ hashFiles('package-lock.json', '.npmrc') }}
@@ -49,13 +49,13 @@ jobs:
4949
- uses: actions/checkout@v6.0.2
5050
- name: Restore node_modules cache
5151
id: node-modules-cache
52-
uses: actions/cache/restore@v5.0.3
52+
uses: actions/cache/restore@v5.0.4
5353
with:
5454
path: node_modules
5555
key: node-modules-cache-${{ hashFiles('package-lock.json', '.npmrc') }}
5656
fail-on-cache-miss: true
5757
- name: Restore NX cache
58-
uses: actions/cache@v5.0.3
58+
uses: actions/cache@v5.0.4
5959
with:
6060
path: .nx/cache
6161
key: nx-lint-${{ github.sha }}
@@ -85,7 +85,7 @@ jobs:
8585
uses: actions/checkout@v6.0.2
8686
- name: Restore node_modules cache
8787
id: node-modules-cache
88-
uses: actions/cache/restore@v5.0.3
88+
uses: actions/cache/restore@v5.0.4
8989
with:
9090
path: node_modules
9191
key: node-modules-cache-${{ hashFiles('package-lock.json', '.npmrc') }}
@@ -132,13 +132,13 @@ jobs:
132132
- uses: actions/checkout@v6.0.2
133133
- name: Restore node_modules cache
134134
id: node-modules-cache
135-
uses: actions/cache/restore@v5.0.3
135+
uses: actions/cache/restore@v5.0.4
136136
with:
137137
path: node_modules
138138
key: node-modules-cache-${{ hashFiles('package-lock.json', '.npmrc') }}
139139
fail-on-cache-miss: true
140140
- name: Restore NX cache
141-
uses: actions/cache@v5.0.3
141+
uses: actions/cache@v5.0.4
142142
with:
143143
path: .nx/cache
144144
key: nx-test-${{ matrix.test-suits.key || matrix.test-suits.name }}-${{ github.sha }}
@@ -147,7 +147,7 @@ jobs:
147147
- name: Test
148148
if: steps.nx-test-cache.outputs.cache-hit != 'true'
149149
continue-on-error: false
150-
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08
150+
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60
151151
with:
152152
timeout_minutes: 10
153153
max_attempts: 3
@@ -165,13 +165,13 @@ jobs:
165165
- uses: actions/checkout@v6.0.2
166166
- name: Restore node_modules cache
167167
id: node-modules-cache
168-
uses: actions/cache/restore@v5.0.3
168+
uses: actions/cache/restore@v5.0.4
169169
with:
170170
path: node_modules
171171
key: node-modules-cache-${{ hashFiles('package-lock.json', '.npmrc') }}
172172
fail-on-cache-miss: true
173173
- name: Restore NX cache
174-
uses: actions/cache@v5.0.3
174+
uses: actions/cache@v5.0.4
175175
with:
176176
path: .nx/cache
177177
key: nx-build-${{ github.sha }}
@@ -184,7 +184,7 @@ jobs:
184184
npx nx run-many --target=build
185185
./tools/truncate-nx-cache.sh
186186
- name: Save build cache
187-
uses: actions/cache/save@v5.0.3
187+
uses: actions/cache/save@v5.0.4
188188
with:
189189
path: dist
190190
key: dist-${{ github.sha }}
@@ -205,7 +205,7 @@ jobs:
205205
steps:
206206
- uses: actions/checkout@v6.0.2
207207
- name: Restore build cache
208-
uses: actions/cache/restore@v5.0.3
208+
uses: actions/cache/restore@v5.0.4
209209
with:
210210
path: dist
211211
key: dist-${{ github.sha }}
@@ -222,7 +222,7 @@ jobs:
222222
- name: Login to Amazon ECR
223223
id: login-ecr
224224
if: vars.ECR_REGION
225-
uses: aws-actions/amazon-ecr-login@v2.0.2
225+
uses: aws-actions/amazon-ecr-login@v2.1.0
226226
- name: Format image tag parts
227227
env:
228228
BRANCH: ${{ github.head_ref || github.ref_name }}
@@ -278,7 +278,7 @@ jobs:
278278
aws-region: ${{ vars.ECR_REGION }}
279279
- name: Login to Amazon ECR
280280
id: login-ecr
281-
uses: aws-actions/amazon-ecr-login@v2.0.2
281+
uses: aws-actions/amazon-ecr-login@v2.1.0
282282
- name: Format image tag components
283283
env:
284284
BRANCH: ${{ github.head_ref || github.ref_name }}

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- name: Get next version
2626
if: inputs.version == ''
2727
id: semver
28-
uses: flatherskevin/semver-action@fc3c4e67353e3c45b8164521ddd17c9e38d6d4d8
28+
uses: flatherskevin/semver-action@7ce18d3c29fa36251f492a5121013455741c072c
2929
with:
3030
incrementLevel: patch
3131
source: tags
@@ -53,9 +53,9 @@ jobs:
5353
aws-region: ${{ vars.ECR_REGION }}
5454
- name: Login to private ECR
5555
id: login-private-ecr
56-
uses: aws-actions/amazon-ecr-login@v2.0.2
56+
uses: aws-actions/amazon-ecr-login@v2.1.0
5757
- name: Login to public ECR
58-
uses: aws-actions/amazon-ecr-login@v2.0.2
58+
uses: aws-actions/amazon-ecr-login@v2.1.0
5959
env:
6060
AWS_REGION: ${{ vars.ECR_PUBLIC_REGION }}
6161
with:

packages/server/api/src/app/benchmark/providers/azure/azure-option-resolver.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
getAzureRegionsList,
44
getAzureSubscriptionsList,
55
} from '@openops/common';
6+
import { logger } from '@openops/server-shared';
67
import {
78
BenchmarkWizardOption,
89
CustomAuthConnectionValue,
@@ -54,7 +55,12 @@ async function getSubscriptionsList(
5455
try {
5556
const tokenResult = await authenticateUserWithAzure(credentials);
5657
subscriptions = await getAzureSubscriptionsList(tokenResult.access_token);
57-
} catch {
58+
} catch (error) {
59+
logger.warn('Failed to retrieve Azure subscriptions for benchmark wizard', {
60+
projectId: context.projectId,
61+
connectionId,
62+
error,
63+
});
5864
throwValidationError(
5965
'Unable to retrieve Azure subscriptions with the provided connection details.',
6066
);

packages/server/api/src/app/openops-analytics/benchmark/benchmark-dashboard-service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BenchmarkProviders } from '@openops/shared';
22
import { throwValidationError } from '../../benchmark/errors';
33
import { createAwsBenchmarkDashboard } from './create-aws-benchmark-dashboard';
4+
import { createAzureBenchmarkDashboard } from './create-azure-benchmark-dashboard';
45

56
export async function createBenchmarkDashboard(
67
provider: string,
@@ -9,6 +10,9 @@ export async function createBenchmarkDashboard(
910
case BenchmarkProviders.AWS:
1011
await createAwsBenchmarkDashboard();
1112
break;
13+
case BenchmarkProviders.AZURE:
14+
await createAzureBenchmarkDashboard();
15+
break;
1216
default:
1317
throwValidationError(
1418
`Unsupported benchmark dashboard provider: ${provider}`,
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import {
2+
authenticateOpenOpsAnalyticsAdmin,
3+
enableDashboardEmbedding,
4+
} from '@openops/common';
5+
import { logger } from '@openops/server-shared';
6+
import { assertNotNullOrUndefined } from '@openops/shared';
7+
import { SEED_OPENOPS_TABLE_NAME as OPPORTUNITIES_TABLE_NAME } from '../../openops-tables/template-tables/create-opportunities-table';
8+
import { TIMESERIES_TABLE_NAME } from '../../openops-tables/template-tables/create-timeseries-table';
9+
import { organizationService } from '../../organization/organization.service';
10+
import { upsertDashboard } from '../analytics-dashboard-registry-service';
11+
import { getOrCreateOpenOpsTablesDatabaseConnection } from '../create-database-connection';
12+
import { getDefaultProjectForOrganization } from '../project-selector';
13+
import {
14+
createDashboardZipBuffer,
15+
importDashboardFromZip,
16+
resolveTableIds,
17+
} from './benchmark-dashboard-helpers';
18+
import { createAzureBenchmarkDatasets } from './create-azure-benchmark-datasets';
19+
20+
const USER_FRIENDLY_TABLE_SUFFIX = '_userfriendly';
21+
const AZURE_BENCHMARK_DASHBOARD_SLUG = 'azure_benchmark';
22+
const AZURE_BENCHMARK_DASHBOARD_DISPLAY_NAME = 'Azure Benchmark';
23+
24+
export async function createAzureBenchmarkDashboard(): Promise<void> {
25+
logger.info('Starting Azure Benchmark dashboard seeding');
26+
27+
const { access_token } = await authenticateOpenOpsAnalyticsAdmin();
28+
29+
const dbConnection = await getOrCreateOpenOpsTablesDatabaseConnection(
30+
access_token,
31+
);
32+
33+
const organization = await organizationService.getOldestOrganization();
34+
assertNotNullOrUndefined(organization, 'Organization not found');
35+
36+
const project = await getDefaultProjectForOrganization(organization.id);
37+
assertNotNullOrUndefined(project, 'Project not found');
38+
39+
const tableIds = await resolveTableIds(project, [
40+
OPPORTUNITIES_TABLE_NAME,
41+
TIMESERIES_TABLE_NAME,
42+
]);
43+
44+
const opportunitiesTableName = `${OPPORTUNITIES_TABLE_NAME}_${tableIds[OPPORTUNITIES_TABLE_NAME]}${USER_FRIENDLY_TABLE_SUFFIX}`;
45+
const timeseriesTableName = `${TIMESERIES_TABLE_NAME}_${tableIds[TIMESERIES_TABLE_NAME]}${USER_FRIENDLY_TABLE_SUFFIX}`;
46+
47+
const datasets = await createAzureBenchmarkDatasets(
48+
access_token,
49+
dbConnection.id,
50+
opportunitiesTableName,
51+
timeseriesTableName,
52+
);
53+
54+
const dashboardZipBuffer = await createDashboardZipBuffer('azure');
55+
56+
await importDashboardFromZip(
57+
access_token,
58+
dashboardZipBuffer,
59+
dbConnection.uuid,
60+
{
61+
Azure_Benchmark_Opportunities: datasets.opportunities.uuid,
62+
Azure_Benchmark_KPI_efficiency: datasets.kpi.uuid,
63+
Azure_Benchmark_Timeseries: datasets.timeseries.uuid,
64+
},
65+
);
66+
67+
const embedResponse = await enableDashboardEmbedding(
68+
access_token,
69+
AZURE_BENCHMARK_DASHBOARD_SLUG,
70+
);
71+
72+
await upsertDashboard(
73+
{
74+
id: AZURE_BENCHMARK_DASHBOARD_SLUG,
75+
name: AZURE_BENCHMARK_DASHBOARD_DISPLAY_NAME,
76+
slug: AZURE_BENCHMARK_DASHBOARD_SLUG,
77+
embedId: embedResponse.result.uuid,
78+
enabled: true,
79+
},
80+
access_token,
81+
);
82+
83+
logger.info('Azure Benchmark dashboard seeding completed successfully');
84+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { createVirtualDataset } from '../virtual-dataset';
2+
3+
export async function createAzureBenchmarkDatasets(
4+
token: string,
5+
databaseId: number,
6+
opportunitiesTableName: string,
7+
timeseriesTableName: string,
8+
) {
9+
const opportunities = await createVirtualDataset(token, {
10+
tableName: 'Azure_Benchmark_Opportunities',
11+
sql: `
12+
SELECT *
13+
FROM public."${opportunitiesTableName}"
14+
WHERE "Workflow" LIKE 'Azure Benchmark%'
15+
`,
16+
databaseId,
17+
schema: 'public',
18+
recreateIfExists: true,
19+
});
20+
21+
const kpi = await createVirtualDataset(token, {
22+
tableName: 'Azure_Benchmark_KPI_efficiency',
23+
sql: `
24+
WITH opp AS (
25+
SELECT
26+
"Account" AS opp_account,
27+
COALESCE(SUM("Estimated savings USD per month"), 0) AS opp_sum
28+
FROM public."${opportunitiesTableName}"
29+
WHERE "Workflow" LIKE 'Azure Benchmark%'
30+
GROUP BY "Account"
31+
),
32+
ts AS (
33+
SELECT
34+
"Account" AS ts_account,
35+
COALESCE(SUM("Value"), 0) AS ts_value
36+
FROM public."${timeseriesTableName}"
37+
WHERE "Workflow" = 'Run Azure Benchmark'
38+
AND "Date" = (date_trunc('month', current_date) - interval '1 month')::date
39+
GROUP BY "Account"
40+
)
41+
SELECT
42+
COALESCE(opp.opp_account, ts.ts_account) AS "Account",
43+
COALESCE(opp.opp_sum, 0) AS savings,
44+
COALESCE(ts.ts_value, 0) AS monthly_cost,
45+
CASE
46+
WHEN COALESCE(ts.ts_value, 0) = 0 THEN 100.0
47+
ELSE (1 - (COALESCE(opp.opp_sum, 0) / COALESCE(ts.ts_value, 0))) * 100.0
48+
END AS kpi
49+
FROM opp
50+
FULL OUTER JOIN ts
51+
ON opp.opp_account = ts.ts_account;
52+
`,
53+
databaseId,
54+
schema: 'public',
55+
recreateIfExists: true,
56+
});
57+
58+
const timeseries = await createVirtualDataset(token, {
59+
tableName: 'Azure_Benchmark_Timeseries',
60+
sql: `
61+
SELECT *
62+
FROM public."${timeseriesTableName}"
63+
WHERE "Workflow" = 'Run Azure Benchmark' AND "Account" IS NOT NULL
64+
`,
65+
databaseId,
66+
schema: 'public',
67+
recreateIfExists: true,
68+
});
69+
70+
return { opportunities, kpi, timeseries };
71+
}

0 commit comments

Comments
 (0)