Merge branch 'master' into docs-theming
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Updated the WebPack configuration to use `contenthash`. This fixes an issue were builds would sometimes generate output files with the same name but different content across builds, leading to breakages when loading the frontend app.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Added support for executing actions from the `ActionsRegistry` in the `scaffolder-backend`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Updated dependency `dompurify` to `^3.2.4`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
TechDocs page titles have been improved, especially for deeply nested pages.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-notifications': patch
|
||||
---
|
||||
|
||||
Fixed missing app context when rendering the notifications view
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
When possible, requests a more limited set of results from the Gitlab projects API, which can reduce the amount of network traffic required to sync with Gitlab.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Updating `catalog:get-catalog-entity` action to be `readOnly` and non destructive
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-react': patch
|
||||
---
|
||||
|
||||
Update to documentation regarding TechDocs redirects.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Modify the `backstage.json` also for custom patterns if it extends the default pattern.
|
||||
|
||||
Examples:
|
||||
|
||||
- `@backstage/*` (default pattern)
|
||||
- `@{backstage,backstage-community}/*`
|
||||
- `@{extra1,backstage,extra2}/*`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': minor
|
||||
---
|
||||
|
||||
Adding redirect handling for TechDocs URLs that reference entities that now reference an external entity for TechDocs. Including tests and documentation.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/catalog-client': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-events-backend': patch
|
||||
'@backstage/plugin-search-backend': patch
|
||||
'@backstage/plugin-events-node': patch
|
||||
---
|
||||
|
||||
Updated generated schemas
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-msgraph': patch
|
||||
---
|
||||
|
||||
Ensure that msgraph parent group stays same in case the group has multiple parents
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-backend': minor
|
||||
---
|
||||
|
||||
Adding new entity that specifies an external entity in the techdocs-entity annotation and updates to documentation regarding TechDocs redirects.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Catalog table columns support i18n
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"mode": "exit",
|
||||
"tag": "next",
|
||||
"initialVersions": {
|
||||
"example-app": "0.2.112",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-github': patch
|
||||
---
|
||||
|
||||
Add block creations field in github branch protection scaffolder actions
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
---
|
||||
|
||||
Add support for specifying an entity `spec.type` in `catalog.rules` and `catalog.locations.rules` within the catalog configuration.
|
||||
|
||||
For example, this enables allowing all `Template` entities with the type `website`:
|
||||
|
||||
```diff
|
||||
catalog:
|
||||
rules:
|
||||
- allow:
|
||||
- Component
|
||||
- API
|
||||
- Resource
|
||||
- System
|
||||
- Domain
|
||||
- Location
|
||||
+ - allow:
|
||||
+ - kind: Template
|
||||
+ spec.type: website
|
||||
locations:
|
||||
- type: url
|
||||
pattern: https://github.com/org/*\/blob/master/*.yaml
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Removed the need to mock `window.matchMedia` in tests, falling back to default breakpoint values instead.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-github': minor
|
||||
---
|
||||
|
||||
Adding a new scaffolder action `github:issues:create` following the reference of `github:issues:label` with `dryRun` testing possibility
|
||||
|
||||
It can be used like this
|
||||
|
||||
```
|
||||
steps:
|
||||
- id: create-simple-issue
|
||||
name: Create Simple Issue
|
||||
action: github:issues:create
|
||||
input:
|
||||
repoUrl: ${{ parameters.repoUrl }}
|
||||
title: "[${{ parameters.projectName }}] Simple Bug Report"
|
||||
body: |
|
||||
## Bug Description
|
||||
This is a simple bug report created by the scaffolder template.
|
||||
|
||||
### Steps to Reproduce
|
||||
1. Run the application
|
||||
2. Navigate to the main page
|
||||
3. Click on the problematic button
|
||||
|
||||
### Expected Behavior
|
||||
The button should work correctly.
|
||||
|
||||
### Actual Behavior
|
||||
The button does not respond to clicks.
|
||||
output:
|
||||
links:
|
||||
- title: Simple Issue
|
||||
url: ${{ steps['create-simple-issue'].output.issueUrl }}
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-app-api': patch
|
||||
---
|
||||
|
||||
feat: support no en languages
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Fixes issue with Github credentials provider which fails to match organization name if using allowedInstallationOwners
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-addons-test-utils': minor
|
||||
---
|
||||
|
||||
Adding catalogApiRef to test-utils to support catalog API usage by TechDocs reader page.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Removed the deprecation warning when not passing an explicit type to `EntityCardBlueprint`. Omitting the type is now intended, allowing the layout to pick the default type instead, typically `content`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Prevent the MultiEntityPicker from removing options present in form state when new options are selected
|
||||
@@ -38,6 +38,7 @@ Bitrise
|
||||
Blackbox
|
||||
bool
|
||||
boolean
|
||||
breakpoint
|
||||
Brex
|
||||
broadcasted
|
||||
bugfixes
|
||||
@@ -146,6 +147,7 @@ Expedia
|
||||
facto
|
||||
failover
|
||||
Fargate
|
||||
faqs
|
||||
featureful
|
||||
Figma
|
||||
firehydrant
|
||||
@@ -460,6 +462,7 @@ subfolders
|
||||
subheader
|
||||
subheaders
|
||||
subkey
|
||||
subpage
|
||||
subpath
|
||||
subroutes
|
||||
substring
|
||||
|
||||
@@ -217,18 +217,18 @@ export const cellPropDefs: Record<string, PropDef> = {
|
||||
};
|
||||
|
||||
export const tablePaginationPropDefs: Record<string, PropDef> = {
|
||||
pageIndex: {
|
||||
offset: {
|
||||
type: 'number',
|
||||
description: 'The current page index.',
|
||||
description: 'The current offset (starting index) for pagination.',
|
||||
},
|
||||
pageSize: {
|
||||
type: 'number',
|
||||
description: 'The number of items per page.',
|
||||
},
|
||||
setPageIndex: {
|
||||
setOffset: {
|
||||
type: 'enum',
|
||||
values: ['(pageIndex: number) => void'],
|
||||
description: 'Handler that is called when the page index changes.',
|
||||
values: ['(offset: number) => void'],
|
||||
description: 'Handler that is called when the offset changes.',
|
||||
},
|
||||
setPageSize: {
|
||||
type: 'enum',
|
||||
@@ -277,10 +277,7 @@ export const tableUsageSnippet = `import { Cell, ..., TableHeader, TablePaginati
|
||||
</Table>
|
||||
<TablePagination />`;
|
||||
|
||||
export const tableBasicSnippet = `import { Table, TablePagination } from '@backstage/ui';
|
||||
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, Cell, CellProfile, TablePagination, useTable } from '@backstage/ui';
|
||||
|
||||
const data = [
|
||||
{
|
||||
@@ -293,10 +290,13 @@ const data = [
|
||||
// ... more data
|
||||
];
|
||||
|
||||
const newData = data4.slice(
|
||||
pageIndex * pageSize,
|
||||
(pageIndex + 1) * pageSize,
|
||||
);
|
||||
// Uncontrolled pagination (easiest)
|
||||
const { data: paginatedData, paginationProps } = useTable({
|
||||
data,
|
||||
pagination: {
|
||||
defaultPageSize: 5,
|
||||
},
|
||||
});
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
@@ -306,9 +306,9 @@ const newData = data4.slice(
|
||||
<Column>Albums</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{newData.map(item => (
|
||||
{paginatedData?.map(item => (
|
||||
<Row key={item.name}>
|
||||
<CellProfileBUI
|
||||
<CellProfile
|
||||
name={item.name}
|
||||
src={item.image}
|
||||
href={item.website}
|
||||
@@ -320,10 +320,4 @@ const newData = data4.slice(
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
pageIndex={pageIndex}
|
||||
pageSize={pageSize}
|
||||
rowCount={data4.length}
|
||||
setPageIndex={setPageIndex}
|
||||
setPageSize={setPageSize}
|
||||
/>`;
|
||||
<TablePagination {...paginationProps} />`;
|
||||
|
||||
@@ -53,7 +53,7 @@ Once the host build is complete, we are ready to build our image. The following
|
||||
`Dockerfile` is included when creating a new app with `@backstage/create-app`:
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-bookworm-slim
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
# Set Python interpreter for `node-gyp` to use
|
||||
ENV PYTHON=/usr/bin/python3
|
||||
@@ -178,7 +178,7 @@ the repo root:
|
||||
|
||||
```dockerfile
|
||||
# Stage 1 - Create yarn install skeleton layer
|
||||
FROM node:20-bookworm-slim AS packages
|
||||
FROM node:22-bookworm-slim AS packages
|
||||
|
||||
WORKDIR /app
|
||||
COPY backstage.json package.json yarn.lock ./
|
||||
@@ -193,7 +193,7 @@ COPY plugins plugins
|
||||
RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+
|
||||
|
||||
# Stage 2 - Install dependencies and build packages
|
||||
FROM node:20-bookworm-slim AS build
|
||||
FROM node:22-bookworm-slim AS build
|
||||
|
||||
# Set Python interpreter for `node-gyp` to use
|
||||
ENV PYTHON=/usr/bin/python3
|
||||
@@ -231,7 +231,7 @@ RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \
|
||||
&& tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle
|
||||
|
||||
# Stage 3 - Build the actual backend image and install production dependencies
|
||||
FROM node:20-bookworm-slim
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
# Set Python interpreter for `node-gyp` to use
|
||||
ENV PYTHON=/usr/bin/python3
|
||||
|
||||
@@ -126,7 +126,7 @@ metadata:
|
||||
|
||||
The value of this annotation informs of the path to this component's TechDocs within an external entity that owns the TechDocs.
|
||||
In conjunction with [backstage.io/techdocs-entity](#backstageiotechdocs-entity) this allows for deep linking into the TechDocs of
|
||||
another entity, not just linking to the root of another entities TechDocs.
|
||||
another entity, not just linking to the root of another entity's TechDocs.
|
||||
|
||||
### backstage.io/view-url, backstage.io/edit-url
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ This page answers frequently asked questions about [TechDocs](README.md).
|
||||
- [What static site generator is TechDocs using?](#what-static-site-generator-is-techdocs-using)
|
||||
- [What is the mkdocs-techdocs-core plugin?](#what-is-the-mkdocs-techdocs-core-plugin)
|
||||
- [Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc)?](#does-techdocs-support-file-formats-other-than-markdown-eg-rst-asciidoc-)
|
||||
- [What should be the value of `backstage.io/techdocs-ref` when using external build and storage?](#what-should-be-the-value-of-backstageiotechdocs-ref-when-using-external-build-and-storage)
|
||||
- [Is it possible for users to suggest changes or provide feedback on a TechDocs page?](#is-it-possible-for-users-to-suggest-changes-or-provide-feedback-on-a-techdocs-page)
|
||||
|
||||
#### What static site generator is TechDocs using?
|
||||
|
||||
@@ -46,6 +48,10 @@ annotation should still be present in entity descriptor file (e.g.
|
||||
`catalog-info.yaml`) for Backstage to know that TechDocs is enabled for the
|
||||
entity.
|
||||
|
||||
#### What happens when you navigate to a TechDocs URL for an entity uses the `backstage.io/techdocs-entity` annotation?
|
||||
|
||||
If you navigate to a TechDocs URL in the format `docs/{namespace}/{kind}/{name}` for an entity that has the `backstage.io/techdocs-entity` annotation (instead of the `backstage.io/techdocs-ref` annotation), then Backstage will redirect to the TechDocs page of the entity referenced in the value of that annotation.
|
||||
|
||||
#### Is it possible for users to suggest changes or provide feedback on a TechDocs page?
|
||||
|
||||
This is supported for TechDocs sites whose source code is hosted in either
|
||||
|
||||
@@ -185,7 +185,7 @@ export const examplePlugin = createFrontendPlugin({
|
||||
|
||||
## Plugin specific extensions
|
||||
|
||||
There are many different plugins that you can extend with additional functionality through extensions. One such plugin is [the catalog plugin](https://backstage.io/docs/features/software-catalog/), one of the core features of Backstage. It lets you catalog the software in your organization, where each item in the catalog has its own page that can be populated with tools and information relating to that catalog entity. In this example we will explore how our plugin can provide such a tool to display on an entity page.
|
||||
There are many different plugins that you can extend with additional functionality through extensions. One such plugin is [the catalog plugin](../../features/software-catalog/), one of the core features of Backstage. It lets you catalog the software in your organization, where each item in the catalog has its own page that can be populated with tools and information relating to that catalog entity. In this example we will explore how our plugin can provide such a tool to display on an entity page.
|
||||
|
||||
```tsx title="in src/plugin.ts - An example entity content extension"
|
||||
import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha';
|
||||
|
||||
@@ -184,7 +184,7 @@ To install custom rules in a plugin, we need to use the [`PermissionsRegistrySer
|
||||
import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
|
||||
);
|
||||
/* highlight-add-next-line */
|
||||
backend.add(import('./extensions/catalogPermissionRules'));
|
||||
backend.add(import('./modules/catalogPermissionRules'));
|
||||
```
|
||||
|
||||
5. Now when you run you Backstage instance - `yarn start` - the rule will be added to the catalog plugin.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
searchTerm: string;
|
||||
onSearchTermChange: (newTerm: string) => void;
|
||||
};
|
||||
|
||||
export const PluginsSearch = (props: Props) => {
|
||||
const { searchTerm, onSearchTermChange } = props;
|
||||
return (
|
||||
<input
|
||||
name="search"
|
||||
onInput={e => onSearchTermChange((e.target as HTMLInputElement).value)}
|
||||
value={searchTerm}
|
||||
placeholder="Search plugins..."
|
||||
className="DocSearch-Input search"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -5,10 +5,11 @@ import { truncateDescription } from '@site/src/util/truncateDescription';
|
||||
import { ChipCategory } from '@site/src/util/types';
|
||||
import Layout from '@theme/Layout';
|
||||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { IPluginData, PluginCard } from './_pluginCard';
|
||||
import pluginsStyles from './plugins.module.scss';
|
||||
import { PluginsSearch } from '@site/src/components/pluginsSearch/pluginsSearch';
|
||||
|
||||
interface IPluginsList {
|
||||
corePlugins: IPluginData[];
|
||||
@@ -55,6 +56,7 @@ const Plugins = () => {
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||
const [showCoreFeatures, setShowCoreFeatures] = useState(true);
|
||||
const [showOtherPlugins, setShowOtherPlugins] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const handleChipClick = (categoryName: string) => {
|
||||
const isSelected =
|
||||
@@ -97,6 +99,34 @@ const Plugins = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const matchesSearch = (pluginData: IPluginData, term: string) => {
|
||||
if (!term) return true;
|
||||
const lowerTerm = term.toLowerCase();
|
||||
return (
|
||||
pluginData.title.toLowerCase().includes(lowerTerm) ||
|
||||
pluginData.description.toLowerCase().includes(lowerTerm) ||
|
||||
pluginData.category.toLowerCase().includes(lowerTerm) ||
|
||||
(pluginData.author && pluginData.author.toLowerCase().includes(lowerTerm))
|
||||
);
|
||||
};
|
||||
|
||||
const matchesCategory = (pluginData: IPluginData, categories: string[]) => {
|
||||
if (categories.length === 0) return true;
|
||||
return categories.includes(pluginData.category);
|
||||
};
|
||||
|
||||
const corePlugins = useMemo(() => {
|
||||
return plugins.corePlugins
|
||||
.filter(pluginData => matchesCategory(pluginData, selectedCategories))
|
||||
.filter(pluginData => matchesSearch(pluginData, searchTerm));
|
||||
}, [selectedCategories, searchTerm]);
|
||||
|
||||
const otherPlugins = useMemo(() => {
|
||||
return plugins.otherPlugins
|
||||
.filter(pluginData => matchesCategory(pluginData, selectedCategories))
|
||||
.filter(pluginData => matchesSearch(pluginData, searchTerm));
|
||||
}, [selectedCategories, searchTerm]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div
|
||||
@@ -126,31 +156,36 @@ const Plugins = () => {
|
||||
categories={categories}
|
||||
handleChipClick={handleChipClick}
|
||||
/>
|
||||
<PluginsSearch
|
||||
searchTerm={searchTerm}
|
||||
onSearchTermChange={setSearchTerm}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showCoreFeatures && (
|
||||
{corePlugins.length === 0 && otherPlugins.length === 0 && (
|
||||
<div className="margin-vert--lg">
|
||||
<h3>No plugins found</h3>
|
||||
<p>
|
||||
We couldn't find any plugins matching your criteria. Please try
|
||||
adjusting your search or filter settings.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showCoreFeatures && corePlugins.length > 0 && (
|
||||
<div>
|
||||
<h2>Core Features</h2>
|
||||
<h2>Core Features ({corePlugins.length})</h2>
|
||||
<div className="pluginsContainer margin-bottom--lg">
|
||||
{plugins.corePlugins
|
||||
.filter(
|
||||
pluginData =>
|
||||
!selectedCategories.length ||
|
||||
selectedCategories.includes(pluginData.category),
|
||||
)
|
||||
.map(pluginData => (
|
||||
<PluginCard
|
||||
key={pluginData.title}
|
||||
{...pluginData}
|
||||
></PluginCard>
|
||||
))}
|
||||
{corePlugins.map(pluginData => (
|
||||
<PluginCard key={pluginData.title} {...pluginData}></PluginCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showOtherPlugins && (
|
||||
{showOtherPlugins && otherPlugins.length > 0 && (
|
||||
<div>
|
||||
<h2>All Plugins</h2>
|
||||
<h2>All Plugins ({otherPlugins.length})</h2>
|
||||
<p>
|
||||
Friendly reminder: While we love the variety and contributions of
|
||||
our open source plugins, they haven't been fully vetted by the
|
||||
@@ -158,18 +193,9 @@ const Plugins = () => {
|
||||
your due diligence before installing. Happy exploring!
|
||||
</p>
|
||||
<div className="pluginsContainer margin-bottom--lg">
|
||||
{plugins.otherPlugins
|
||||
.filter(
|
||||
pluginData =>
|
||||
!selectedCategories.length ||
|
||||
selectedCategories.includes(pluginData.category),
|
||||
)
|
||||
.map(pluginData => (
|
||||
<PluginCard
|
||||
key={pluginData.title}
|
||||
{...pluginData}
|
||||
></PluginCard>
|
||||
))}
|
||||
{otherPlugins.map(pluginData => (
|
||||
<PluginCard key={pluginData.title} {...pluginData}></PluginCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -76,6 +76,20 @@
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
:global(.search) {
|
||||
float: right;
|
||||
margin-bottom: 1rem;
|
||||
margin-right: 1rem;
|
||||
font-size: calc(0.875rem * var(--ifm-button-size-multiplier));
|
||||
font-weight: var(--ifm-button-font-weight);
|
||||
background-color: transparent;
|
||||
border: var(--ifm-button-border-width) solid var(--ifm-color-primary);
|
||||
border-radius: var(--ifm-button-border-radius);
|
||||
color: var(--ifm-color-primary);
|
||||
width: 220px;
|
||||
height: 2.2rem;
|
||||
}
|
||||
|
||||
:global(.dropdown) {
|
||||
float: right;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
+2
-2
@@ -25,9 +25,9 @@
|
||||
"scripts": {
|
||||
"build-storybook": "storybook build --output-dir dist-storybook",
|
||||
"build:all": "backstage-cli repo build --all",
|
||||
"build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'",
|
||||
"build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(api-docs|api-docs-module-protoc-gen-doc|app-visualizer|catalog-graph|catalog-import|catalog-unprocessed-entities|config-schema|example-todo-list|example-todo-list-backend)'",
|
||||
"build:api-reports": "yarn build:api-reports:only --tsc",
|
||||
"build:api-reports:only": "LANG=en_US.UTF-8 NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --sql-reports --allow-warnings 'packages/backend-app-api,packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-undocumented,ae-wrong-input-file-type --validate-release-tags",
|
||||
"build:api-reports:only": "LANG=en_US.UTF-8 NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --sql-reports --allow-warnings 'packages/backend-app-api,packages/core-components,plugins/+(catalog|catalog-import|kubernetes)' -o ae-undocumented,ae-wrong-input-file-type --validate-release-tags",
|
||||
"build:backend": "yarn workspace example-backend build",
|
||||
"build:knip-reports": "backstage-repo-tools knip-reports",
|
||||
"build:plugins-report": "node ./scripts/build-plugins-report",
|
||||
|
||||
@@ -236,115 +236,14 @@ export class DefaultApiClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entities matching a given filter.
|
||||
* @param fields - By default the full entities are returned, but you can pass in a `fields` query
|
||||
parameter which selects what parts of the entity data to retain. This makes the
|
||||
response smaller and faster to transfer, and may allow the catalog to perform
|
||||
more efficient queries.
|
||||
|
||||
The query parameter value is a comma separated list of simplified JSON paths
|
||||
like above. Each path corresponds to the key of either a value, or of a subtree
|
||||
root that you want to keep in the output. The rest is pruned away. For example,
|
||||
specifying `?fields=metadata.name,metadata.annotations,spec` retains only the
|
||||
`name` and `annotations` fields of the `metadata` of each entity (it'll be an
|
||||
object with at most two keys), keeps the entire `spec` unchanged, and cuts out
|
||||
all other roots such as `relations`.
|
||||
|
||||
Some more real world usable examples:
|
||||
|
||||
- Return only enough data to form the full ref of each entity:
|
||||
|
||||
`/entities/by-query?fields=kind,metadata.namespace,metadata.name`
|
||||
|
||||
* @param limit - Number of records to return in the response.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity.
|
||||
Each filter set is a number of conditions that all have to match for the
|
||||
condition to be true (conditions effectively have an AND between them). At least
|
||||
one filter set has to be true for the entity to be part of the result set
|
||||
(filter sets effectively have an OR between them).
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type
|
||||
|
||||
Return entities that match
|
||||
|
||||
Filter set 1:
|
||||
Condition 1: kind = user
|
||||
AND
|
||||
Condition 2: metadata.namespace = default
|
||||
|
||||
OR
|
||||
|
||||
Filter set 2:
|
||||
Condition 1: kind = group
|
||||
AND
|
||||
Condition 2: spec.type exists
|
||||
```
|
||||
|
||||
Each condition is either on the form `<key>`, or on the form `<key>=<value>`.
|
||||
The first form asserts on the existence of a certain key (with any value), and
|
||||
the second asserts that the key exists and has a certain value. All checks are
|
||||
always case _insensitive_.
|
||||
|
||||
In all cases, the key is a simplified JSON path in a given piece of entity data.
|
||||
Each part of the path is a key of an object, and the traversal also descends
|
||||
through arrays. There are two special forms:
|
||||
|
||||
- Array items that are simple value types (such as strings) match on a key-value
|
||||
pair where the key is the item as a string, and the value is the string `true`
|
||||
- Relations can be matched on a `relations.<type>=<targetRef>` form
|
||||
|
||||
Let's look at a simplified example to illustrate the concept:
|
||||
|
||||
```json
|
||||
{
|
||||
"a": {
|
||||
"b": ["c", { "d": 1 }],
|
||||
"e": 7
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would match any one of the following conditions:
|
||||
|
||||
- `a`
|
||||
- `a.b`
|
||||
- `a.b.c`
|
||||
- `a.b.c=true`
|
||||
- `a.b.d`
|
||||
- `a.b.d=1`
|
||||
- `a.e`
|
||||
- `a.e=7`
|
||||
|
||||
Some more real world usable examples:
|
||||
|
||||
- Return all orphaned entities:
|
||||
|
||||
`/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true`
|
||||
|
||||
- Return all users and groups:
|
||||
|
||||
`/entities/by-query?filter=kind=user&filter=kind=group`
|
||||
|
||||
- Return all service components:
|
||||
|
||||
`/entities/by-query?filter=kind=component,spec.type=service`
|
||||
|
||||
- Return all entities with the `java` tag:
|
||||
|
||||
`/entities/by-query?filter=metadata.tags.java`
|
||||
|
||||
- Return all users who are members of the `ops` group (note that the full
|
||||
[reference](references.md) of the group is used):
|
||||
|
||||
`/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
|
||||
* @param offset - Number of records to skip in the query page.
|
||||
* @param after - Pointer to the previous page of results.
|
||||
* @param order -
|
||||
*/
|
||||
* Get all entities matching a given filter.
|
||||
* @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it'll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
|
||||
* @param limit - Number of records to return in the response.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param offset - Number of records to skip in the query page.
|
||||
* @param after - Pointer to the previous page of results.
|
||||
* @param order -
|
||||
*/
|
||||
public async getEntities(
|
||||
// @ts-ignore
|
||||
request: GetEntities,
|
||||
@@ -368,148 +267,16 @@ Some more real world usable examples:
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for entities by a given query.
|
||||
* @param fields - By default the full entities are returned, but you can pass in a `fields` query
|
||||
parameter which selects what parts of the entity data to retain. This makes the
|
||||
response smaller and faster to transfer, and may allow the catalog to perform
|
||||
more efficient queries.
|
||||
|
||||
The query parameter value is a comma separated list of simplified JSON paths
|
||||
like above. Each path corresponds to the key of either a value, or of a subtree
|
||||
root that you want to keep in the output. The rest is pruned away. For example,
|
||||
specifying `?fields=metadata.name,metadata.annotations,spec` retains only the
|
||||
`name` and `annotations` fields of the `metadata` of each entity (it'll be an
|
||||
object with at most two keys), keeps the entire `spec` unchanged, and cuts out
|
||||
all other roots such as `relations`.
|
||||
|
||||
Some more real world usable examples:
|
||||
|
||||
- Return only enough data to form the full ref of each entity:
|
||||
|
||||
`/entities/by-query?fields=kind,metadata.namespace,metadata.name`
|
||||
|
||||
* @param limit - Number of records to return in the response.
|
||||
* @param offset - Number of records to skip in the query page.
|
||||
* @param orderField - By default the entities are returned ordered by their internal uid. You can
|
||||
customize the `orderField` query parameters to affect that ordering.
|
||||
|
||||
For example, to return entities by their name:
|
||||
|
||||
`/entities/by-query?orderField=metadata.name,asc`
|
||||
|
||||
Each parameter can be followed by `asc` for ascending lexicographical order or
|
||||
`desc` for descending (reverse) lexicographical order.
|
||||
|
||||
* @param cursor - You may pass the `cursor` query parameters to perform cursor based pagination
|
||||
through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property:
|
||||
|
||||
```json
|
||||
"pageInfo": {
|
||||
"nextCursor": "a-cursor",
|
||||
"prevCursor": "another-cursor"
|
||||
}
|
||||
```
|
||||
|
||||
If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach,
|
||||
if `prevCursor` exists, it can be used to retrieve the previous batch of entities.
|
||||
|
||||
- [`filter`](#filtering), for selecting only a subset of all entities
|
||||
- [`fields`](#field-selection), for selecting only parts of the full data
|
||||
structure of each entity
|
||||
- `limit` for limiting the number of entities returned (20 is the default)
|
||||
- [`orderField`](#ordering), for deciding the order of the entities
|
||||
- `fullTextFilter`
|
||||
**NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that,
|
||||
it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters,
|
||||
as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
|
||||
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity.
|
||||
Each filter set is a number of conditions that all have to match for the
|
||||
condition to be true (conditions effectively have an AND between them). At least
|
||||
one filter set has to be true for the entity to be part of the result set
|
||||
(filter sets effectively have an OR between them).
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type
|
||||
|
||||
Return entities that match
|
||||
|
||||
Filter set 1:
|
||||
Condition 1: kind = user
|
||||
AND
|
||||
Condition 2: metadata.namespace = default
|
||||
|
||||
OR
|
||||
|
||||
Filter set 2:
|
||||
Condition 1: kind = group
|
||||
AND
|
||||
Condition 2: spec.type exists
|
||||
```
|
||||
|
||||
Each condition is either on the form `<key>`, or on the form `<key>=<value>`.
|
||||
The first form asserts on the existence of a certain key (with any value), and
|
||||
the second asserts that the key exists and has a certain value. All checks are
|
||||
always case _insensitive_.
|
||||
|
||||
In all cases, the key is a simplified JSON path in a given piece of entity data.
|
||||
Each part of the path is a key of an object, and the traversal also descends
|
||||
through arrays. There are two special forms:
|
||||
|
||||
- Array items that are simple value types (such as strings) match on a key-value
|
||||
pair where the key is the item as a string, and the value is the string `true`
|
||||
- Relations can be matched on a `relations.<type>=<targetRef>` form
|
||||
|
||||
Let's look at a simplified example to illustrate the concept:
|
||||
|
||||
```json
|
||||
{
|
||||
"a": {
|
||||
"b": ["c", { "d": 1 }],
|
||||
"e": 7
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would match any one of the following conditions:
|
||||
|
||||
- `a`
|
||||
- `a.b`
|
||||
- `a.b.c`
|
||||
- `a.b.c=true`
|
||||
- `a.b.d`
|
||||
- `a.b.d=1`
|
||||
- `a.e`
|
||||
- `a.e=7`
|
||||
|
||||
Some more real world usable examples:
|
||||
|
||||
- Return all orphaned entities:
|
||||
|
||||
`/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true`
|
||||
|
||||
- Return all users and groups:
|
||||
|
||||
`/entities/by-query?filter=kind=user&filter=kind=group`
|
||||
|
||||
- Return all service components:
|
||||
|
||||
`/entities/by-query?filter=kind=component,spec.type=service`
|
||||
|
||||
- Return all entities with the `java` tag:
|
||||
|
||||
`/entities/by-query?filter=metadata.tags.java`
|
||||
|
||||
- Return all users who are members of the `ops` group (note that the full
|
||||
[reference](references.md) of the group is used):
|
||||
|
||||
`/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
|
||||
* @param fullTextFilterTerm - Text search term.
|
||||
* @param fullTextFilterFields - A comma separated list of fields to sort returned results by.
|
||||
*/
|
||||
* Search for entities by a given query.
|
||||
* @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it'll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name`
|
||||
* @param limit - Number of records to return in the response.
|
||||
* @param offset - Number of records to skip in the query page.
|
||||
* @param orderField - By default the entities are returned ordered by their internal uid. You can customize the `orderField` query parameters to affect that ordering. For example, to return entities by their name: `/entities/by-query?orderField=metadata.name,asc` Each parameter can be followed by `asc` for ascending lexicographical order or `desc` for descending (reverse) lexicographical order.
|
||||
* @param cursor - You may pass the `cursor` query parameters to perform cursor based pagination through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property: ```json \"pageInfo\": { \"nextCursor\": \"a-cursor\", \"prevCursor\": \"another-cursor\" } ``` If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach, if `prevCursor` exists, it can be used to retrieve the previous batch of entities. - [`filter`](#filtering), for selecting only a subset of all entities - [`fields`](#field-selection), for selecting only parts of the full data structure of each entity - `limit` for limiting the number of entities returned (20 is the default) - [`orderField`](#ordering), for deciding the order of the entities - `fullTextFilter` **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that, it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters, as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param fullTextFilterTerm - Text search term.
|
||||
* @param fullTextFilterFields - A comma separated list of fields to sort returned results by.
|
||||
*/
|
||||
public async getEntitiesByQuery(
|
||||
// @ts-ignore
|
||||
request: GetEntitiesByQuery,
|
||||
@@ -533,93 +300,10 @@ Some more real world usable examples:
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a batch set of entities given an array of entityRefs.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity.
|
||||
Each filter set is a number of conditions that all have to match for the
|
||||
condition to be true (conditions effectively have an AND between them). At least
|
||||
one filter set has to be true for the entity to be part of the result set
|
||||
(filter sets effectively have an OR between them).
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type
|
||||
|
||||
Return entities that match
|
||||
|
||||
Filter set 1:
|
||||
Condition 1: kind = user
|
||||
AND
|
||||
Condition 2: metadata.namespace = default
|
||||
|
||||
OR
|
||||
|
||||
Filter set 2:
|
||||
Condition 1: kind = group
|
||||
AND
|
||||
Condition 2: spec.type exists
|
||||
```
|
||||
|
||||
Each condition is either on the form `<key>`, or on the form `<key>=<value>`.
|
||||
The first form asserts on the existence of a certain key (with any value), and
|
||||
the second asserts that the key exists and has a certain value. All checks are
|
||||
always case _insensitive_.
|
||||
|
||||
In all cases, the key is a simplified JSON path in a given piece of entity data.
|
||||
Each part of the path is a key of an object, and the traversal also descends
|
||||
through arrays. There are two special forms:
|
||||
|
||||
- Array items that are simple value types (such as strings) match on a key-value
|
||||
pair where the key is the item as a string, and the value is the string `true`
|
||||
- Relations can be matched on a `relations.<type>=<targetRef>` form
|
||||
|
||||
Let's look at a simplified example to illustrate the concept:
|
||||
|
||||
```json
|
||||
{
|
||||
"a": {
|
||||
"b": ["c", { "d": 1 }],
|
||||
"e": 7
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would match any one of the following conditions:
|
||||
|
||||
- `a`
|
||||
- `a.b`
|
||||
- `a.b.c`
|
||||
- `a.b.c=true`
|
||||
- `a.b.d`
|
||||
- `a.b.d=1`
|
||||
- `a.e`
|
||||
- `a.e=7`
|
||||
|
||||
Some more real world usable examples:
|
||||
|
||||
- Return all orphaned entities:
|
||||
|
||||
`/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true`
|
||||
|
||||
- Return all users and groups:
|
||||
|
||||
`/entities/by-query?filter=kind=user&filter=kind=group`
|
||||
|
||||
- Return all service components:
|
||||
|
||||
`/entities/by-query?filter=kind=component,spec.type=service`
|
||||
|
||||
- Return all entities with the `java` tag:
|
||||
|
||||
`/entities/by-query?filter=metadata.tags.java`
|
||||
|
||||
- Return all users who are members of the `ops` group (note that the full
|
||||
[reference](references.md) of the group is used):
|
||||
|
||||
`/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
|
||||
* @param getEntitiesByRefsRequest -
|
||||
*/
|
||||
* Get a batch set of entities given an array of entityRefs.
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
* @param getEntitiesByRefsRequest -
|
||||
*/
|
||||
public async getEntitiesByRefs(
|
||||
// @ts-ignore
|
||||
request: GetEntitiesByRefs,
|
||||
@@ -730,93 +414,10 @@ Some more real world usable examples:
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entity facets that match the given filters.
|
||||
* @param facet -
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity.
|
||||
Each filter set is a number of conditions that all have to match for the
|
||||
condition to be true (conditions effectively have an AND between them). At least
|
||||
one filter set has to be true for the entity to be part of the result set
|
||||
(filter sets effectively have an OR between them).
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type
|
||||
|
||||
Return entities that match
|
||||
|
||||
Filter set 1:
|
||||
Condition 1: kind = user
|
||||
AND
|
||||
Condition 2: metadata.namespace = default
|
||||
|
||||
OR
|
||||
|
||||
Filter set 2:
|
||||
Condition 1: kind = group
|
||||
AND
|
||||
Condition 2: spec.type exists
|
||||
```
|
||||
|
||||
Each condition is either on the form `<key>`, or on the form `<key>=<value>`.
|
||||
The first form asserts on the existence of a certain key (with any value), and
|
||||
the second asserts that the key exists and has a certain value. All checks are
|
||||
always case _insensitive_.
|
||||
|
||||
In all cases, the key is a simplified JSON path in a given piece of entity data.
|
||||
Each part of the path is a key of an object, and the traversal also descends
|
||||
through arrays. There are two special forms:
|
||||
|
||||
- Array items that are simple value types (such as strings) match on a key-value
|
||||
pair where the key is the item as a string, and the value is the string `true`
|
||||
- Relations can be matched on a `relations.<type>=<targetRef>` form
|
||||
|
||||
Let's look at a simplified example to illustrate the concept:
|
||||
|
||||
```json
|
||||
{
|
||||
"a": {
|
||||
"b": ["c", { "d": 1 }],
|
||||
"e": 7
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This would match any one of the following conditions:
|
||||
|
||||
- `a`
|
||||
- `a.b`
|
||||
- `a.b.c`
|
||||
- `a.b.c=true`
|
||||
- `a.b.d`
|
||||
- `a.b.d=1`
|
||||
- `a.e`
|
||||
- `a.e=7`
|
||||
|
||||
Some more real world usable examples:
|
||||
|
||||
- Return all orphaned entities:
|
||||
|
||||
`/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true`
|
||||
|
||||
- Return all users and groups:
|
||||
|
||||
`/entities/by-query?filter=kind=user&filter=kind=group`
|
||||
|
||||
- Return all service components:
|
||||
|
||||
`/entities/by-query?filter=kind=component,spec.type=service`
|
||||
|
||||
- Return all entities with the `java` tag:
|
||||
|
||||
`/entities/by-query?filter=metadata.tags.java`
|
||||
|
||||
- Return all users who are members of the `ops` group (note that the full
|
||||
[reference](references.md) of the group is used):
|
||||
|
||||
`/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
|
||||
*/
|
||||
* Get all entity facets that match the given filters.
|
||||
* @param facet -
|
||||
* @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
|
||||
*/
|
||||
public async getEntityFacets(
|
||||
// @ts-ignore
|
||||
request: GetEntityFacets,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -430,10 +430,10 @@ export async function createConfig(
|
||||
path: paths.targetDist,
|
||||
publicPath:
|
||||
options.moduleFederation?.mode === 'remote' ? 'auto' : `${publicPath}/`,
|
||||
filename: isDev ? '[name].js' : 'static/[name].[fullhash:8].js',
|
||||
filename: isDev ? '[name].js' : 'static/[name].[contenthash:8].js',
|
||||
chunkFilename: isDev
|
||||
? '[name].chunk.js'
|
||||
: 'static/[name].[chunkhash:8].chunk.js',
|
||||
: 'static/[name].[contenthash:8].chunk.js',
|
||||
...(isDev
|
||||
? {
|
||||
devtoolModuleFilenameTemplate: (info: any) =>
|
||||
|
||||
@@ -72,7 +72,7 @@ export const optimization = (
|
||||
},
|
||||
filename: isDev
|
||||
? 'module-[name].js'
|
||||
: 'static/module-[name].[chunkhash:8].js',
|
||||
: 'static/module-[name].[contenthash:8].js',
|
||||
priority: 10,
|
||||
minSize: 100000,
|
||||
minChunks: 1,
|
||||
|
||||
@@ -772,6 +772,7 @@ describe('bump', () => {
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
releaseVersion: '1.0.0',
|
||||
packages: [],
|
||||
}),
|
||||
),
|
||||
@@ -797,7 +798,7 @@ describe('bump', () => {
|
||||
'bumping @backstage-extra/custom in b to ^1.1.0',
|
||||
'bumping @backstage-extra/custom-two in b to ^2.0.0',
|
||||
'bumping @backstage/theme in b to ^2.0.0',
|
||||
'Skipping backstage.json update as custom pattern is used',
|
||||
'Your project is now at version 1.0.0, which has been written to backstage.json',
|
||||
'Running yarn install to install new versions',
|
||||
'Checking for moved packages to the @backstage-community namespace...',
|
||||
'⚠️ The following packages may have breaking changes:',
|
||||
|
||||
@@ -18,6 +18,7 @@ maybeBootstrapProxy();
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import { minimatch } from 'minimatch';
|
||||
import semver from 'semver';
|
||||
import { OptionValues } from 'commander';
|
||||
import yaml from 'yaml';
|
||||
@@ -78,6 +79,14 @@ type PkgVersionInfo = {
|
||||
location: string;
|
||||
};
|
||||
|
||||
function extendsDefaultPattern(pattern: string): boolean {
|
||||
if (!pattern.endsWith('/*')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return minimatch('@backstage/', pattern.slice(0, -1));
|
||||
}
|
||||
|
||||
export default async (opts: OptionValues) => {
|
||||
const lockfilePath = paths.resolveTargetRoot('yarn.lock');
|
||||
const lockfile = await Lockfile.load(lockfilePath);
|
||||
@@ -245,8 +254,8 @@ export default async (opts: OptionValues) => {
|
||||
|
||||
console.log();
|
||||
|
||||
// Do not update backstage.json when upgrade patterns are used.
|
||||
if (pattern === DEFAULT_PATTERN_GLOB) {
|
||||
// Do not update backstage.json when default pattern is not covered
|
||||
if (extendsDefaultPattern(pattern)) {
|
||||
await bumpBackstageJsonVersion(
|
||||
releaseManifest.releaseVersion,
|
||||
hasYarnPlugin,
|
||||
|
||||
+16
-1
@@ -144,11 +144,26 @@ describe('AppLanguageSelector', () => {
|
||||
AppLanguageSelector.createWithStorage({
|
||||
availableLanguages: ['de'],
|
||||
}),
|
||||
).toThrow("Supported languages must include 'en'");
|
||||
).toThrow(
|
||||
"Initial language must be one of the supported languages, got 'en'",
|
||||
);
|
||||
|
||||
const selector = AppLanguageSelector.createWithStorage(baseOptions);
|
||||
expect(() => selector.setLanguage('sv')).toThrow(
|
||||
"Failed to change language to 'sv', available languages are 'en', 'de'",
|
||||
);
|
||||
});
|
||||
|
||||
it('should support no en languages', () => {
|
||||
const selector = AppLanguageSelector.createWithStorage({
|
||||
availableLanguages: ['de'],
|
||||
defaultLanguage: 'de',
|
||||
});
|
||||
|
||||
expect(selector.getLanguage()).toEqual({ language: 'de' });
|
||||
|
||||
expect(() => selector.setLanguage('en')).toThrow(
|
||||
"Failed to change language to 'en', available languages are 'de'",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+1
-3
@@ -44,11 +44,9 @@ export class AppLanguageSelector implements AppLanguageApi {
|
||||
)}'`,
|
||||
);
|
||||
}
|
||||
if (!languages.includes(DEFAULT_LANGUAGE)) {
|
||||
throw new Error(`Supported languages must include '${DEFAULT_LANGUAGE}'`);
|
||||
}
|
||||
|
||||
const initialLanguage = options?.defaultLanguage ?? DEFAULT_LANGUAGE;
|
||||
|
||||
if (!languages.includes(initialLanguage)) {
|
||||
throw new Error(
|
||||
`Initial language must be one of the supported languages, got '${initialLanguage}'`,
|
||||
|
||||
@@ -16,28 +16,3 @@
|
||||
// package: the name of the package, e.g. @testing-library/react
|
||||
// query: the version query to pin the version for, e.g. ^14.0.0
|
||||
// version: the version to pin to, must be in range of the query, e.g. 14.11.0
|
||||
|
||||
"@google-cloud/storage@^7.0.0":
|
||||
version "7.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-7.14.0.tgz#eda9715f68507949214af804c906eba6d168a214"
|
||||
integrity sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==
|
||||
|
||||
"@octokit/types@npm:^13.0.0":
|
||||
version "13.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd"
|
||||
integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==
|
||||
|
||||
"@octokit/types@npm:^13.1.0":
|
||||
version "13.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd"
|
||||
integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==
|
||||
|
||||
"@octokit/types@npm:^13.5.0":
|
||||
version "13.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd"
|
||||
integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA==
|
||||
|
||||
"@octokit/openapi-types@^22.2.0":
|
||||
version "22.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.2.0.tgz#75aa7dcd440821d99def6a60b5f014207ae4968e"
|
||||
integrity sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# Alternatively, there is also a multi-stage Dockerfile documented here:
|
||||
# https://backstage.io/docs/deployment/docker#multi-stage-build
|
||||
|
||||
FROM node:20-bookworm-slim
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
# Set Python interpreter for `node-gyp` to use
|
||||
ENV PYTHON=/usr/bin/python3
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# Alternatively, there is also a multi-stage Dockerfile documented here:
|
||||
# https://backstage.io/docs/deployment/docker#multi-stage-build
|
||||
|
||||
FROM node:20-bookworm-slim
|
||||
FROM node:22-bookworm-slim
|
||||
|
||||
# Set Python interpreter for `node-gyp` to use
|
||||
ENV PYTHON=/usr/bin/python3
|
||||
|
||||
@@ -231,6 +231,42 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => {
|
||||
expect(token).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should not fail to issue tokens for an organization when there is a case mismatch in the organization name', async () => {
|
||||
octokit.apps.listInstallations.mockResolvedValue({
|
||||
headers: {
|
||||
etag: '123',
|
||||
},
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
repository_selection: 'selected',
|
||||
account: {
|
||||
login: 'backstage',
|
||||
},
|
||||
},
|
||||
],
|
||||
} as RestEndpointMethodTypes['apps']['listInstallations']['response']);
|
||||
|
||||
octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({
|
||||
data: {
|
||||
expires_at: DateTime.local().plus({ hours: 1 }).toString(),
|
||||
token: 'secret_token',
|
||||
repository_selection: 'selected',
|
||||
},
|
||||
} as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']);
|
||||
|
||||
octokit.apps.listReposAccessibleToInstallation.mockReturnValue({
|
||||
data: [{ name: 'some-repo' }],
|
||||
} as unknown as RestEndpointMethodTypes['apps']['listReposAccessibleToInstallation']['response']);
|
||||
|
||||
const { token, headers } = await github.getCredentials({
|
||||
url: 'https://github.com/Backstage',
|
||||
});
|
||||
const expectedToken = 'secret_token';
|
||||
expect(headers).toEqual({ Authorization: `Bearer ${expectedToken}` });
|
||||
expect(token).toEqual('secret_token');
|
||||
});
|
||||
|
||||
it('should not fail to issue tokens for an organization when the app is installed for a single repo', async () => {
|
||||
octokit.apps.listInstallations.mockResolvedValue({
|
||||
headers: {
|
||||
|
||||
@@ -102,7 +102,9 @@ class GithubAppManager {
|
||||
private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations
|
||||
|
||||
constructor(config: GithubAppConfig, baseUrl?: string) {
|
||||
this.allowedInstallationOwners = config.allowedInstallationOwners;
|
||||
this.allowedInstallationOwners = config.allowedInstallationOwners?.map(
|
||||
owner => owner.toLocaleLowerCase('en-US'),
|
||||
);
|
||||
this.baseUrl = baseUrl;
|
||||
this.baseAuthConfig = {
|
||||
appId: config.appId,
|
||||
@@ -121,7 +123,11 @@ class GithubAppManager {
|
||||
repo?: string,
|
||||
): Promise<{ accessToken: string | undefined }> {
|
||||
if (this.allowedInstallationOwners) {
|
||||
if (!this.allowedInstallationOwners?.includes(owner)) {
|
||||
if (
|
||||
!this.allowedInstallationOwners?.includes(
|
||||
owner.toLocaleLowerCase('en-US'),
|
||||
)
|
||||
) {
|
||||
return { accessToken: undefined }; // An empty token allows anonymous access to public repos
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ export const DisabledWithIcon: Story = {
|
||||
...WithIcon.args,
|
||||
isDisabled: true,
|
||||
},
|
||||
render: WithIcon.render,
|
||||
};
|
||||
|
||||
export const ShowError: Story = {
|
||||
|
||||
@@ -22,7 +22,8 @@ type UseMediaQueryOptions = {
|
||||
initializeWithValue?: boolean;
|
||||
};
|
||||
|
||||
const IS_SERVER = typeof window === 'undefined';
|
||||
const IS_SERVER =
|
||||
typeof window === 'undefined' || typeof window.matchMedia === 'undefined';
|
||||
|
||||
export function useMediaQuery(
|
||||
query: string,
|
||||
@@ -51,6 +52,9 @@ export function useMediaQuery(
|
||||
}
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (IS_SERVER) {
|
||||
return;
|
||||
}
|
||||
const matchMedia = window.matchMedia(query);
|
||||
|
||||
// Triggered at the first client-side load and if query changes
|
||||
|
||||
@@ -59,6 +59,7 @@ function setupFakeServer(
|
||||
page: number;
|
||||
include_subgroups: boolean;
|
||||
archived: boolean;
|
||||
simple?: boolean;
|
||||
}) => {
|
||||
data: GitLabProject[];
|
||||
nextPage?: number;
|
||||
@@ -78,10 +79,12 @@ function setupFakeServer(
|
||||
const page = req.url.searchParams.get('page');
|
||||
const include_subgroups = req.url.searchParams.get('include_subgroups');
|
||||
const archived = req.url.searchParams.get('archived');
|
||||
const simple = req.url.searchParams.get('simple');
|
||||
const response = listProjectsCallback({
|
||||
page: parseInt(page!, 10),
|
||||
include_subgroups: include_subgroups === 'true',
|
||||
archived: archived === 'true',
|
||||
simple: simple === 'true',
|
||||
});
|
||||
|
||||
// Filter the fake results based on the `last_activity_after` parameter
|
||||
@@ -471,6 +474,110 @@ describe('GitlabDiscoveryProcessor', () => {
|
||||
});
|
||||
expect(result2).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('sets simple=true when skipForkedRepos is false', async () => {
|
||||
const processor = getProcessor({
|
||||
options: { skipForkedRepos: false },
|
||||
});
|
||||
setupFakeServer(
|
||||
PROJECTS_URL,
|
||||
_ => {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
archived: false,
|
||||
default_branch: 'main',
|
||||
last_activity_at: '2021-08-05T11:03:05.774Z',
|
||||
web_url: 'https://gitlab.fake/1',
|
||||
path_with_namespace: '1',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
request => {
|
||||
// Verify that simple=true is set in the request
|
||||
expect(request.url.searchParams.get('simple')).toBe('true');
|
||||
},
|
||||
);
|
||||
|
||||
const result: any[] = [];
|
||||
await processor.readLocation(PROJECT_LOCATION, false, e => {
|
||||
result.push(e);
|
||||
});
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not set simple when skipForkedRepos is true', async () => {
|
||||
const processor = getProcessor({
|
||||
options: { skipForkedRepos: true },
|
||||
});
|
||||
setupFakeServer(
|
||||
PROJECTS_URL,
|
||||
_ => {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
archived: false,
|
||||
default_branch: 'main',
|
||||
last_activity_at: '2021-08-05T11:03:05.774Z',
|
||||
web_url: 'https://gitlab.fake/1',
|
||||
path_with_namespace: '1',
|
||||
// Include forked_from_project to test fork filtering
|
||||
forked_from_project: {
|
||||
id: 100,
|
||||
name: 'original-project',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
request => {
|
||||
// Verify that simple parameter is not set
|
||||
expect(request.url.searchParams.get('simple')).toBeNull();
|
||||
},
|
||||
);
|
||||
|
||||
const result: any[] = [];
|
||||
await processor.readLocation(PROJECT_LOCATION, false, e => {
|
||||
result.push(e);
|
||||
});
|
||||
// Should be empty because forked repo is skipped
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('sets default parameters correctly (archived=false, simple=true)', async () => {
|
||||
const processor = getProcessor(); // Uses defaults: skipForkedRepos=false, includeArchivedRepos=false
|
||||
setupFakeServer(
|
||||
PROJECTS_URL,
|
||||
_ => {
|
||||
return {
|
||||
data: [
|
||||
{
|
||||
id: 1,
|
||||
archived: false,
|
||||
default_branch: 'main',
|
||||
last_activity_at: '2021-08-05T11:03:05.774Z',
|
||||
web_url: 'https://gitlab.fake/1',
|
||||
path_with_namespace: '1',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
request => {
|
||||
// Verify default parameters: archived=false and simple=true
|
||||
expect(request.url.searchParams.get('archived')).toBe('false');
|
||||
expect(request.url.searchParams.get('simple')).toBe('true');
|
||||
},
|
||||
);
|
||||
|
||||
const result: any[] = [];
|
||||
await processor.readLocation(PROJECT_LOCATION, false, e => {
|
||||
result.push(e);
|
||||
});
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handles failure', () => {
|
||||
|
||||
@@ -115,6 +115,11 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor {
|
||||
// that the options doesn't include the key so that the API doesn't receive an empty query parameter.
|
||||
...(lastActivity && { last_activity_after: lastActivity }),
|
||||
...(!this.includeArchivedRepos && { archived: false }),
|
||||
// Only use simple=true when we don't need to skip forked repos.
|
||||
// The simple=true parameter reduces response size by returning fewer fields,
|
||||
// but it excludes the 'forked_from_project' field which is required for fork detection.
|
||||
// Therefore, we can only optimize with simple=true when skipForkedRepos is false.
|
||||
...(!this.skipForkedRepos && { simple: true }),
|
||||
};
|
||||
|
||||
const projects = paginated(options => client.listProjects(options), opts);
|
||||
|
||||
@@ -187,6 +187,55 @@ describe('GitLabClient', () => {
|
||||
|
||||
expect(allProjects).toHaveLength(mock.all_projects_response.length);
|
||||
});
|
||||
|
||||
it('should pass simple parameter to API when provided', async () => {
|
||||
const client = new GitLabClient({
|
||||
config: readGitLabIntegrationConfig(
|
||||
new ConfigReader(mock.config_self_managed),
|
||||
),
|
||||
logger: mockServices.logger.mock(),
|
||||
});
|
||||
|
||||
// Mock the pagedRequest method to verify parameters
|
||||
const mockPagedRequest = jest.fn().mockResolvedValue({
|
||||
items: [],
|
||||
nextPage: undefined,
|
||||
});
|
||||
(client as any).pagedRequest = mockPagedRequest;
|
||||
|
||||
await client.listProjects({ simple: true });
|
||||
|
||||
expect(mockPagedRequest).toHaveBeenCalledWith('/projects', {
|
||||
simple: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass simple parameter to group projects API when provided', async () => {
|
||||
const client = new GitLabClient({
|
||||
config: readGitLabIntegrationConfig(
|
||||
new ConfigReader(mock.config_self_managed),
|
||||
),
|
||||
logger: mockServices.logger.mock(),
|
||||
});
|
||||
|
||||
// Mock the pagedRequest method to verify parameters
|
||||
const mockPagedRequest = jest.fn().mockResolvedValue({
|
||||
items: [],
|
||||
nextPage: undefined,
|
||||
});
|
||||
(client as any).pagedRequest = mockPagedRequest;
|
||||
|
||||
await client.listProjects({ group: 'test-group', simple: true });
|
||||
|
||||
expect(mockPagedRequest).toHaveBeenCalledWith(
|
||||
'/groups/test-group/projects',
|
||||
{
|
||||
group: 'test-group',
|
||||
simple: true,
|
||||
include_subgroups: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listUsers', () => {
|
||||
|
||||
@@ -43,6 +43,7 @@ interface ListProjectOptions extends CommonListOptions {
|
||||
group?: string;
|
||||
membership?: boolean;
|
||||
topics?: string;
|
||||
simple?: boolean;
|
||||
}
|
||||
|
||||
interface UserListOptions extends CommonListOptions {
|
||||
|
||||
+127
@@ -147,6 +147,21 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => {
|
||||
'GitlabDiscoveryEntityProvider:test-id',
|
||||
);
|
||||
|
||||
// Mock the GitLabClient listProjects method to verify default parameters
|
||||
const originalListProjects = (provider as any).gitLabClient.listProjects;
|
||||
const mockListProjects = jest.fn().mockImplementation(async options => {
|
||||
// Verify default parameters: archived=false and simple=true (since skipForkedRepos=false by default)
|
||||
expect(options).toMatchObject({
|
||||
group: 'group1',
|
||||
per_page: 50,
|
||||
archived: false,
|
||||
simple: true, // Should be set since skipForkedRepos defaults to false
|
||||
});
|
||||
// Call the original method to maintain test behavior
|
||||
return originalListProjects.call((provider as any).gitLabClient, options);
|
||||
});
|
||||
(provider as any).gitLabClient.listProjects = mockListProjects;
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
@@ -675,3 +690,115 @@ describe('GitlabDiscoveryEntityProvider - events', () => {
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GitlabDiscoveryEntityProvider - simple parameter', () => {
|
||||
it('should pass simple=true when skipForkedRepos is false', async () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'example.com',
|
||||
apiBaseUrl: 'https://example.com/api/v4',
|
||||
token: 'test-token',
|
||||
},
|
||||
],
|
||||
},
|
||||
catalog: {
|
||||
providers: {
|
||||
gitlab: {
|
||||
'test-id': {
|
||||
host: 'example.com',
|
||||
group: 'test-group',
|
||||
skipForkedRepos: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
|
||||
const provider = GitlabDiscoveryEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
|
||||
// Mock the GitLabClient listProjects method to verify parameters
|
||||
const mockListProjects = jest.fn().mockResolvedValue({
|
||||
items: [],
|
||||
nextPage: undefined,
|
||||
});
|
||||
|
||||
(provider as any).gitLabClient.listProjects = mockListProjects;
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
await provider.refresh(logger);
|
||||
|
||||
expect(mockListProjects).toHaveBeenCalledWith({
|
||||
group: 'test-group',
|
||||
page: undefined,
|
||||
per_page: 50,
|
||||
archived: false,
|
||||
simple: true, // Should be set when skipForkedRepos is false
|
||||
});
|
||||
});
|
||||
|
||||
it('should not pass simple when skipForkedRepos is true', async () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'example.com',
|
||||
apiBaseUrl: 'https://example.com/api/v4',
|
||||
token: 'test-token',
|
||||
},
|
||||
],
|
||||
},
|
||||
catalog: {
|
||||
providers: {
|
||||
gitlab: {
|
||||
'test-id': {
|
||||
host: 'example.com',
|
||||
group: 'test-group',
|
||||
skipForkedRepos: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const schedule = new PersistingTaskRunner();
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
|
||||
const provider = GitlabDiscoveryEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
|
||||
// Mock the GitLabClient listProjects method to verify parameters
|
||||
const mockListProjects = jest.fn().mockResolvedValue({
|
||||
items: [],
|
||||
nextPage: undefined,
|
||||
});
|
||||
|
||||
(provider as any).gitLabClient.listProjects = mockListProjects;
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
await provider.refresh(logger);
|
||||
|
||||
expect(mockListProjects).toHaveBeenCalledWith({
|
||||
group: 'test-group',
|
||||
page: undefined,
|
||||
per_page: 50,
|
||||
archived: false,
|
||||
// simple should not be present when skipForkedRepos is true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -326,6 +326,11 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider {
|
||||
...(!this.config.includeArchivedRepos && { archived: false }),
|
||||
...(this.config.membership && { membership: true }),
|
||||
...(this.config.topics && { topics: this.config.topics }),
|
||||
// Only use simple=true when we don't need to skip forked repos.
|
||||
// The simple=true parameter reduces response size by returning fewer fields,
|
||||
// but it excludes the 'forked_from_project' field which is required for fork detection.
|
||||
// Therefore, we can only optimize with simple=true when skipForkedRepos is false.
|
||||
...(!this.config.skipForkedRepos && { simple: true }),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1102,6 +1102,14 @@ describe('read microsoft graph', () => {
|
||||
name: 'c',
|
||||
},
|
||||
});
|
||||
const groupD = group({
|
||||
metadata: {
|
||||
annotations: {
|
||||
'graph.microsoft.com/group-id': 'group-id-d',
|
||||
},
|
||||
name: 'd',
|
||||
},
|
||||
});
|
||||
const user1 = user({
|
||||
metadata: {
|
||||
annotations: {
|
||||
@@ -1118,16 +1126,17 @@ describe('read microsoft graph', () => {
|
||||
name: 'user2',
|
||||
},
|
||||
});
|
||||
const groups = [rootGroup, groupA, groupB, groupC];
|
||||
const groups = [rootGroup, groupA, groupB, groupC, groupD];
|
||||
const users = [user1, user2];
|
||||
const groupMember = new Map<string, Set<string>>();
|
||||
groupMember.set('group-id-b', new Set(['group-id-c']));
|
||||
groupMember.set('group-id-d', new Set(['group-id-c']));
|
||||
const groupMemberOf = new Map<string, Set<string>>();
|
||||
groupMemberOf.set('user-id-1', new Set(['group-id-a']));
|
||||
groupMemberOf.set('user-id-2', new Set(['group-id-c']));
|
||||
|
||||
// We have a root groups
|
||||
// We have three groups: a, b, c. c is child of b
|
||||
// We have three groups: a, b, c. c is child of b and d, b should be picked as it's first
|
||||
// we have two users: u1, u2. u1 is member of a, u2 is member of c
|
||||
resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf);
|
||||
|
||||
@@ -1147,6 +1156,11 @@ describe('read microsoft graph', () => {
|
||||
expect(groupC.spec.parent).toEqual('group:default/b');
|
||||
expect(groupC.spec.children).toEqual(expect.arrayContaining([]));
|
||||
|
||||
expect(groupD.spec.parent).toEqual('group:default/root');
|
||||
expect(groupD.spec.children).toEqual(
|
||||
expect.arrayContaining(['group:default/c']),
|
||||
);
|
||||
|
||||
expect(user1.spec.memberOf).toEqual(
|
||||
expect.arrayContaining(['group:default/a']),
|
||||
);
|
||||
|
||||
@@ -359,7 +359,14 @@ export function resolveRelations(
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Until we have better support for multiple parents in the model,
|
||||
// the order of the parents is important as changing it causes
|
||||
// unnecessary entity stitching randomly.
|
||||
retrieveItems(parentGroups, id).forEach(p => {
|
||||
// Only set the parent if it doesn't exist yet
|
||||
if (group.spec.parent) {
|
||||
return;
|
||||
}
|
||||
const parentGroup = groupMap.get(p);
|
||||
if (parentGroup) {
|
||||
// TODO: Only having a single parent group might not match every companies model, but fine for now.
|
||||
@@ -546,5 +553,5 @@ function retrieveItems(
|
||||
target: Map<string, Set<string>>,
|
||||
key: string,
|
||||
): Set<string> {
|
||||
return target.get(key) ?? new Set();
|
||||
return new Set([...(target.get(key) ?? [])].sort());
|
||||
}
|
||||
|
||||
Vendored
+4
-1
@@ -38,8 +38,11 @@ export interface Config {
|
||||
* Allow entities of these particular kinds.
|
||||
*
|
||||
* E.g. ["Component", "API", "Template", "Location"]
|
||||
*
|
||||
* You can also specify the type of the entity by using an object with `kind` and optional `spec.type` properties.
|
||||
* E.g. [{ kind: "Component", 'spec.type': "service" }]
|
||||
*/
|
||||
allow: Array<string>;
|
||||
allow: Array<string | { kind: string; 'spec.type'?: string }>;
|
||||
/**
|
||||
* Limit this rule to a specific location
|
||||
*
|
||||
|
||||
@@ -28,6 +28,11 @@ export const createGetCatalogEntityAction = ({
|
||||
actionsRegistry.register({
|
||||
name: 'get-catalog-entity',
|
||||
title: 'Get Catalog Entity',
|
||||
attributes: {
|
||||
destructive: false,
|
||||
readOnly: true,
|
||||
idempotent: true,
|
||||
},
|
||||
description: `
|
||||
This allows you to get a single entity from the software catalog.
|
||||
Each entity in the software catalog has a unique name, kind, and namespace. The default namespace is "default".
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user