Compare commits

...

78 Commits

Author SHA1 Message Date
Alex Meijer c855f42c00
Merge pull request #94 from opencost/dependabot/github_actions/tspascoal/get-user-teams-membership-3
Bump tspascoal/get-user-teams-membership from 2 to 3
2025-08-07 13:53:36 -04:00
Alex Meijer 1e0e0f9d2c
Merge branch 'main' into dependabot/github_actions/tspascoal/get-user-teams-membership-3 2025-08-07 13:53:28 -04:00
Alex Meijer b182f8eeed
Merge pull request #99 from opencost/dependabot/npm_and_yarn/babel/core-7.28.0
Bump @babel/core from 7.26.8 to 7.28.0
2025-08-07 13:47:35 -04:00
Alex Meijer 474f285864
Merge branch 'main' into dependabot/npm_and_yarn/babel/core-7.28.0 2025-08-07 13:47:05 -04:00
Alex Meijer aa0c95ddd2
Merge pull request #103 from cpetersen5/main
Change order of checkout steps
2025-08-07 12:22:34 -04:00
Christian Petersen 3dff690f8a
Change order of checkout steps 2025-08-07 10:19:59 -06:00
Alex Meijer 0d59411e83
Merge pull request #102 from cpetersen5/main
Add step to checkout opencost-ui in Build and Publish Release workflow
2025-08-07 12:15:02 -04:00
Christian Petersen a6f8efa219
Merge branch 'main' of github.com:cpetersen5/opencost-ui 2025-08-07 10:12:52 -06:00
Christian Petersen 7b32959631
Add step to checkout opencost-ui in Build and Publish Release workflow 2025-08-07 10:12:34 -06:00
Christian Petersen 980c996947
Merge branch 'opencost:main' into main 2025-08-07 10:10:37 -06:00
Alex Meijer 61747f15f4
Merge pull request #101 from cpetersen5/main
Remove working directory from build and publish container step
2025-08-07 12:03:20 -04:00
Christian Petersen dda060aa0f
Remove working directory from build and publish container step
Signed-off-by: Christian Petersen <Christian.Petersen2@ibm.com>
2025-08-07 10:03:11 -06:00
Christian Petersen e68fb9cd2d
Remove working directory from build and publish container step 2025-08-07 10:01:10 -06:00
Alex Meijer 09fe99a22d
Merge pull request #100 from SharmaVansh1910/patch-1
Update README.md: Fix Links
2025-08-01 11:44:50 -04:00
VANSH SHARMA b8e5256ba4
Update README.md
Signed-off-by: VANSH SHARMA <vansh1910sharma@gmail.com>
2025-08-01 21:07:36 +05:30
Alex Meijer a1760dacad
Merge pull request #97 from reschandreas/main
fix images and loading of external costs
2025-07-28 07:43:02 -04:00
dependabot[bot] 0fbfae1751
Bump @babel/core from 7.26.8 to 7.28.0
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.8 to 7.28.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.28.0/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-version: 7.28.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-28 05:16:58 +00:00
Andreas Resch 3b9b26fbf8
fix images and loading of external costs
Signed-off-by: Andreas Resch <andreas@resch.io>
2025-07-27 09:24:20 +02:00
Alex Meijer 6444dffdea
Merge pull request #77 from opencost/dependabot/npm_and_yarn/date-io/core-3.2.0
Bump @date-io/core from 1.3.13 to 3.2.0
2025-07-24 16:23:16 -04:00
Alex Meijer 483d4b4d02
Merge branch 'main' into dependabot/npm_and_yarn/date-io/core-3.2.0 2025-07-24 14:54:04 -04:00
Alex Meijer 244d62b05d
Merge pull request #84 from andoriyaprashant/tablepagination
Fix deprecated and missing props in TablePagination
2025-07-10 14:13:50 -04:00
dependabot[bot] 243c07c117
Bump tspascoal/get-user-teams-membership from 2 to 3
Bumps [tspascoal/get-user-teams-membership](https://github.com/tspascoal/get-user-teams-membership) from 2 to 3.
- [Release notes](https://github.com/tspascoal/get-user-teams-membership/releases)
- [Commits](https://github.com/tspascoal/get-user-teams-membership/compare/v2...v3)

---
updated-dependencies:
- dependency-name: tspascoal/get-user-teams-membership
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 04:52:42 +00:00
Alex Meijer 82b4d90762
Merge branch 'main' into dependabot/npm_and_yarn/date-io/core-3.2.0 2025-06-12 11:36:42 -04:00
Alex Meijer 76ef9f7ba4
Merge pull request #93 from opencost/atm/trigger-demo-promotion-debug
bugfix
2025-06-09 13:54:01 -04:00
Alex Meijer 1eaf0ab553 bugfix
Signed-off-by: Alex Meijer <alexander.meijer@ibm.com>
2025-06-09 13:48:47 -04:00
Alex Meijer a81d15bf86
Merge pull request #92 from opencost/atm/trigger-demo-promotion-debug
debugging builds, refactor
2025-06-09 13:44:48 -04:00
Alex Meijer 7a35df1d05 debugging builds, refactor
Signed-off-by: Alex Meijer <alexander.meijer@ibm.com>
2025-06-09 12:33:31 -04:00
Alex Meijer 39b6cf4566
Merge pull request #91 from opencost/atm/trigger-demo-promotion
add automation to promote to demo
2025-06-09 10:58:00 -04:00
Alex Meijer 99774230e0 add automation to promote to demo
Signed-off-by: Alex Meijer <alexander.meijer@ibm.com>
2025-06-09 08:27:36 -04:00
Alex Meijer b0b885e393
Merge pull request #90 from mittal-ishaan/revert-82-agg
Revert "feat: Add support for multiple aggregations in cost allocation view"
2025-06-09 08:05:34 -04:00
Ishaan Mittal a803dcf1ec Revert "feat: Add support for multiple aggregations in cost allocation view"
Signed-off-by: Ishaan Mittal <ishaanmittal123@gmail.com>
2025-06-09 14:50:41 +05:30
Alex Meijer dc9351db45
Merge pull request #89 from mittal-ishaan/fix_build_publish_script
Fix build and publish script
2025-06-04 08:33:39 -04:00
Ishaan Mittal 4a7bbcabfa reference step outputs as env
Signed-off-by: Ishaan Mittal <ishaanmittal123@gmail.com>
2025-06-04 18:01:23 +05:30
Ishaan Mittal 93cfa29092
Merge branch 'main' into fix_build_publish_script 2025-06-04 17:53:56 +05:30
Ishaan Mittal 84d9daf0b0 Fix build and publish script
Signed-off-by: Ishaan Mittal <ishaanmittal123@gmail.com>
2025-06-04 17:51:45 +05:30
Leonardo Rodrigues de Mello 56121e8565
Merge branch 'main' into tablepagination 2025-05-29 16:16:15 -04:00
Alex Meijer b2cb88d498
Merge pull request #82 from maxvishy/agg
feat: Add support for multiple aggregations in cost allocation view
2025-05-29 16:13:27 -04:00
andoriyaprashant 3ab380e20b Fix deprecated and missing props in TablePagination
Signed-off-by: andoriyaprashant <prashantandoriya@gmail.com>
2025-05-16 21:06:06 +05:30
vishy e506066667 feat: Add support for multiple aggregations in cost allocation view
Signed-off-by: vishy <maxvishy02@gmail.com>
2025-05-14 06:44:09 -04:00
dependabot[bot] 812c9924c5
Bump @date-io/core from 1.3.13 to 3.2.0
Bumps [@date-io/core](https://github.com/dmtrKovalenko/date-io) from 1.3.13 to 3.2.0.
- [Release notes](https://github.com/dmtrKovalenko/date-io/releases)
- [Commits](https://github.com/dmtrKovalenko/date-io/compare/v1.3.13...v3.2.0)

---
updated-dependencies:
- dependency-name: "@date-io/core"
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 13:23:56 +00:00
Leonardo Rodrigues de Mello 9ab6d4ab7d
Merge pull request #79 from opencost/dependabot/npm_and_yarn/recharts-2.15.3
Bump recharts from 2.15.2 to 2.15.3
2025-04-23 09:22:14 -04:00
dependabot[bot] a00a0364ec
Bump recharts from 2.15.2 to 2.15.3
Bumps [recharts](https://github.com/recharts/recharts) from 2.15.2 to 2.15.3.
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/3.x/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v2.15.2...v2.15.3)

---
updated-dependencies:
- dependency-name: recharts
  dependency-version: 2.15.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-21 05:37:10 +00:00
Leonardo Rodrigues de Mello fca2debcf6
Merge pull request #76 from opencost/dependabot/npm_and_yarn/axios-1.8.4
Bump axios from 1.8.2 to 1.8.4
2025-04-14 17:41:28 -04:00
dependabot[bot] 870f5b172e
Bump axios from 1.8.2 to 1.8.4
Bumps [axios](https://github.com/axios/axios) from 1.8.2 to 1.8.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.2...v1.8.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 05:05:31 +00:00
Leonardo Rodrigues de Mello b5bf6fcb11
Merge pull request #62 from opencost/dependabot/npm_and_yarn/recharts-2.15.1
Bump recharts from 2.9.0 to 2.15.1
2025-04-04 09:33:01 -04:00
dependabot[bot] 732d1833a9
Bump recharts from 2.9.0 to 2.15.1
Bumps [recharts](https://github.com/recharts/recharts) from 2.9.0 to 2.15.1.
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/3.x/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v2.9.0...v2.15.1)

---
updated-dependencies:
- dependency-name: recharts
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 13:25:52 +00:00
Leonardo Rodrigues de Mello 3538dcc160
Merge pull request #67 from opencost/dependabot/github_actions/extractions/setup-just-3
Bump extractions/setup-just from 2 to 3
2025-04-04 09:24:30 -04:00
dependabot[bot] 5293871084
Bump extractions/setup-just from 2 to 3
Bumps [extractions/setup-just](https://github.com/extractions/setup-just) from 2 to 3.
- [Release notes](https://github.com/extractions/setup-just/releases)
- [Commits](https://github.com/extractions/setup-just/compare/v2...v3)

---
updated-dependencies:
- dependency-name: extractions/setup-just
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 13:21:29 +00:00
Leonardo Rodrigues de Mello 2d4aa53b42
Merge pull request #68 from opencost/dependabot/github_actions/docker/login-action-3.4.0
Bump docker/login-action from 3.3.0 to 3.4.0
2025-04-04 09:20:21 -04:00
dependabot[bot] ac06644299
Bump docker/login-action from 3.3.0 to 3.4.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](9780b0c442...74a5d14239)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 13:12:50 +00:00
Leonardo Rodrigues de Mello d06a251b1e
Merge pull request #69 from opencost/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.26.10
Bump @babel/plugin-transform-runtime from 7.25.9 to 7.26.10
2025-04-04 09:11:45 -04:00
dependabot[bot] a6195994e3
Bump @babel/plugin-transform-runtime from 7.25.9 to 7.26.10
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.25.9 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 13:09:57 +00:00
Leonardo Rodrigues de Mello 0f068665e6
Merge pull request #72 from opencost/dependabot/npm_and_yarn/npm_and_yarn-f7ce7b98ff
Bump the npm_and_yarn group with 2 updates
2025-04-04 09:08:36 -04:00
dependabot[bot] 7c71e78a44
Bump the npm_and_yarn group with 2 updates
Bumps the npm_and_yarn group with 2 updates: [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) and [axios](https://github.com/axios/axios).


Updates `@babel/runtime` from 7.26.0 to 7.27.0
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

Updates `axios` from 1.7.9 to 1.8.2
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.9...v1.8.2)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: axios
  dependency-version: 1.8.2
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-04 13:05:45 +00:00
Leonardo Rodrigues de Mello feae30e9ef
Merge pull request #71 from opencost/dependabot/npm_and_yarn/parcel-2.14.4
Bump parcel from 2.11.0 to 2.14.4
2025-04-04 09:04:35 -04:00
dependabot[bot] e7c2dd8ab7
Bump parcel from 2.11.0 to 2.14.4
Bumps [parcel](https://github.com/parcel-bundler/parcel) from 2.11.0 to 2.14.4.
- [Release notes](https://github.com/parcel-bundler/parcel/releases)
- [Changelog](https://github.com/parcel-bundler/parcel/blob/v2/CHANGELOG.md)
- [Commits](https://github.com/parcel-bundler/parcel/compare/v2.11.0...v2.14.4)

---
updated-dependencies:
- dependency-name: parcel
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-31 04:23:46 +00:00
Leonardo Rodrigues de Mello 00ae581675
Merge pull request #63 from opencost/dependabot/npm_and_yarn/babel/core-7.26.8
Bump @babel/core from 7.26.0 to 7.26.8
2025-02-13 08:47:28 -05:00
dependabot[bot] 9977ce146b
Bump @babel/core from 7.26.0 to 7.26.8
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.26.0 to 7.26.8.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.8/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 04:58:25 +00:00
Leonardo Rodrigues de Mello 2fb88d7b84
Merge pull request #56 from opencost/dependabot/npm_and_yarn/babel/core-7.26.0
Bump @babel/core from 7.23.2 to 7.26.0
2025-01-23 17:15:50 -05:00
dependabot[bot] a3aba40222
Bump @babel/core from 7.23.2 to 7.26.0
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.23.2 to 7.26.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.0/packages/babel-core)

---
updated-dependencies:
- dependency-name: "@babel/core"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 05:02:11 +00:00
Leonardo Rodrigues de Mello e1496653ba
Merge pull request #53 from opencost/dependabot/npm_and_yarn/babel/preset-react-7.26.3
Bump @babel/preset-react from 7.22.15 to 7.26.3
2025-01-17 23:05:36 -05:00
dependabot[bot] 30a60f6d7f
Bump @babel/preset-react from 7.22.15 to 7.26.3
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.22.15 to 7.26.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.3/packages/babel-preset-react)

---
updated-dependencies:
- dependency-name: "@babel/preset-react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-18 04:03:43 +00:00
Leonardo Rodrigues de Mello 67a03c71cc
Merge pull request #51 from opencost/dependabot/npm_and_yarn/axios-1.7.9
Bump axios from 1.6.0 to 1.7.9
2025-01-17 23:00:30 -05:00
dependabot[bot] bb7d15d91c
Bump axios from 1.6.0 to 1.7.9
Bumps [axios](https://github.com/axios/axios) from 1.6.0 to 1.7.9.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.0...v1.7.9)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-18 03:57:13 +00:00
Leonardo Rodrigues de Mello fda4397553
Merge pull request #47 from opencost/dependabot/npm_and_yarn/npm_and_yarn-8bca5484b5
Bump the npm_and_yarn group with 3 updates
2025-01-17 22:56:06 -05:00
dependabot[bot] ded5518f55
Bump the npm_and_yarn group with 3 updates
Bumps the npm_and_yarn group with 3 updates: [axios](https://github.com/axios/axios), [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and [braces](https://github.com/micromatch/braces).


Updates `axios` from 1.6.0 to 1.7.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.0...v1.7.4)

Updates `path-to-regexp` from 1.8.0 to 1.9.0
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v1.8.0...v1.9.0)

Updates `braces` from 3.0.2 to 3.0.3
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: path-to-regexp
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: braces
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-09 22:10:00 +00:00
Leonardo Rodrigues de Mello dcbd21f018
Merge pull request #46 from opencost/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.25.9
Bump @babel/plugin-transform-runtime from 7.23.9 to 7.25.9
2025-01-09 17:08:20 -05:00
dependabot[bot] c787d1c208
Bump @babel/plugin-transform-runtime from 7.23.9 to 7.25.9
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.23.9 to 7.25.9.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.9/packages/babel-plugin-transform-runtime)

---
updated-dependencies:
- dependency-name: "@babel/plugin-transform-runtime"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-09 22:05:02 +00:00
Leonardo Rodrigues de Mello cb61fc4413
Merge pull request #45 from opencost/dependabot/npm_and_yarn/babel/runtime-7.26.0
Bump @babel/runtime from 7.23.9 to 7.26.0
2025-01-09 17:00:41 -05:00
dependabot[bot] ea120d7d7d
Bump @babel/runtime from 7.23.9 to 7.26.0
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.23.9 to 7.26.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-09 21:57:53 +00:00
Cliff Colvin aa7fbcbc1a
Merge pull request #49 from kastl-ars/20241204_entrypoint_openshift
only create /etc/nginx/conf.d/default.nginx.conf if it does not yet exist (fix #4)
2024-12-23 14:23:18 -06:00
Johannes Kastl c7d3146d1d
docker-entrypoint.sh: only create /etc/nginx/conf.d/default.nginx.conf if it does not yet exist (fix #4)
Signed-off-by: Johannes Kastl <johannes.kastl@ars.de>
2024-12-04 14:07:18 +01:00
Cliff Colvin 459ddc5a50
Merge pull request #43 from wolfeaustin/austin/opencost-ui-external-costs
adds external costs page, reorganizes for longevity
2024-11-06 11:02:55 -06:00
Leonardo Rodrigues de Mello 3ce2bc2277
Merge branch 'main' into austin/opencost-ui-external-costs 2024-10-22 08:51:40 -07:00
wolfeaustin ee502c1628 adds external costs page, reorganizes for longevity 2024-10-17 12:49:42 -07:00
Leonardo Rodrigues de Mello 83f4b6113b
Merge pull request #32 from cchildress-gsu/main
added GPU Cost to a column in allocationReport.js
2024-10-11 20:33:08 -04:00
Leonardo Rodrigues de Mello 8f81bc663d
Merge branch 'main' into main 2024-10-11 20:32:35 -04:00
Chris Childress 6be3300e1d
added GPU Cost to a column in allocationReport.js
Signed-off-by: Chris Childress <cchildress@gsu.edu>
2024-07-29 12:22:22 -05:00
34 changed files with 2958 additions and 2242 deletions

View File

@ -0,0 +1,59 @@
name: 'Build Container'
description: 'Build and publish OpenCost UI container image'
inputs:
actor:
description: 'GitHub actor'
required: true
GITHUB_TOKEN:
description: 'GitHub token for authentication'
required: true
image_tag:
description: 'Full image tag to use for the container'
required: true
release_version:
description: 'Release version for the container'
required: true
registry:
description: 'Container registry to use'
required: true
default: 'ghcr.io'
runs:
using: "composite"
steps:
- name: Log into registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772
with:
registry: ${{ inputs.registry }}
username: ${{ inputs.actor }}
password: ${{ inputs.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
buildkitd-flags: --debug
- name: Set up just
uses: extractions/setup-just@v3
- name: Install crane
uses: imjasonh/setup-crane@v0.3
- name: Install manifest-tool
run: |
mkdir -p manifest-tool
pushd manifest-tool
wget -q https://github.com/estesp/manifest-tool/releases/download/v2.0.8/binaries-manifest-tool-2.0.8.tar.gz
tar -xzf binaries-manifest-tool-2.0.8.tar.gz
cp manifest-tool-linux-amd64 manifest-tool
echo "$(pwd)" >> $GITHUB_PATH
shell: bash
- name: Build and push (multiarch)
env:
IMAGE_TAG: ${{ inputs.image_tag }}
RELEASE_VERSION: ${{ inputs.release_version }}
run: |
just build "$IMAGE_TAG" "$RELEASE_VERSION"
shell: bash

View File

@ -0,0 +1,59 @@
name: Build and Publish Develop UI
on:
workflow_run:
workflows: [Build/Test]
types: [completed]
branches: [main]
concurrency:
group: build-opencost-ui-develop
cancel-in-progress: false
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
jobs:
build-and-publish-opencost-ui:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
contents: read
packages: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Set SHA
id: sha
run: |
echo "OC_SHORTHASH=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Set OpenCost Image Tags
id: tags
run: |
echo "IMAGE_TAG=ghcr.io/${{ github.repository_owner }}/opencost-ui:develop-${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
- name: Build and publish container
uses: ./.github/actions/build-container
with:
actor: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
image_tag: ${{ steps.tags.outputs.IMAGE_TAG }}
release_version: develop-${{ steps.sha.outputs.OC_SHORTHASH }}
registry: ${{ env.REGISTRY }}
- name: Install crane
uses: imjasonh/setup-crane@v0.4
- name: Tag and push latest image
run: |
# Extract the repository part (everything before the last colon)
REPO=$(echo "${{ steps.tags.outputs.IMAGE_TAG }}" | sed 's/:.*$//')
# Create the new tag
NEW_TAG="${REPO}:develop-latest"
echo "Copying ${{ steps.tags.outputs.IMAGE_TAG }} to ${NEW_TAG}"
crane copy "${{ steps.tags.outputs.IMAGE_TAG }}" "${NEW_TAG}"

View File

@ -59,6 +59,12 @@ jobs:
VERSION_NUMBER=${{ steps.version_number.outputs.RELEASE_VERSION }}
echo "BRANCH_NAME=v${VERSION_NUMBER%.*}" >> $GITHUB_ENV
# checkout opencost UI to allow access to actions
- name: Checkout actions
uses: actions/checkout@v4
with:
repository: 'opencost/opencost-ui'
- name: Checkout Repo
uses: actions/checkout@v4
with:
@ -73,15 +79,6 @@ jobs:
echo "OC_SHORTHASH=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
popd
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set OpenCost Image Tags
id: tags
run: |
@ -89,31 +86,17 @@ jobs:
echo "IMAGE_TAG_UI_LATEST=ghcr.io/opencost/opencost-ui:latest" >> $GITHUB_OUTPUT
echo "IMAGE_TAG_UI_VERSION=ghcr.io/opencost/opencost-ui:${{ steps.version_number.outputs.RELEASE_VERSION }}" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and publish container
uses: ./.github/actions/build-container
with:
buildkitd-flags: --debug
- name: Set up just
uses: extractions/setup-just@v2
actor: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
image_tag: ${{ steps.tags.outputs.IMAGE_TAG_UI }}
release_version: ${{ steps.version_number.outputs.RELEASE_VERSION }}
registry: ${{ env.REGISTRY }}
- name: Install crane
uses: imjasonh/setup-crane@v0.3
## Install manifest-tool, which is required to combine multi-arch images
## https://github.com/estesp/manifest-tool
- name: Install manifest-tool
uses: imjasonh/setup-crane@v0.4
- name: Tag and push latest image
run: |
mkdir -p manifest-tool
pushd manifest-tool
wget -q https://github.com/estesp/manifest-tool/releases/download/v2.0.8/binaries-manifest-tool-2.0.8.tar.gz
tar -xzf binaries-manifest-tool-2.0.8.tar.gz
cp manifest-tool-linux-amd64 manifest-tool
echo "$(pwd)" >> $GITHUB_PATH
- name: Build and push (multiarch) OpenCost UI
working-directory: ./opencost-ui
run: |
just build '${{ steps.tags.outputs.IMAGE_TAG_UI }}' '${{ steps.version_number.outputs.RELEASE_VERSION }}'
crane copy '${{ steps.tags.outputs.IMAGE_TAG_UI }}' '${{ steps.tags.outputs.IMAGE_TAG_UI_LATEST }}'
crane copy '${{ steps.tags.outputs.IMAGE_TAG_UI }}' '${{ steps.tags.outputs.IMAGE_TAG_UI_VERSION }}'
crane copy "${{ steps.tags.outputs.IMAGE_TAG_UI }}" "${{ steps.tags.outputs.IMAGE_TAG_UI_LATEST }}"
crane copy "${{ steps.tags.outputs.IMAGE_TAG_UI }}" "${{ steps.tags.outputs.IMAGE_TAG_UI_VERSION }}"

61
.github/workflows/build-test-image.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Build and Publish UI Test Image
on:
merge_group:
types: [checks_requested]
pull_request_target:
branches:
- main
env:
REGISTRY: ghcr.io
jobs:
check_actor_permissions:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request_target' }}
outputs:
ismaintainer: ${{ steps.determine-maintainer.outputs.ismaintainer }}
steps:
- name: Check team membership
uses: tspascoal/get-user-teams-membership@v3
id: teamAffiliation
with:
GITHUB_TOKEN: ${{ secrets.ORG_READER_PAT }}
username: ${{ github.actor }}
organization: opencost
- name: determine if actor is a maintainer
id: determine-maintainer
run: |
echo "Actor: ${{ github.actor }}"
echo "Is maintainer: ${{ contains(steps.teamAffiliation.outputs.teams, 'OpenCost Maintainers') || contains(github.actor, 'dependabot[bot]') }}"
echo "ismaintainer=${{ contains(steps.teamAffiliation.outputs.teams, 'OpenCost Maintainers') || contains(github.actor, 'dependabot[bot]') }}" >> $GITHUB_OUTPUT
build-and-publish-test-image:
runs-on: ubuntu-latest
needs: check_actor_permissions
if: ${{ (always() && !cancelled()) && ( github.event_name == 'merge_group' || needs.check_actor_permissions.outputs.ismaintainer == 'true') }}
permissions:
contents: read
packages: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.merge_group.head_sha || github.event.pull_request.head.sha }}
- name: Set SHA
id: sha
run: |
echo "OC_SHORTHASH=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Set OpenCost Image Tags
id: tags
run: |
echo "IMAGE_TAG=ghcr.io/${{ github.repository_owner }}/opencost-ui:test-${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
- name: Build and publish container
uses: ./.github/actions/build-container
with:
actor: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
image_tag: ${{ steps.tags.outputs.IMAGE_TAG }}
release_version: test-${{ steps.sha.outputs.OC_SHORTHASH }}
registry: ${{ env.REGISTRY }}

View File

@ -19,7 +19,7 @@ jobs:
-
name: Install just
uses: extractions/setup-just@v2
uses: extractions/setup-just@v3
-
name: Install node

39
.github/workflows/promote-to-demo.yaml vendored Normal file
View File

@ -0,0 +1,39 @@
name: Promote UI to Demo
on:
workflow_run:
workflows: [Build and Publish Develop UI]
types: [completed]
branches: [main]
concurrency:
group: build-opencost-ui-develop
cancel-in-progress: false
jobs:
prep-image-name:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
outputs:
image_tag: ${{ steps.tags.outputs.IMAGE_TAG }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Set SHA
id: sha
run: |
echo "OC_SHORTHASH=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Set OpenCost Image Tags
id: tags
run: |
echo "IMAGE_TAG=ghcr.io/${{ github.repository_owner }}/opencost-ui:develop-${{ steps.sha.outputs.OC_SHORTHASH }}" >> $GITHUB_OUTPUT
install-on-demo:
needs: [prep-image-name]
uses: opencost/opencost-infra/.github/workflows/promote-to-oc-demo.yaml@main
secrets: inherit
with:
img-fqdn: ${{ needs.prep-image-name.outputs.image_tag }}
is_be: false
is_fe: true

View File

@ -2,16 +2,16 @@
# OpenCost UI
<img src="./opencost-header.png"/>
<img src="src/images/logo.png"/>
This is the web UI for the [OpenCost](http://github.com/opencost/opencost) project. You can learn more about the [User Interface](https://www.opencost.io/docs/installation/ui) in the OpenCost docs.
[![OpenCost UI Walkthrough](./ui/src/thumbnail.png)](https://youtu.be/lCP4Ci9Kcdg)
[![OpenCost UI Walkthrough](./src/thumbnail.png)](https://youtu.be/lCP4Ci9Kcdg)
*OpenCost UI Walkthrough*
## Installing
See https://www.opencost.io/docs/install for the full instructions.
See [Installation Guide](https://opencost.io/docs/installation/install) for the full instructions.
## Using

View File

@ -18,8 +18,9 @@ else
sed -i "s^PLACEHOLDER_FOOTER_CONTENT^OpenCost version: $VERSION ($HEAD)^g" /var/www/*.js
fi
envsubst '$API_PORT $API_SERVER $UI_PORT' < /etc/nginx/conf.d/default.nginx.conf.template > /etc/nginx/conf.d/default.nginx.conf
if [[ ! -e /etc/nginx/conf.d/default.nginx.conf ]];then
envsubst '$API_PORT $API_SERVER $UI_PORT' < /etc/nginx/conf.d/default.nginx.conf.template > /etc/nginx/conf.d/default.nginx.conf
fi
echo "Starting OpenCost UI version $VERSION ($HEAD)"
# Run the parent (nginx) container's entrypoint script

3713
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,14 @@
"defaults"
],
"dependencies": {
"@babel/runtime": "^7.23.9",
"@date-io/core": "^1.3.13",
"@babel/runtime": "^7.27.0",
"@date-io/core": "^3.2.0",
"@date-io/date-fns": "^1.3.13",
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/pickers": "^3.3.10",
"@material-ui/styles": "^4.11.5",
"axios": "^1.6.0",
"axios": "^1.8.4",
"date-fns": "^2.30.0",
"html-to-react": "^1.7.0",
"material-design-icons-iconfont": "^6.1.0",
@ -29,15 +29,15 @@
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"recharts": "^2.2.0"
"recharts": "^2.15.3"
},
"devDependencies": {
"@babel/core": "^7.13.10",
"@babel/core": "^7.28.0",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-transform-runtime": "^7.23.9",
"@babel/preset-react": "^7.12.13",
"@babel/plugin-transform-runtime": "^7.26.10",
"@babel/preset-react": "^7.26.3",
"buffer": "^6.0.3",
"parcel": "^2.11.0",
"parcel": "^2.14.4",
"process": "^0.11.10",
"set-value": "4.1.0"
},

View File

@ -23,10 +23,7 @@ const useStyles = makeStyles({
const Header = (props) => {
const classes = useStyles();
const { title, breadcrumbs } = props;
const { pathname } = useLocation();
const headerTitle = pathname === "/cloud" ? "Cloud Costs" : "Cost Allocation";
const { title, breadcrumbs, headerTitle } = props;
return (
<div className={classes.root}>
@ -36,7 +33,7 @@ const Header = (props) => {
<div className={classes.context}>
{title && (
<Typography variant="h4" className={classes.title}>
{props.title}
{title}
</Typography>
)}
{breadcrumbs && breadcrumbs.length > 0 && (

View File

@ -6,6 +6,8 @@ import { BarChart } from "@material-ui/icons";
import { Cloud } from "@material-ui/icons";
import { makeStyles } from "@material-ui/styles";
const logo = new URL("../../images/logo.png", import.meta.url).href;
const DRAWER_WIDTH = 200;
const SidebarNav = ({ active }) => {
@ -44,6 +46,7 @@ const SidebarNav = ({ active }) => {
icon: <BarChart />,
},
{ name: "Cloud Costs", href: "cloud", icon: <Cloud /> },
{ name: "External Costs", href: "external-costs", icon: <Cloud /> },
];
return (
@ -54,7 +57,7 @@ const SidebarNav = ({ active }) => {
variant={"permanent"}
>
<img
src={require("../../images/logo.png")}
src={logo}
alt="OpenCost"
style={{ flexShrink: 1, padding: "1rem" }}
/>

View File

@ -48,6 +48,7 @@ function stableSort(array, comparator) {
const headCells = [
{ id: "name", numeric: false, label: "Name", width: "auto" },
{ id: "cpuCost", numeric: true, label: "CPU", width: 90 },
{ id: "gpuCost", numeric: true, label: "GPU", width: 90 },
{ id: "ramCost", numeric: true, label: "RAM", width: 90 },
{ id: "pvCost", numeric: true, label: "PV", width: 90 },
{ id: "totalEfficiency", numeric: true, label: "Efficiency", width: 90 },
@ -185,6 +186,9 @@ const AllocationReport = ({
<TableCell align="right">
{toCurrency(row.cpuCost, currency)}
</TableCell>
<TableCell align="right">
{toCurrency(row.gpuCost, currency)}
</TableCell>
<TableCell align="right">
{toCurrency(row.ramCost, currency)}
</TableCell>
@ -209,6 +213,9 @@ const AllocationReport = ({
<TableCell align="right">
{toCurrency(row.cpuCost, currency)}
</TableCell>
<TableCell align="right">
{toCurrency(row.gpuCost, currency)}
</TableCell>
<TableCell align="right">
{toCurrency(row.ramCost, currency)}
</TableCell>
@ -231,8 +238,8 @@ const AllocationReport = ({
rowsPerPage={rowsPerPage}
rowsPerPageOptions={[10, 25, 50]}
page={Math.min(page, lastPage)}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</div>
);

View File

@ -13,7 +13,7 @@ import {
TableBody,
} from "@material-ui/core";
import { toCurrency } from "../util";
import { toCurrency } from "../../util";
import CloudCostChart from "./cloudCostChart";
import { CloudCostRow } from "./cloudCostRow";
@ -31,8 +31,6 @@ const CloudCost = ({
},
});
const classes = useStyles();
function descendingComparator(a, b, orderBy) {
@ -206,8 +204,8 @@ const CloudCost = ({
rowsPerPage={rowsPerPage}
rowsPerPageOptions={[10, 25, 50]}
page={Math.min(page, lastPage)}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</div>
</div>

View File

@ -10,8 +10,8 @@ import {
ResponsiveContainer,
Cell,
} from "recharts";
import { primary, greyscale, browns } from "../../constants/colors";
import { toCurrency } from "../../util";
import { primary, greyscale, browns } from "../../../constants/colors";
import { toCurrency } from "../../../util";
const RangeChart = ({ data, currency, height }) => {
const useStyles = makeStyles({

View File

@ -1,6 +1,6 @@
import * as React from "react";
import { Modal, Paper, Typography } from "@material-ui/core";
import Warnings from "../components/Warnings";
import Warnings from "../../components/Warnings";
import CircularProgress from "@material-ui/core/CircularProgress";
import {
@ -13,8 +13,8 @@ import {
BarChart,
Bar,
} from "recharts";
import { toCurrency } from "../util";
import cloudCostDayTotals from "../services/cloudCostDayTotals";
import { toCurrency } from "../../util";
import cloudCostDayTotals from "../../services/cloudCostDayTotals";
const CloudCostDetails = ({
onClose,

View File

@ -2,8 +2,8 @@ import * as React from "react";
import { TableCell, TableRow } from "@material-ui/core";
import { toCurrency } from "../util";
import { primary } from "../constants/colors";
import { toCurrency } from "../../util";
import { primary } from "../../constants/colors";
const displayCurrencyAsLessThanPenny = (amount, currency) =>
amount > 0 && amount < 0.01

View File

@ -6,7 +6,7 @@ import Select from "@material-ui/core/Select";
import * as React from "react";
import SelectWindow from "../../components/SelectWindow";
import SelectWindow from "../../SelectWindow";
const useStyles = makeStyles({
wrapper: {

View File

@ -0,0 +1,93 @@
import { Modal, Paper } from "@material-ui/core";
import {
TableContainer,
TableCell,
TableRow,
Table,
TableBody,
} from "@material-ui/core";
// for now, we can assume that the "Name" is resourceType
export const ExternalCostDetails = ({ row, onClose }) => (
<div>
<Modal
open={true}
onClose={onClose}
title={row.resource_type}
style={{ margin: "10%" }}
>
<Paper style={{ padding: 20 }}>
<TableContainer>
<Table>
<TableBody>
<TableRow>
<TableCell>account_name</TableCell>
<TableCell>{row.account_name}</TableCell>
</TableRow>
<TableRow>
<TableCell>aggregate</TableCell>
<TableCell>{row.aggregate}</TableCell>
</TableRow>
<TableRow>
<TableCell>charge_category</TableCell>
<TableCell>{row.charge_category}</TableCell>
</TableRow>
<TableRow>
<TableCell>cost</TableCell>
<TableCell>{row.cost}</TableCell>
</TableRow>
<TableRow>
<TableCell>cost_source</TableCell>
<TableCell>{row.cost_source}</TableCell>
</TableRow>
<TableRow>
<TableCell>cost_type</TableCell>
<TableCell>{row.cost_type}</TableCell>
</TableRow>
<TableRow>
<TableCell>description</TableCell>
<TableCell>{row.description}</TableCell>
</TableRow>
<TableRow>
<TableCell>domain</TableCell>
<TableCell>{row.domain}</TableCell>
</TableRow>
<TableRow>
<TableCell>id</TableCell>
<TableCell>{row.id}</TableCell>
</TableRow>
<TableRow>
<TableCell>list_unit_price</TableCell>
<TableCell>{row.list_unit_price}</TableCell>
</TableRow>
<TableRow>
<TableCell>provider_id</TableCell>
<TableCell>{row.provider_id}</TableCell>
</TableRow>
<TableRow>
<TableCell>resource_name</TableCell>
<TableCell>{row.resource_name}</TableCell>
</TableRow>
<TableRow>
<TableCell>resource_type</TableCell>
<TableCell>{row.resource_type}</TableCell>
</TableRow>
<TableRow>
<TableCell>usage_quantity</TableCell>
<TableCell>{row.usage_quantity}</TableCell>
</TableRow>
<TableRow>
<TableCell>usage_unit</TableCell>
<TableCell>{row.usage_unit}</TableCell>
</TableRow>
<TableRow>
<TableCell>zone</TableCell>
<TableCell>{row.zone}</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Paper>
</Modal>
</div>
);

View File

@ -0,0 +1,43 @@
import * as React from "react";
import { TableCell, TableRow } from "@material-ui/core";
import { toCurrency } from "../../util";
import { primary } from "../../constants/colors";
const displayCurrencyAsLessThanPenny = (amount, currency) =>
amount > 0 && amount < 0.01
? `<${toCurrency(0.01, currency)}`
: toCurrency(amount, currency);
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
const ExternalCostRow = ({
cost,
currency,
onClick,
name,
costType,
}) => {
return (
<TableRow onClick={onClick}>
<TableCell
align={"left"}
style={{ cursor: "pointer", color: "#346ef2", padding: "1rem" }}
>
{name}
</TableCell>
<TableCell align={"right"} style={{ paddingRight: "2em" }}>
{capitalizeFirstLetter(costType)}
</TableCell>
{/* total cost */}
<TableCell align={"right"} style={{ paddingRight: "2em" }}>
{`${displayCurrencyAsLessThanPenny(cost, currency)}`}
</TableCell>
</TableRow>
);
};
export { ExternalCostRow };

View File

@ -0,0 +1,31 @@
import * as React from "react";
import Typography from "@material-ui/core/Typography";
import RangeChart from "./rangeChart";
const ExternalCostsChart = ({
graphData,
currency,
n,
height,
aggregateBy,
}) => {
if (graphData.length === 0) {
return (
<Typography variant="body2" style={{ padding: "4em" }}>
No data
</Typography>
);
}
return (
<RangeChart
data={graphData}
currency={currency}
height={height}
aggregateBy={aggregateBy}
/>
);
};
export default React.memo(ExternalCostsChart);

View File

@ -0,0 +1,78 @@
import { makeStyles } from "@material-ui/styles";
import FormControl from "@material-ui/core/FormControl";
import InputLabel from "@material-ui/core/InputLabel";
import MenuItem from "@material-ui/core/MenuItem";
import Select from "@material-ui/core/Select";
import * as React from "react";
import SelectWindow from "../../components/SelectWindow";
import {
windowOptions,
aggregationOptions,
costTypeOptions
} from '../../components/externalCosts/tokens'
const useStyles = makeStyles({
wrapper: {
display: "inline-flex",
},
formControl: {
margin: 8,
minWidth: 120,
},
});
function ExternalCostsControls({
window,
setWindow,
aggregateBy,
setAggregateBy,
costType,
setCostType
}) {
const classes = useStyles();
return (
<div className={classes.wrapper}>
<SelectWindow
windowOptions={windowOptions}
window={window}
setWindow={setWindow}
/>
<FormControl className={classes.formControl}>
<InputLabel id="aggregation-select-label">Breakdown</InputLabel>
<Select
id="aggregation-select"
value={aggregateBy}
onChange={(e) => {
setAggregateBy(e.target.value);
}}
>
{aggregationOptions.map((opt) => (
<MenuItem key={opt.value} value={opt.value}>
{opt.name}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl className={classes.formControl}>
<InputLabel id="aggregation-select-label">Cost Type</InputLabel>
<Select
id="cost-type-select"
value={costType}
onChange={(e) => {
setCostType(e.target.value);
}}
>
{costTypeOptions.map((opt) => (
<MenuItem key={opt.value} value={opt.value}>
{opt.name}
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
export default React.memo(ExternalCostsControls);

View File

@ -0,0 +1,182 @@
import * as React from "react";
import { makeStyles } from "@material-ui/styles";
import {
Typography,
TableContainer,
TableCell,
TableHead,
TablePagination,
TableRow,
TableSortLabel,
Table,
TableBody,
} from "@material-ui/core";
import { useLocation, useHistory } from "react-router";
import { toCurrency } from "../../util";
import { ExternalCostRow } from "./externalCostRow";
import { aggToKeyMapExternalCosts } from "./tokens";
const ExternalCostsTable = ({
tableData,
currency = "USD",
aggregateBy = "usageUnit",
drilldown,
}) => {
const useStyles = makeStyles({
noResults: {
padding: 24,
},
});
const classes = useStyles();
const headCells = [
{
id: "aggregate",
numeric: false,
label: "Name",
width: "auto",
},
{
id: "costType",
numeric: false,
label: "Cost Type",
width: 160,
},
{
id: "cost",
numeric: true,
label: "Cost",
width: 160,
},
];
const routerLocation = useLocation();
const searchParams = new URLSearchParams(routerLocation.search);
const routerHistory = useHistory();
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(25);
const numData = tableData.customCosts?.length ?? 0;
const lastPage = Math.floor(numData / rowsPerPage);
const handleChangePage = (event, newPage) => setPage(newPage);
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
let pageRows = [];
if (tableData && 'customCosts' in tableData) {
pageRows = tableData.customCosts.slice(
page * rowsPerPage,
page * rowsPerPage + rowsPerPage
);
}
React.useEffect(() => {
setPage(0);
}, [numData]);
if ('customCosts' in tableData && tableData.customCosts.length === 0) {
return (
<Typography variant="body2" className={classes.noResults}>
No results
</Typography>
);
}
function dataToExternalCostRow(row) {
return (
<ExternalCostRow
cost={row.cost}
key={row.usage_unit}
costType={row.cost_type}
onClick={() =>
// we don't want to allow drilldown on item without an empty name
row[aggToKeyMapExternalCosts[aggregateBy]] ? drilldown(row) : {}
}
name={row[aggToKeyMapExternalCosts[aggregateBy]] || "Unallocated"}
/>
);
}
const currentSortBy = searchParams.get("sortBy");
const currentSortDirection = searchParams.get("sortDirection");
return (
<div id="cloud-cost-table">
<TableContainer>
<Table>
<TableHead>
<TableRow>
{headCells.map((cell) => (
<TableCell
key={cell.id}
colSpan={cell.colspan}
align={cell.numeric ? "right" : "left"}
style={{ width: cell.width }}
>
<TableSortLabel
active={currentSortBy === cell.id}
direction={currentSortBy === cell.id ? currentSortDirection : 'desc'}
onClick={() => {
if (currentSortBy === cell.id) {
// then we simply need to update direction
searchParams.set(
"sortDirection",
currentSortDirection === "desc" ? "asc" : "desc"
);
} else {
searchParams.set("sortBy", cell.id);
searchParams.set("sortDirection", "desc");
}
routerHistory.push({
search: `?${searchParams.toString()}`,
});
}}
>
{cell.label}
</TableSortLabel>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell align={"left"} style={{ fontWeight: 500 }}>
{"Total Cost"}
</TableCell>
<TableCell
align={"right"}
style={{ fontWeight: 500, paddingRight: "2em" }}
>
{/* Cost Type */}
</TableCell>
<TableCell
align={"right"}
style={{ fontWeight: 500, paddingRight: "2em" }}
>
{toCurrency(tableData.totalCost || 0, currency)}
</TableCell>
</TableRow>
{pageRows.map(dataToExternalCostRow)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
component="div"
count={numData}
rowsPerPage={rowsPerPage}
rowsPerPageOptions={[10, 25, 50]}
page={Math.min(page, lastPage)}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</div>
);
};
export default React.memo(ExternalCostsTable);

View File

@ -0,0 +1,224 @@
import * as React from "react";
import { makeStyles } from "@material-ui/styles";
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
Cell,
} from "recharts";
import { primary, greyscale, browns } from "../../constants/colors";
import { toCurrency } from "../../util";
import { aggToKeyMapExternalCosts } from "./tokens";
const RangeChart = ({ data, currency, height, aggregateBy }) => {
const useStyles = makeStyles({
tooltip: {
borderRadius: 2,
background: "rgba(255, 255, 255, 0.95)",
padding: 12,
},
tooltipLineItem: {
fontSize: "1rem",
margin: 0,
marginBottom: 4,
padding: 0,
},
});
const accents = [...primary, ...greyscale, ...browns];
const getItemCost = (item) => {
return item.cost;
};
function toBar({ end, graph, start }) {
const points = graph.map((item) => ({
...item,
window: { end, start },
}));
const dateFormatter = Intl.DateTimeFormat(navigator.language, {
year: "numeric",
month: "numeric",
day: "numeric",
timeZone: "UTC",
});
const timeFormatter = Intl.DateTimeFormat(navigator.language, {
hour: "numeric",
minute: "numeric",
timeZone: "UTC",
});
const s = new Date(start);
const e = new Date(end);
const interval = (e.valueOf() - s.valueOf()) / 1000 / 60 / 60;
const bar = {
end: new Date(end),
key: interval >= 24 ? dateFormatter.format(s) : timeFormatter.format(s),
items: {},
start: new Date(start),
};
points.forEach((item) => {
const windowStart = new Date(item.window.start);
const windowEnd = new Date(item.window.end);
const windowHours =
(windowEnd.valueOf() - windowStart.valueOf()) / 1000 / 60 / 60;
if (windowHours >= 24) {
bar.key = dateFormatter.format(bar.start);
} else {
bar.key = timeFormatter.format(bar.start);
}
bar.items[item[aggToKeyMapExternalCosts[aggregateBy]] || 'Unallocated'] =
getItemCost(item);
});
return bar;
}
const getDataForDay = (dayData) => {
const { end, start } = dayData.window;
const copy = [...dayData.customCosts];
const sortedItems = copy.slice().sort((a, b) => {
return a.cost > b.cost ? -1 : 1;
});
const top8 = sortedItems.slice(0, 8);
return { end, start, graph: top8 };
};
const getDataForGraph = (dataPoints) => {
const orderedDataPoints = dataPoints.map(getDataForDay);
const bars = orderedDataPoints.map(toBar);
const keyToFill = {};
// we want to keep track of the order of fill assignment
const assignmentOrder = [];
let p = 0;
orderedDataPoints.forEach(({ graph, start, end }) => {
graph.forEach((item) => {
const key = item[aggToKeyMapExternalCosts[aggregateBy]] || 'Unallocated';
if (keyToFill[key] === undefined) {
assignmentOrder.push(key);
keyToFill[key] = accents[p];
p = (p + 1) % accents.length;
}
});
});
const labels = assignmentOrder.map((dataKey) => ({
dataKey,
fill: keyToFill[dataKey],
}));
return { bars, labels, keyToFill };
};
const {
bars: barData,
labels: barLabels,
keyToFill,
} = getDataForGraph(data.timeseries);
const classes = useStyles();
const CustomTooltip = (params) => {
const { active, payload } = params;
if (!payload || payload.length == 0) {
return null;
}
// come back and address issue with non-unique key names
const total = payload.reduce((sum, item) => sum + item.value, 0.0);
if (active) {
return (
<div className={classes.tooltip}>
<p
className={classes.tooltipLineItem}
style={{ color: "#000000" }}
>{`Total: ${toCurrency(total, currency)}`}</p>
{payload
.slice()
.map((item, i) => (
<div
key={i}
style={{
display: "grid",
gridTemplateColumns: "20px 1fr",
gap: ".5em",
margin: ".25em",
}}
>
<div>
<div
style={{
backgroundColor: keyToFill[item.payload.items[i][0]],
width: 18,
height: 18,
}}
/>
</div>
<div>
<p className={classes.tooltipLineItem}>{`${
item.payload.items[i][0]
}: ${toCurrency(item.value, currency)}`}</p>
</div>
</div>
))
.reverse()}
</div>
);
}
return null;
};
const orderedBars = barData.map((bar) => {
return {
...bar,
items: Object.entries(bar.items).sort((a, b) => (a[1] > b[1] ? -1 : 1)),
};
});
return (
<ResponsiveContainer height={height} width={"100%"}>
<BarChart
data={orderedBars}
margin={{ top: 30, right: 35, left: 30, bottom: 45 }}
>
<CartesianGrid strokeDasharray={"3 3"} vertical={false} />
<XAxis dataKey={"key"} />
<YAxis tickFormatter={(val) => toCurrency(val, currency, 2, true)} />
<Tooltip content={<CustomTooltip />} wrapperStyle={{ zIndex: 1000 }} />
{new Array(10).fill(0).map((item, idx) => (
<Bar
dataKey={(entry) => (entry.items[idx] ? entry.items[idx][1] : null)}
stackId="x"
>
{orderedBars.map((bar) =>
bar.items[idx] ? (
<Cell fill={keyToFill[bar.items[idx][0]]} />
) : (
<Cell />
)
)}
</Bar>
))}
</BarChart>
</ResponsiveContainer>
);
};
export default RangeChart;

View File

@ -0,0 +1,39 @@
const costTypeOptions = [
{ name: "Blended", value: "blended" },
{ name: "Billed", value: "billed" },
{ name: "List", value: "list" },
];
const windowOptions = [
{ name: "Today", value: "today" },
{ name: "Yesterday", value: "yesterday" },
{ name: "Last 24h", value: "24h" },
{ name: "Last 48h", value: "48h" },
{ name: "Week-to-date", value: "week" },
{ name: "Last week", value: "lastweek" },
{ name: "Last 7 days", value: "7d" },
{ name: "Last 14 days", value: "14d" },
];
const aggregationOptions = [
{ name: "Domain", value: "domain" },
{ name: "Account Name", value: "accountName" },
{ name: "Resource Name", value: "resourceName" },
{ name: "Resource Type", value: "resourceType" },
{ name: "Zone", value: "zone" },
{ name: "Charge Category", value: "chargeCategory" },
{ name: "Provider ID", value: "providerId" },
{ name: "Usage Unit", value: "usageUnit" },
];
const aggToKeyMapExternalCosts = {
zone: "zone",
accountName: "account_name",
chargeCategory: "charge_category",
resourceName: "resource_name",
resourceType: "resource_type",
providerId: "provider_id",
usageUnit: "usage_unit",
domain: "domain",
};
export { costTypeOptions, windowOptions, aggregationOptions, aggToKeyMapExternalCosts };

View File

@ -18,21 +18,21 @@ import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import { useLocation, useHistory } from "react-router";
import AllocationReport from "./components/allocationReport";
import Controls from "./components/Controls";
import Header from "./components/Header";
import Page from "./components/Page";
import Footer from "./components/Footer";
import Subtitle from "./components/Subtitle";
import Warnings from "./components/Warnings";
import AllocationService from "./services/allocation";
import AllocationReport from "../components/allocationReport";
import Controls from "../components/Controls";
import Header from "../components/Header";
import Page from "../components/Page";
import Footer from "../components/Footer";
import Subtitle from "../components/Subtitle";
import Warnings from "../components/Warnings";
import AllocationService from "../services/allocation";
import {
checkCustomWindow,
cumulativeToTotals,
rangeToCumulative,
toVerboseTimeRange,
} from "./util";
import { currencyCodes } from "./constants/currencyCodes";
} from "../util";
import { currencyCodes } from "../constants/currencyCodes";
const windowOptions = [
{ name: "Today", value: "today" },
@ -231,7 +231,7 @@ const ReportsPage = () => {
}
return (
<Page active="reports.html">
<Header>
<Header headerTitle='Cost Allocation'>
<IconButton aria-label="refresh" onClick={() => setFetch(true)}>
<RefreshIcon />
</IconButton>

View File

@ -1,7 +1,7 @@
import * as React from "react";
import Page from "./components/Page";
import Header from "./components/Header";
import Footer from "./components/Footer";
import Page from "../components/Page";
import Header from "../components/Header";
import Footer from "../components/Footer";
import IconButton from "@material-ui/core/IconButton";
import RefreshIcon from "@material-ui/icons/Refresh";
import { makeStyles } from "@material-ui/styles";
@ -10,23 +10,24 @@ import CircularProgress from "@material-ui/core/CircularProgress";
import { get, find } from "lodash";
import { useLocation, useHistory } from "react-router";
import { checkCustomWindow, toVerboseTimeRange } from "./util";
import CloudCostEditControls from "./cloudCost/controls/cloudCostEditControls";
import Subtitle from "./components/Subtitle";
import Warnings from "./components/Warnings";
import CloudCostTopService from "./services/cloudCostTop";
import { checkCustomWindow, toVerboseTimeRange } from "../util";
import CloudCostEditControls from "../components/cloudCost/controls/cloudCostEditControls";
import Subtitle from "../components/Subtitle";
import Warnings from "../components/Warnings";
import CloudCostTopService from "../services/cloudCostTop";
import {
windowOptions,
costMetricOptions,
aggregationOptions,
aggMap,
} from "./cloudCost/tokens";
import { currencyCodes } from "./constants/currencyCodes";
import CloudCost from "./cloudCost/cloudCost";
import { CloudCostDetails } from "./cloudCost/cloudCostDetails";
} from "../components/cloudCost/tokens";
const CloudCostReports = () => {
import { currencyCodes } from "../constants/currencyCodes";
import CloudCost from "../components/cloudCost/cloudCost";
import { CloudCostDetails } from "../components/cloudCost/cloudCostDetails";
const CloudCosts = () => {
const useStyles = makeStyles({
reportHeader: {
display: "flex",
@ -230,7 +231,7 @@ const CloudCostReports = () => {
return (
<Page active="cloud.html">
<Header>
<Header headerTitle='Cloud Costs'>
<IconButton aria-label="refresh" onClick={() => setFetch(true)}>
<RefreshIcon />
</IconButton>
@ -334,4 +335,4 @@ const CloudCostReports = () => {
);
};
export default React.memo(CloudCostReports);
export default React.memo(CloudCosts);

325
src/pages/ExternalCosts.js Normal file
View File

@ -0,0 +1,325 @@
import * as React from "react";
import Page from "../components/Page";
import Header from "../components/Header";
import Footer from "../components/Footer";
import IconButton from "@material-ui/core/IconButton";
import RefreshIcon from "@material-ui/icons/Refresh";
import { makeStyles } from "@material-ui/styles";
import { Paper, Button } from "@material-ui/core";
import CircularProgress from "@material-ui/core/CircularProgress";
import { useLocation, useHistory } from "react-router";
import Warnings from "../components/Warnings";
import ExternalCostsService from "../services/externalCosts";
import {
windowOptions,
aggregationOptions,
costTypeOptions,
} from "../components/externalCosts/tokens";
import ExternalCostsControls from "../components/externalCosts/externalCostsControls";
import ExternalCostsChart from "../components/externalCosts/externalCostsChart";
import ExternalCostsTable from "../components/externalCosts/externalCostsTable";
import { aggToKeyMapExternalCosts } from "../components/externalCosts/tokens";
import { ExternalCostDetails } from "../components/externalCosts/externalCostDetailModal";
const ExternalCosts = () => {
const useStyles = makeStyles({
reportHeader: {
display: "flex",
flexFlow: "row",
padding: 24,
},
titles: {
flexGrow: 1,
},
});
const classes = useStyles();
const [window, setWindow] = React.useState(windowOptions[0].value);
const [costType, setCostType] = React.useState(costTypeOptions[0].value);
const [sortBy, setSortBy] = React.useState("cost");
const [sortDirection, setSortDirection] = React.useState("desc");
const [aggregateBy, setAggregateBy] = React.useState(
aggregationOptions[0].value
);
const [filters, setFilters] = React.useState([]);
const [currency, setCurrency] = React.useState("USD");
// page and settings state
const [init, setInit] = React.useState(false);
const [fetch, setFetch] = React.useState(false);
const [loading, setLoading] = React.useState(true);
const [errors, setErrors] = React.useState([]);
const [showModal, setShowModal] = React.useState(false);
// data
const [externalCostData, setExternalCostData] = React.useState([]);
const [externalCostTableData, setExternalCostTableData] = React.useState([]);
// parse any context information from the URL
const routerLocation = useLocation();
const searchParams = new URLSearchParams(routerLocation.search);
const routerHistory = useHistory();
async function initialize() {
setInit(true);
}
async function fetchChartData() {
try {
const resp = await ExternalCostsService.fetchExternalGraphCosts(
window,
aggregateBy,
filters,
costType,
sortBy,
sortDirection
);
if (resp) {
setExternalCostData(resp);
} else {
if (resp.message && resp.message.indexOf("boundary error") >= 0) {
let match = resp.message.match(/(ETL is \d+\.\d+% complete)/);
let secondary = "Try again after ETL build is complete";
if (match.length > 0) {
secondary = `${match[1]}. ${secondary}`;
}
setErrors([
{
primary: "Data unavailable while ETL is building",
secondary: secondary,
},
]);
}
setExternalCostData([]);
}
} catch (err) {
console.log(err);
if (err.message.indexOf("404") === 0) {
setErrors([
{
primary: "Failed to load report data",
secondary:
"Please update OpenCost to the latest version, and open an Issue if problems persist.",
},
]);
} else {
let secondary =
"Please open an Issue with OpenCost if problems persist.";
if (err.message.length > 0) {
secondary = err.message;
}
setErrors([
{
primary: "Failed to load report data",
secondary: secondary,
},
]);
}
setExternalCostData([]);
}
}
async function fetchTableData() {
try {
const resp = await ExternalCostsService.fetchExternalTableCosts(
window,
aggregateBy,
filters,
costType,
sortBy,
sortDirection
);
if (resp) {
setExternalCostTableData(resp);
} else {
if (resp.message && resp.message.indexOf("boundary error") >= 0) {
let match = resp.message.match(/(ETL is \d+\.\d+% complete)/);
let secondary = "Try again after ETL build is complete";
if (match.length > 0) {
secondary = `${match[1]}. ${secondary}`;
}
setErrors([
{
primary: "Data unavailable while ETL is building",
secondary: secondary,
},
]);
}
setExternalCostTableData([]);
}
} catch (err) {
console.log(err);
if (err.message.indexOf("404") === 0) {
setErrors([
{
primary: "Failed to load report data",
secondary:
"Please update OpenCost to the latest version, and open an Issue if problems persist.",
},
]);
} else {
let secondary =
"Please open an Issue with OpenCost if problems persist.";
if (err.message.length > 0) {
secondary = err.message;
}
setErrors([
{
primary: "Failed to load report data",
secondary: secondary,
},
]);
}
setExternalCostTableData([]);
}
}
async function fetchData() {
setLoading(true);
setErrors([]);
// todo: come back and have inidividual loading
await fetchChartData();
await fetchTableData();
setLoading(false);
}
function drilldown(row) {
// Drilldown order: domain > account Name > resource type > resource name
// We only want drilldown functionality on these items
if (
["domain", "accountName", "resourceType", "resourceName"].includes(
aggregateBy
)
) {
setFilters([
...filters,
{
property: aggregateBy,
value: row[aggToKeyMapExternalCosts[aggregateBy]],
},
]);
if (aggregateBy === "domain") {
setAggregateBy("accountName");
} else if (aggregateBy === "accountName") {
setAggregateBy("resourceType");
} else if (aggregateBy === "resourceType") {
setAggregateBy("resourceName");
} else {
setShowModal(row);
}
}
}
React.useEffect(() => {
setWindow(searchParams.get("window") || "7d");
setAggregateBy(searchParams.get("agg") || "domain");
setCurrency(searchParams.get("currency") || "USD");
setCostType(searchParams.get("costType") || "blended");
setSortBy(searchParams.get("sortBy") || "cost");
setSortDirection(searchParams.get("sortDirection") || "desc");
}, [routerLocation]);
// Initialize once, then fetch report each time setFetch(true) is called
React.useEffect(() => {
if (!init) {
initialize();
}
if (init || fetch) {
fetchData();
}
}, [init, fetch]);
React.useEffect(() => {
setFetch(!fetch);
}, [window, aggregateBy, filters, costType, sortBy, sortDirection]);
return (
<Page active="cloud.html">
{/* figure out if we need */}
<Header headerTitle="External Costs">
<IconButton aria-label="refresh" onClick={() => setFetch(true)}>
<RefreshIcon />
</IconButton>
</Header>
{!loading && errors.length > 0 && (
<div style={{ marginBottom: 20 }}>
<Warnings warnings={errors} />
</div>
)}
{init && (
<Paper id="cloud-cost">
<div className={classes.reportHeader}>
<ExternalCostsControls
costType={costType}
setCostType={(type) => {
searchParams.set("costType", type);
routerHistory.push({
search: `?${searchParams.toString()}`,
});
}}
window={window}
setWindow={(win) => {
searchParams.set("window", win);
routerHistory.push({
search: `?${searchParams.toString()}`,
});
}}
aggregateBy={aggregateBy}
setAggregateBy={(agg) => {
setFilters([]);
searchParams.set("agg", agg);
routerHistory.push({
search: `?${searchParams.toString()}`,
});
}}
/>
</div>
{loading && (
<div style={{ display: "flex", justifyContent: "center" }}>
<div style={{ paddingTop: 100, paddingBottom: 100 }}>
<CircularProgress />
</div>
</div>
)}
{!loading && (
<>
<ExternalCostsChart
graphData={externalCostData}
currency={"USD"}
height={300}
aggregateBy={aggregateBy}
/>
</>
)}
<Button
style={{ margin: ".5em" }}
variant="outlined"
onClick={() => setFilters([])}
>
Clear Filters
</Button>
{!loading && (
<>
<ExternalCostsTable
tableData={externalCostTableData}
aggregateBy={aggregateBy}
drilldown={drilldown}
/>
</>
)}
{showModal && (
<ExternalCostDetails
row={showModal}
onClose={() => setShowModal(null)}
/>
)}
</Paper>
)}
<Footer />
</Page>
);
};
export default React.memo(ExternalCosts);

View File

@ -1,8 +1,9 @@
import * as React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Reports from "./Reports.js";
import CloudCostReports from "./cloudCostReports.js";
import Reports from "./pages/Allocations.js";
import CloudCosts from "./pages/CloudCosts.js";
import ExternalCosts from "./pages/ExternalCosts.js";
const Routes = () => {
return (
@ -15,7 +16,10 @@ const Routes = () => {
<Reports />
</Route>
<Route exact path="/cloud">
<CloudCostReports />
<CloudCosts />
</Route>
<Route exact path="/external-costs">
<ExternalCosts />
</Route>
</Switch>
</Router>

View File

@ -1,6 +1,6 @@
import axios from "axios";
import { parseFilters } from "../util";
import { costMetricToPropName } from "../cloudCost/tokens";
import { costMetricToPropName } from "../components/cloudCost/tokens";
function formatItemsForCost({ data, costType }) {
return data.sets.map(({ cloudCosts, window }) => {

View File

@ -0,0 +1,60 @@
import axios from "axios";
// API blows up when comma is encoded
export function parseExternalCostFilters(filters) {
if (typeof filters === "string") {
return filters;
}
return (
[...new Set(filters.map((f) => `${f.property}:"${f.value}"`))].join(
'+'
) || ""
);
}
class ExternalCostsService {
BASE_URL = process.env.BASE_URL || "{PLACEHOLDER_BASE_URL}";
async fetchExternalGraphCosts(win, aggregate, filters, costType, sortBy, sortDirection) {
if (this.BASE_URL.includes("PLACEHOLDER_BASE_URL")) {
this.BASE_URL = `http://localhost:9090/model`;
}
const params = {
window: win,
aggregate: aggregate,
costType,
filter: parseExternalCostFilters(filters),
sortBy,
sortDirection
};
const result = await axios.get(`${this.BASE_URL}/customCost/timeseries`, {
params,
});
return result.data.data;
}
async fetchExternalTableCosts(win, aggregate, filters, costType, sortBy, sortDirection) {
if (this.BASE_URL.includes("PLACEHOLDER_BASE_URL")) {
this.BASE_URL = `http://localhost:9090/model`;
}
const params = {
window: win,
aggregate: aggregate,
costType,
filter: parseExternalCostFilters(filters),
sortBy,
sortDirection
};
const result = await axios.get(`${this.BASE_URL}/customCost/total`, {
params,
});
return result.data.data;
}
}
export default new ExternalCostsService();

View File

@ -1,5 +1,5 @@
import { forEach, get, round } from "lodash";
import { costMetricToPropName } from "./cloudCost/tokens";
import { costMetricToPropName } from "./components/cloudCost/tokens";
// rangeToCumulative takes an AllocationSetRange (type: array[AllocationSet])
// and accumulates the values into a single AllocationSet (type: object)