diff --git a/README.md b/README.md index d7ffa96ee..8f1db88d5 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ At the moment, the following artifacts kinds are supported *(with plans to suppo - [Kyverno policies](https://kyverno.io) - [Meshery designs](https://meshery.io) - [OLM operators](https://github.com/operator-framework) +- [OpenCost plugins](https://www.opencost.io) - [Open Policy Agent (OPA) policies](https://www.openpolicyagent.org/) - [Tekton tasks, pipelines and stepactions](https://tekton.dev/) - [Tinkerbell actions](https://tinkerbell.org/) diff --git a/charts/artifact-hub/Chart.yaml b/charts/artifact-hub/Chart.yaml index 95a8591c1..458f1492e 100644 --- a/charts/artifact-hub/Chart.yaml +++ b/charts/artifact-hub/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: artifact-hub description: Artifact Hub is a web-based application that enables finding, installing, and publishing Cloud Native packages. type: application -version: 1.18.0 +version: 1.18.1-0 appVersion: 1.18.0 kubeVersion: ">= 1.19.0-0" home: https://artifacthub.io @@ -31,6 +31,7 @@ keywords: - headlamp - inspektor gadget - meshery + - opencost maintainers: - name: Sergio email: tegioz@icloud.com diff --git a/charts/artifact-hub/values.schema.json b/charts/artifact-hub/values.schema.json index b820c03c1..29d715d71 100644 --- a/charts/artifact-hub/values.schema.json +++ b/charts/artifact-hub/values.schema.json @@ -1162,7 +1162,7 @@ }, "repositoriesKinds": { "title": "Repositories kinds to process ([] = all)", - "description": "The following kinds are supported at the moment: falco, helm, olm, opa, tbaction, krew, helm-plugin, tekton-task, keda-scaler, coredns, keptn, tekton-pipeline, container, kubewarden, gatekeeper, kyverno, knative-client-plugin, backstage, argo-template, kubearmor, kcl, headlamp, inspektor-gadget, tekton-stepaction, meshery", + "description": "The following kinds are supported at the moment: falco, helm, olm, opa, tbaction, krew, helm-plugin, tekton-task, keda-scaler, coredns, keptn, tekton-pipeline, container, kubewarden, gatekeeper, kyverno, knative-client-plugin, backstage, argo-template, kubearmor, kcl, headlamp, inspektor-gadget, tekton-stepaction, meshery, opencost", "type": "array", "items": { "type": "string" diff --git a/cmd/ah/lint.go b/cmd/ah/lint.go index 3569f3bf2..2c11b2c3a 100644 --- a/cmd/ah/lint.go +++ b/cmd/ah/lint.go @@ -90,7 +90,7 @@ func newLintCmd() *cobra.Command { return lint(opts, &output{cmd.OutOrStdout()}) }, } - lintCmd.Flags().StringVarP(&opts.kind, "kind", "k", "helm", "repository kind: argo-template, backstage, coredns, falco, gatekeeper, headlamp, helm, helm-plugin, inspektor-gadget, kcl, keda-scaler, keptn, knative-client-plugin, krew, kubearmor, kubewarden, kyverno, meshery, olm, opa, tbaction, tekton-pipeline, tekton-stepaction, tekton-task") + lintCmd.Flags().StringVarP(&opts.kind, "kind", "k", "helm", "repository kind: argo-template, backstage, coredns, falco, gatekeeper, headlamp, helm, helm-plugin, inspektor-gadget, kcl, keda-scaler, keptn, knative-client-plugin, krew, kubearmor, kubewarden, kyverno, meshery, olm, opa, opencost, tbaction, tekton-pipeline, tekton-stepaction, tekton-task") lintCmd.Flags().StringVarP(&opts.path, "path", "p", ".", "repository's packages path") return lintCmd } @@ -125,6 +125,7 @@ func lint(opts *lintOptions, out *output) error { hub.Kyverno, hub.Meshery, hub.OPA, + hub.OpenCost, hub.TBAction: report = lintGeneric(opts.path, kind) case hub.Helm: @@ -631,6 +632,7 @@ func (out *output) printPkgDetails(pkg *hub.Package) { hub.Kyverno, hub.Meshery, hub.OPA, + hub.OpenCost, hub.TBAction: // Install diff --git a/database/migrations/schema/059_opencost_plugins.sql b/database/migrations/schema/059_opencost_plugins.sql new file mode 100644 index 000000000..396b6079b --- /dev/null +++ b/database/migrations/schema/059_opencost_plugins.sql @@ -0,0 +1,5 @@ +insert into repository_kind values (25, 'OpenCost plugins'); + +---- create above / drop below ---- + +delete from repository_kind where repository_kind_id = 25; diff --git a/database/tests/schema/schema.sql b/database/tests/schema/schema.sql index 1b11eeb32..17ed8aa55 100644 --- a/database/tests/schema/schema.sql +++ b/database/tests/schema/schema.sql @@ -566,7 +566,8 @@ select results_eq( (21, 'Headlamp plugins'), (22, 'Inspektor gadgets'), (23, 'Tekton stepactions'), - (24, 'Meshery designs') + (24, 'Meshery designs'), + (25, 'OpenCost plugins') $$, 'Repository kinds should exist' ); diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 5d9e5f889..c18be982e 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1598,6 +1598,29 @@ paths: $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" + "/packages/opencost/{repoName}/{packageName}": + get: + tags: + - Packages + summary: Get package details + description: Get package details + operationId: getOpencostPluginDetails + parameters: + - $ref: "#/components/parameters/RepoNameParam" + - $ref: "#/components/parameters/PackageNameParam" + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/OpencostPlugin" + "404": + $ref: "#/components/responses/NotFoundResponse" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalServerError" "/packages/tbaction/{repoName}/{packageName}": get: tags: @@ -2195,6 +2218,30 @@ paths: $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" + "/packages/opencost/{repoName}/{packageName}/{version}": + get: + tags: + - Packages + summary: Get package version details + description: Get package version details + operationId: getOpencostPluginVersionDetails + parameters: + - $ref: "#/components/parameters/RepoNameParam" + - $ref: "#/components/parameters/PackageNameParam" + - $ref: "#/components/parameters/VersionParam" + responses: + "200": + description: "" + content: + application/json: + schema: + $ref: "#/components/schemas/OpencostPlugin" + "404": + $ref: "#/components/responses/NotFoundResponse" + "429": + $ref: "#/components/responses/TooManyRequests" + "500": + $ref: "#/components/responses/InternalServerError" "/packages/tbaction/{repoName}/{packageName}/{version}": get: tags: @@ -4460,6 +4507,8 @@ components: policy1: | - macro: text condition: (evt.num < 0) + OpencostPlugin: + $ref: "#/components/schemas/Package" TBActionPackage: $ref: "#/components/schemas/Package" TektonPipelinePackage: @@ -4969,6 +5018,7 @@ components: * `22` - Inspektor gadgets * `23` - Tekton stepactions * `24` - Meshery designs + * `25` - Opencost plugins RepositoryKindParam: type: string enum: @@ -4997,6 +5047,7 @@ components: - inspektor-gadget - tekton-stepaction - meshery + - opencost description: | Repository kind name: * `helm` - Helm charts @@ -5024,6 +5075,7 @@ components: * `inspektor-gadget` - Inspektor gadgets * `tekton-stepaction` - Tekton stepactions * `meshery` - Meshery designs + * `opencost` - Opencost plugins RepositorySummary: type: object required: @@ -5568,6 +5620,7 @@ components: * `22` - Inspektor gadgets * `23` - Tekton stepactions * `24` - Meshery designs + * `25` - Opencost plugins PackageNameParam: in: path name: packageName diff --git a/docs/opencost_plugins_repositories.md b/docs/opencost_plugins_repositories.md new file mode 100644 index 000000000..577c60789 --- /dev/null +++ b/docs/opencost_plugins_repositories.md @@ -0,0 +1,49 @@ +## OpenCost plugins repositories + +OpenCost plugins repositories are expected to be hosted in GitHub, GitLab or Bitbucket repos. When adding your repository to Artifact Hub, the url used **must** follow the following format: + +- `https://github.com/user/repo[/path/to/packages]` +- `https://gitlab.com/user/repo[/path/to/packages]` +- `https://bitbucket.org/user/repo[/path/to/packages]` + +By default the `master` branch is used, but it's possible to specify a different one from the UI. + +*Please NOTE that the repository URL used when adding the repository to Artifact Hub **must NOT** contain the git hosting platform specific parts, like **tree/branch**, just the path to your packages like it would show in the filesystem.* + +The *path/to/packages* provided can contain metadata for one or more packages. Each package version **must** be on a separate folder, and it's up to you to decide if you want to publish one or multiple versions of your package. + +The structure of a repository with multiple plugins packages and versions could look something like this: + +```sh +$ tree path/to/packages +path/to/packages +├── artifacthub-repo.yml +├── package1 +│   ├── 1.0.0 +│   │   ├── README.md +│   │   └── artifacthub-pkg.yml +│   └── 2.0.0 +│      ├── README.md +│   └── artifacthub-pkg.yml +└── package2 + └── 1.0.0 +       ├── README.md + └── artifacthub-pkg.yml +``` + +This structure is flexible, and in some cases where you only have a package and a version it can be greatly simplified. In the case of a single package with a single version available at a time (the publisher doesn't want to make previous ones available, for example), the structure could look like this: + +```sh +$ tree path/to/packages +path/to/packages +├── artifacthub-repo.yml +└── package1 +    ├── README.md + └── artifacthub-pkg.yml +``` + +In the previous case, even the `package1` directory could be omitted. The reason is that both packages names and versions are read from the `artifacthub-pkg.yml` metadata file, so directories names are not used at all. + +Each package version **needs** an `artifacthub-pkg.yml` metadata file. Please see the file [spec](https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-pkg.yml) for more details. The [artifacthub-repo.yml](https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml) repository metadata file shown above can be used to setup features like [Verified publisher](https://github.com/artifacthub/hub/blob/master/docs/repositories.md#verified-publisher) or [Ownership claim](https://github.com/artifacthub/hub/blob/master/docs/repositories.md#ownership-claim). This file must be located at `/path/to/packages`. + +Once you have added your repository, you are all set up. As you add new versions of your plugins packages or new packages to your git repository, they'll be automatically indexed and listed in Artifact Hub. diff --git a/docs/repositories.md b/docs/repositories.md index 6332afc86..a477e8503 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -24,6 +24,7 @@ The following repositories kinds are supported at the moment: - [Meshery designs repositories](https://github.com/artifacthub/hub/blob/master/docs/meshery_designs_repositories.md) - [OLM operators repositories](https://github.com/artifacthub/hub/blob/master/docs/olm_operators_repositories.md) - [OPA policies repositories](https://github.com/artifacthub/hub/blob/master/docs/opa_policies_repositories.md) +- [OpenCost plugins repositories](https://github.com/artifacthub/hub/blob/master/docs/opencost_plugins_repositories.md) - [Tekton pipelines repositories](https://github.com/artifacthub/hub/blob/master/docs/tekton_pipelines_repositories.md) - [Tekton tasks repositories](https://github.com/artifacthub/hub/blob/master/docs/tekton_tasks_repositories.md) - [Tekton stepactions repositories](https://github.com/artifacthub/hub/blob/master/docs/tekton_stepactions_repositories.md) diff --git a/docs/www/headers/opencost_plugins_repositories b/docs/www/headers/opencost_plugins_repositories new file mode 100644 index 000000000..eba0233c4 --- /dev/null +++ b/docs/www/headers/opencost_plugins_repositories @@ -0,0 +1,6 @@ +--- +title: "OpenCost plugins" +aliases: [ + "/opencost_plugins_repositories", +] +--- diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index df9c29f35..8de4e9cfc 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -265,7 +265,7 @@ func (h *Handlers) setupRouter() { r.Get("/stats", h.Packages.GetStats) r.With(corsMW).Get("/search", h.Packages.Search) r.With(h.Users.RequireLogin).Get("/starred", h.Packages.GetStarredByUser) - r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns|^keptn|^tekton-pipeline|^container|^kubewarden|^gatekeeper|^kyverno|^knative-client-plugin|^backstage|^argo-template|^kubearmor|^kcl|^headlamp|^inspektor-gadget|^tekton-stepaction|^meshery$}/{repoName}/{packageName}", func(r chi.Router) { + r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns|^keptn|^tekton-pipeline|^container|^kubewarden|^gatekeeper|^kyverno|^knative-client-plugin|^backstage|^argo-template|^kubearmor|^kcl|^headlamp|^inspektor-gadget|^tekton-stepaction|^meshery|^opencost$}/{repoName}/{packageName}", func(r chi.Router) { r.Get("/feed/rss", h.Packages.RssFeed) r.With(corsMW).Get("/summary", h.Packages.GetSummary) r.Get("/{version}", h.Packages.Get) @@ -430,7 +430,7 @@ func (h *Handlers) setupRouter() { // Index special entry points r.Route("/packages", func(r chi.Router) { - r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns|^keptn|^tekton-pipeline|^container|^kubewarden|^gatekeeper|^kyverno|^knative-client-plugin|^backstage|^argo-template|^kubearmor|^kcl|^headlamp|^inspektor-gadget|^tekton-stepaction|^meshery$}/{repoName}/{packageName}", func(r chi.Router) { + r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns|^keptn|^tekton-pipeline|^container|^kubewarden|^gatekeeper|^kyverno|^knative-client-plugin|^backstage|^argo-template|^kubearmor|^kcl|^headlamp|^inspektor-gadget|^tekton-stepaction|^meshery|^opencost$}/{repoName}/{packageName}", func(r chi.Router) { r.With(h.Packages.InjectIndexMeta).Get("/{version}", h.Static.Index) r.With(h.Packages.InjectIndexMeta).Get("/", h.Static.Index) }) diff --git a/internal/handlers/pkg/handlers_test.go b/internal/handlers/pkg/handlers_test.go index 01a575165..ac6aa7851 100644 --- a/internal/handlers/pkg/handlers_test.go +++ b/internal/handlers/pkg/handlers_test.go @@ -2199,6 +2199,17 @@ func TestBuildURL(t *testing.T) { "2.0.0", baseURL + "/packages/meshery/repo1/pkg1/2.0.0", }, + { + &hub.Package{ + NormalizedName: "pkg1", + Repository: &hub.Repository{ + Kind: hub.OpenCost, + Name: "repo1", + }, + }, + "2.0.0", + baseURL + "/packages/opencost/repo1/pkg1/2.0.0", + }, } for _, tc := range testCases { tc := tc diff --git a/internal/hub/repo.go b/internal/hub/repo.go index dccd51f63..c1ddeb2a0 100644 --- a/internal/hub/repo.go +++ b/internal/hub/repo.go @@ -121,6 +121,9 @@ const ( // Meshery represents a repository with Meshery designs. Meshery RepositoryKind = 24 + + // OpenCost represents a repository with OpenCost plugins. + OpenCost RepositoryKind = 25 ) // GetKindName returns the name of the provided repository kind. @@ -168,6 +171,8 @@ func GetKindName(kind RepositoryKind) string { return "olm" case OPA: return "opa" + case OpenCost: + return "opencost" case TBAction: return "tbaction" case TektonPipeline: @@ -227,6 +232,8 @@ func GetKindFromName(kind string) (RepositoryKind, error) { return OLM, nil case "opa": return OPA, nil + case "opencost": + return OpenCost, nil case "tbaction": return TBAction, nil case "tekton-pipeline": diff --git a/internal/repo/manager.go b/internal/repo/manager.go index 826d4dc96..56257d960 100644 --- a/internal/repo/manager.go +++ b/internal/repo/manager.go @@ -99,6 +99,7 @@ var ( hub.Meshery, hub.OLM, hub.OPA, + hub.OpenCost, hub.TBAction, hub.TektonPipeline, hub.TektonTask, @@ -301,6 +302,7 @@ func (m *Manager) ClaimOwnership(ctx context.Context, repoName, orgName string) hub.Meshery, hub.OLM, hub.OPA, + hub.OpenCost, hub.TBAction, hub.TektonPipeline, hub.TektonTask, @@ -483,6 +485,7 @@ func (m *Manager) locateMetadataFile(r *hub.Repository, basePath string) string hub.Meshery, hub.OLM, hub.OPA, + hub.OpenCost, hub.TBAction, hub.TektonPipeline, hub.TektonTask, @@ -849,6 +852,7 @@ func (m *Manager) validateURL(r *hub.Repository) error { hub.Meshery, hub.OLM, hub.OPA, + hub.OpenCost, hub.TBAction, hub.TektonPipeline, hub.TektonTask, diff --git a/internal/tracker/helpers.go b/internal/tracker/helpers.go index e4293c5ea..4a56d8fdc 100644 --- a/internal/tracker/helpers.go +++ b/internal/tracker/helpers.go @@ -128,6 +128,7 @@ func SetupSource(i *hub.TrackerSourceInput) hub.TrackerSource { hub.Kyverno, hub.Meshery, hub.OPA, + hub.OpenCost, hub.TBAction: source = generic.NewTrackerSource(i) case hub.TektonTask, hub.TektonPipeline, hub.TektonStepAction: diff --git a/internal/tracker/tracker.go b/internal/tracker/tracker.go index e40c650db..f20e484f0 100644 --- a/internal/tracker/tracker.go +++ b/internal/tracker/tracker.go @@ -202,6 +202,7 @@ func (t *Tracker) cloneRepository() (string, string, error) { hub.Kyverno, hub.Meshery, hub.OPA, + hub.OpenCost, hub.TBAction, hub.TektonPipeline, hub.TektonTask, diff --git a/scripts/prepare-docs.sh b/scripts/prepare-docs.sh index a5e987d15..01226a10d 100755 --- a/scripts/prepare-docs.sh +++ b/scripts/prepare-docs.sh @@ -27,6 +27,7 @@ cat docs/www/headers/kyverno_policies_repositories docs/kyverno_policies_reposit cat docs/www/headers/meshery_designs_repositories docs/meshery_designs_repositories.md > docs/www/content/topics/repositories/meshery-designs.md cat docs/www/headers/olm_operators_repositories docs/olm_operators_repositories.md > docs/www/content/topics/repositories/olm-operators.md cat docs/www/headers/opa_policies_repositories docs/opa_policies_repositories.md > docs/www/content/topics/repositories/opa-policies.md +cat docs/www/headers/opencost_plugins_repositories docs/opencost_plugins_repositories.md > docs/www/content/topics/repositories/opencost-plugins.md cat docs/www/headers/tekton_pipelines_repositories docs/tekton_pipelines_repositories.md > docs/www/content/topics/repositories/tekton-pipelines.md cat docs/www/headers/tekton_tasks_repositories docs/tekton_tasks_repositories.md > docs/www/content/topics/repositories/tekton-tasks.md cat docs/www/headers/tekton_stepactions_repositories docs/tekton_stepactions_repositories.md > docs/www/content/topics/repositories/tekton-stepactions.md diff --git a/web/public/static/media/opencost-light.svg b/web/public/static/media/opencost-light.svg new file mode 100644 index 000000000..4dae808e9 --- /dev/null +++ b/web/public/static/media/opencost-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/public/static/media/opencost.svg b/web/public/static/media/opencost.svg new file mode 100644 index 000000000..9c2d2bdb6 --- /dev/null +++ b/web/public/static/media/opencost.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/web/public/static/media/opencost_icon.png b/web/public/static/media/opencost_icon.png new file mode 100644 index 000000000..41183042d Binary files /dev/null and b/web/public/static/media/opencost_icon.png differ diff --git a/web/public/static/media/placeholder_pkg_opencost.png b/web/public/static/media/placeholder_pkg_opencost.png new file mode 100644 index 000000000..843d16307 Binary files /dev/null and b/web/public/static/media/placeholder_pkg_opencost.png differ diff --git a/web/src/layout/common/Image.tsx b/web/src/layout/common/Image.tsx index 1c48d167a..a0507f97b 100644 --- a/web/src/layout/common/Image.tsx +++ b/web/src/layout/common/Image.tsx @@ -77,6 +77,8 @@ const Image = (props: Props) => { return '/static/media/placeholder_pkg_inspektor-gadget.png'; case RepositoryKind.MesheryDesign: return '/static/media/placeholder_pkg_meshery.png'; + case RepositoryKind.OpenCost: + return '/static/media/placeholder_pkg_opencost.png'; default: return PLACEHOLDER_SRC; } diff --git a/web/src/layout/common/RepositoryIcon.test.tsx b/web/src/layout/common/RepositoryIcon.test.tsx index 1b47c0ec0..a64194e24 100644 --- a/web/src/layout/common/RepositoryIcon.test.tsx +++ b/web/src/layout/common/RepositoryIcon.test.tsx @@ -156,6 +156,13 @@ describe('RepositoryIcon', () => { expect(icon).toHaveProperty('src', 'http://localhost/static/media/meshery-light.svg'); }); + it('renders Opencost plugin icon', () => { + render(); + const icon = screen.getByAltText('Icon'); + expect(icon).toBeInTheDocument(); + expect(icon).toHaveProperty('src', 'http://localhost/static/media/opencost-light.svg'); + }); + it('renders empty icon', () => { render(); expect(() => screen.getByAltText('Icon')).toThrow(); diff --git a/web/src/layout/common/RepositoryIcon.tsx b/web/src/layout/common/RepositoryIcon.tsx index 949df130e..62ede6bf8 100644 --- a/web/src/layout/common/RepositoryIcon.tsx +++ b/web/src/layout/common/RepositoryIcon.tsx @@ -109,6 +109,10 @@ const ICONS = { default: '/static/media/meshery.svg', white: '/static/media/meshery-light.svg', }, + [RepositoryKind.OpenCost]: { + default: '/static/media/opencost.svg', + white: '/static/media/opencost-light.svg', + }, }; const RepositoryIcon = (props: Props) => { diff --git a/web/src/layout/controlPanel/repositories/Modal.tsx b/web/src/layout/controlPanel/repositories/Modal.tsx index a4c1d8935..a5fe885c9 100644 --- a/web/src/layout/controlPanel/repositories/Modal.tsx +++ b/web/src/layout/controlPanel/repositories/Modal.tsx @@ -526,6 +526,17 @@ const RepositoryModal = (props: Props) => { ); break; + case RepositoryKind.OpenCost: + link = ( + + OpenCost plugins + + ); + break; } if (isUndefined(link)) return; @@ -558,6 +569,7 @@ const RepositoryModal = (props: Props) => { case RepositoryKind.InspektorGadget: case RepositoryKind.TektonStepAction: case RepositoryKind.MesheryDesign: + case RepositoryKind.OpenCost: return ( <>

{ RepositoryKind.InspektorGadget, RepositoryKind.TektonStepAction, RepositoryKind.MesheryDesign, + RepositoryKind.OpenCost, ].includes(selectedKind) && (

{ RepositoryKind.InspektorGadget, RepositoryKind.TektonStepAction, RepositoryKind.MesheryDesign, + RepositoryKind.OpenCost, ].includes(selectedKind) && (
diff --git a/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap b/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap index 093296107..201790a2b 100644 --- a/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap +++ b/web/src/layout/controlPanel/repositories/__snapshots__/Modal.test.tsx.snap @@ -164,6 +164,11 @@ exports[`Repository Modal - repositories section creates snapshot 1`] = ` > OPA policies +