Merge branch 'master' into rugvip/moreports

This commit is contained in:
Patrik Oldsberg
2021-02-04 19:43:32 +01:00
committed by GitHub
305 changed files with 4106 additions and 1758 deletions
+31
View File
@@ -1,5 +1,36 @@
# @backstage/plugin-api-docs
## 0.4.4
### Patch Changes
- 7fc89bae2: Display owner and system as entity page links in the tables of the `api-docs`
plugin.
Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to
`@backstage/plugin-catalog-react` and export it from there to use it by other
plugins.
- bc5082a00: Migrate to new composability API, exporting the plugin as `apiDocsPlugin`, index page as `ApiExplorerPage`, and entity page cards as `EntityApiDefinitionCard`, `EntityConsumedApisCard`, `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`.
- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/plugin-catalog-react@0.0.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.4.3
### Patch Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
"version": "0.4.3",
"version": "0.4.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,10 +30,10 @@
},
"dependencies": {
"@asyncapi/react-component": "^0.18.2",
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/plugin-catalog-react": "^0.0.1",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/theme": "^0.2.3",
"@material-icons/font": "^1.0.2",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -49,8 +49,8 @@
"swagger-ui-react": "^3.37.2"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
@@ -75,8 +75,8 @@ paths:
type: 'openapi',
title: 'OpenAPI',
rawLanguage: 'yaml',
component: definition => (
<OpenApiDefinitionWidget definition={definition} />
component: definitionString => (
<OpenApiDefinitionWidget definition={definitionString} />
),
});
+11
View File
@@ -1,5 +1,16 @@
# @backstage/plugin-app-backend
## 0.3.6
### Patch Changes
- e9aab60c7: Failures to load the frontend configuration schema now throws an error that includes more context and instructions for how to fix the issue.
- Updated dependencies [2430ee7c2]
- Updated dependencies [062df71db]
- Updated dependencies [e9aab60c7]
- @backstage/backend-common@0.5.2
- @backstage/config-loader@0.5.1
## 0.3.5
### Patch Changes
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-backend",
"version": "0.3.5",
"version": "0.3.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,8 +29,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.5.1",
"@backstage/config-loader": "^0.5.0",
"@backstage/backend-common": "^0.5.2",
"@backstage/config-loader": "^0.5.1",
"@backstage/config": "^0.1.2",
"@types/express": "^4.17.6",
"express": "^4.17.1",
@@ -40,7 +40,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/cli": "^0.6.0",
"@types/supertest": "^2.0.8",
"msw": "^0.20.5",
"supertest": "^4.0.2"
+3 -3
View File
@@ -29,9 +29,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.5.1",
"@backstage/backend-common": "^0.5.2",
"@backstage/catalog-client": "^0.3.5",
"@backstage/catalog-model": "^0.7.0",
"@backstage/catalog-model": "^0.7.1",
"@backstage/config": "^0.1.2",
"@types/express": "^4.17.6",
"compression": "^1.7.4",
@@ -65,7 +65,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/cli": "^0.6.0",
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/express-session": "^1.17.2",
+24
View File
@@ -1,5 +1,29 @@
# @backstage/plugin-catalog-backend
## 0.6.0
### Minor Changes
- 3149bfe63: Make use of the `resolveUrl` facility of the `integration` package.
Also rename the `LocationRefProcessor` to `LocationEntityProcessor`, to match the file name. This constitutes an interface change since the class is exported, but it is unlikely to be consumed outside of the package since it sits comfortably with the other default processors inside the catalog builder.
### Patch Changes
- 24e47ef1e: Throw `NotAllowedError` when registering locations with entities of disallowed kinds
- Updated dependencies [c4abcdb60]
- Updated dependencies [2430ee7c2]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [064c513e1]
- Updated dependencies [7881f2117]
- Updated dependencies [3149bfe63]
- Updated dependencies [2e62aea6f]
- Updated dependencies [11cb5ef94]
- @backstage/integration@0.3.2
- @backstage/backend-common@0.5.2
- @backstage/catalog-model@0.7.1
## 0.5.5
### Patch Changes
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
"version": "0.5.5",
"version": "0.6.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,9 +31,10 @@
"dependencies": {
"@aws-sdk/client-organizations": "^3.2.0",
"@azure/msal-node": "^1.0.0-beta.3",
"@backstage/backend-common": "^0.5.1",
"@backstage/catalog-model": "^0.7.0",
"@backstage/backend-common": "^0.5.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/config": "^0.1.2",
"@backstage/integration": "^0.3.2",
"@octokit/graphql": "^4.5.8",
"@types/express": "^4.17.6",
"@types/ldapjs": "^1.0.9",
@@ -57,7 +58,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/cli": "^0.6.0",
"@backstage/test-utils": "^0.1.6",
"@types/core-js": "^2.5.4",
"@types/git-url-parse": "^9.0.0",
@@ -124,7 +124,7 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog {
// produce tens of thousands of entities, and those are too large batch
// sizes to reasonably send to the database.
const batches = Object.values(requestsByKindAndNamespace)
.map(requests => chunk(requests, BATCH_SIZE))
.map(request => chunk(request, BATCH_SIZE))
.flat();
// Bound the number of concurrent batches. We want a bit of concurrency for
@@ -65,7 +65,10 @@ describe('AwsOrganizationCloudAccountProcessor', () => {
});
it('filters out accounts not in specified location target', async () => {
const location = { type: 'aws-cloud-accounts', target: 'o-1vl18kc5a3' };
const locationTest = {
type: 'aws-cloud-accounts',
target: 'o-1vl18kc5a3',
};
listAccounts.mockImplementation(() =>
Promise.resolve({
Accounts: [
@@ -83,11 +86,11 @@ describe('AwsOrganizationCloudAccountProcessor', () => {
NextToken: undefined,
}),
);
await processor.readLocation(location, false, emit);
await processor.readLocation(locationTest, false, emit);
expect(emit).toBeCalledTimes(1);
expect(emit).toBeCalledWith({
type: 'entity',
location,
location: locationTest,
entity: {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Resource',
@@ -66,8 +66,8 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor {
async validateEntityKind(entity: Entity): Promise<boolean> {
for (const validator of this.validators) {
const result = await validator.check(entity);
if (result) {
const results = await validator.check(entity);
if (results) {
return true;
}
}
@@ -15,30 +15,73 @@
*/
import { LocationSpec } from '@backstage/catalog-model';
import { toAbsoluteUrl } from './LocationEntityProcessor';
import { ConfigReader } from '@backstage/config';
import {
ScmIntegrations,
ScmIntegrationRegistry,
} from '@backstage/integration';
import path from 'path';
import { toAbsoluteUrl } from './LocationEntityProcessor';
describe('LocationEntityProcessor', () => {
describe('toAbsoluteUrl', () => {
it('handles files', () => {
const integrations = ({} as unknown) as ScmIntegrationRegistry;
const base: LocationSpec = {
type: 'file',
target: `some${path.sep}path${path.sep}catalog-info.yaml`,
};
expect(toAbsoluteUrl(base, `.${path.sep}c`)).toBe(
expect(toAbsoluteUrl(integrations, base, `.${path.sep}c`)).toBe(
`some${path.sep}path${path.sep}c`,
);
expect(toAbsoluteUrl(base, `${path.sep}c`)).toBe(`${path.sep}c`);
expect(toAbsoluteUrl(integrations, base, `${path.sep}c`)).toBe(
`${path.sep}c`,
);
});
it('handles urls', () => {
const integrations = ScmIntegrations.fromConfig(new ConfigReader({}));
const base: LocationSpec = {
type: 'url',
target: 'http://a.com/b/catalog-info.yaml',
};
expect(toAbsoluteUrl(base, './c/d')).toBe('http://a.com/b/c/d');
expect(toAbsoluteUrl(base, 'c/d')).toBe('http://a.com/b/c/d');
expect(toAbsoluteUrl(base, 'http://b.com/z')).toBe('http://b.com/z');
jest.spyOn(integrations, 'resolveUrl');
expect(toAbsoluteUrl(integrations, base, './c/d')).toBe(
'http://a.com/b/c/d',
);
expect(toAbsoluteUrl(integrations, base, 'c/d')).toBe(
'http://a.com/b/c/d',
);
expect(toAbsoluteUrl(integrations, base, 'http://b.com/z')).toBe(
'http://b.com/z',
);
expect(integrations.resolveUrl).toBeCalledTimes(3);
});
it('handles azure urls specifically', () => {
const integrations = ScmIntegrations.fromConfig(
new ConfigReader({
integrations: {
azure: [{ host: 'dev.azure.com' }],
},
}),
);
expect(
toAbsoluteUrl(
integrations,
{
type: 'url',
target:
'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
},
'./a.yaml',
),
).toBe(
'https://dev.azure.com/organization/project/_git/repository?path=%2Fa.yaml',
);
});
});
});
@@ -15,11 +15,16 @@
*/
import { Entity, LocationEntity, LocationSpec } from '@backstage/catalog-model';
import { ScmIntegrationRegistry } from '@backstage/integration';
import path from 'path';
import * as result from './results';
import { CatalogProcessor, CatalogProcessorEmit } from './types';
import path from 'path';
export function toAbsoluteUrl(base: LocationSpec, target: string): string {
export function toAbsoluteUrl(
integrations: ScmIntegrationRegistry,
base: LocationSpec,
target: string,
): string {
try {
if (base.type === 'file') {
if (target.startsWith('.')) {
@@ -27,13 +32,19 @@ export function toAbsoluteUrl(base: LocationSpec, target: string): string {
}
return target;
}
return new URL(target, base.target).toString();
return integrations.resolveUrl({ url: target, base: base.target });
} catch (e) {
return target;
}
}
export class LocationRefProcessor implements CatalogProcessor {
type Options = {
integrations: ScmIntegrationRegistry;
};
export class LocationEntityProcessor implements CatalogProcessor {
constructor(private readonly options: Options) {}
async postProcessEntity(
entity: Entity,
location: LocationSpec,
@@ -47,7 +58,7 @@ export class LocationRefProcessor implements CatalogProcessor {
emit(
result.inputError(
location,
`LocationRefProcessor cannot handle ${type} type location with target ${location.target} that ends with a path separator`,
`LocationEntityProcessor cannot handle ${type} type location with target ${location.target} that ends with a path separator`,
),
);
}
@@ -61,7 +72,11 @@ export class LocationRefProcessor implements CatalogProcessor {
}
for (const maybeRelativeTarget of targets) {
const target = toAbsoluteUrl(location, maybeRelativeTarget);
const target = toAbsoluteUrl(
this.options.integrations,
location,
maybeRelativeTarget,
);
emit(result.location({ type, target }, false));
}
}
@@ -23,7 +23,7 @@ export { CodeOwnersProcessor } from './CodeOwnersProcessor';
export { FileReaderProcessor } from './FileReaderProcessor';
export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor';
export { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor';
export { LocationRefProcessor } from './LocationEntityProcessor';
export { LocationEntityProcessor } from './LocationEntityProcessor';
export { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor';
export { PlaceholderProcessor } from './PlaceholderProcessor';
export type { PlaceholderResolver } from './PlaceholderProcessor';
@@ -26,6 +26,7 @@ import {
Validators,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import lodash from 'lodash';
import { Logger } from 'winston';
import {
@@ -46,8 +47,8 @@ import {
HigherOrderOperation,
HigherOrderOperations,
LdapOrgReaderProcessor,
LocationEntityProcessor,
LocationReaders,
LocationRefProcessor,
MicrosoftGraphOrgReaderProcessor,
PlaceholderProcessor,
PlaceholderResolver,
@@ -280,6 +281,7 @@ export class CatalogBuilder {
private buildProcessors(): CatalogProcessor[] {
const { config, logger, reader } = this.env;
const integrations = ScmIntegrations.fromConfig(config);
this.checkDeprecatedReaderProcessors();
@@ -306,7 +308,7 @@ export class CatalogBuilder {
MicrosoftGraphOrgReaderProcessor.fromConfig(config, { logger }),
new UrlReaderProcessor({ reader, logger }),
new CodeOwnersProcessor({ reader, logger }),
new LocationRefProcessor(),
new LocationEntityProcessor({ integrations }),
new AnnotateLocationEntityProcessor(),
);
}
+3 -3
View File
@@ -30,8 +30,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.5.1",
"@backstage/catalog-model": "^0.7.0",
"@backstage/backend-common": "^0.5.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/config": "^0.1.2",
"@graphql-modules/core": "^0.7.17",
"apollo-server": "^2.16.1",
@@ -42,7 +42,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/cli": "^0.6.0",
"@backstage/test-utils": "^0.1.5",
"@graphql-codegen/cli": "^1.17.7",
"@graphql-codegen/typescript": "^1.17.7",
+30
View File
@@ -1,5 +1,35 @@
# @backstage/plugin-catalog-import
## 0.3.7
### Patch Changes
- ceef4dd89: Export _api_ (Client, API, ref) from the catalog import plugin.
- b712841d6: Migrated to new composability API, exporting the plugin instance as `catalogImportPlugin`, and the page as `CatalogImportPage`.
- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
- Updated dependencies [12ece98cd]
- Updated dependencies [c4abcdb60]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [064c513e1]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [3149bfe63]
- Updated dependencies [54c7d02f7]
- Updated dependencies [2e62aea6f]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/integration@0.3.2
- @backstage/plugin-catalog-react@0.0.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.3.6
### Patch Changes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-import",
"version": "0.3.6",
"version": "0.3.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,11 +30,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/integration": "^0.3.1",
"@backstage/plugin-catalog-react": "^0.0.1",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/integration": "^0.3.2",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -50,8 +50,8 @@
},
"devDependencies": {
"@backstage/catalog-client": "^0.3.5",
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
@@ -71,8 +71,8 @@ export const RegisterComponentForm = ({
const onSubmit = async (formData: Record<string, string>) => {
const { componentLocation: target } = formData;
async function saveCatalogFileConfig(target: string) {
const data = await catalogApi.addLocation({ target });
async function saveCatalogFileConfig(targetString: string) {
const data = await catalogApi.addLocation({ target: targetString });
saveConfig({
type: 'file',
location: data.location.target,
@@ -80,12 +80,12 @@ export const RegisterComponentForm = ({
});
}
async function trySaveRepositoryConfig(target: string) {
const existingCatalog = await checkForExistingCatalogInfo(target);
async function trySaveRepositoryConfig(targetString: string) {
const existingCatalog = await checkForExistingCatalogInfo(targetString);
if (existingCatalog.exists) {
const targetUrl = target.endsWith('/')
? `${target}${existingCatalog.url}`
: `${target}/${existingCatalog.url}`;
const targetUrl = targetString.endsWith('/')
? `${targetString}${existingCatalog.url}`
: `${targetString}/${existingCatalog.url}`;
await saveCatalogFileConfig(targetUrl);
} else {
saveConfig({
@@ -46,20 +46,20 @@ function manifestGenerationAvailable(configApi: ConfigApi): boolean {
function repositories(configApi: ConfigApi): string[] {
const integrations = configApi.getConfig('integrations');
const repositories = [];
const repos = [];
if (integrations.has('github')) {
repositories.push('GitHub');
repos.push('GitHub');
}
if (integrations.has('bitbucket')) {
repositories.push('Bitbucket');
repos.push('Bitbucket');
}
if (integrations.has('gitlab')) {
repositories.push('GitLab');
repos.push('GitLab');
}
if (integrations.has('azure')) {
repositories.push('Azure');
repos.push('Azure');
}
return repositories;
return repos;
}
export const ImportComponentPage = ({
+26
View File
@@ -0,0 +1,26 @@
# @backstage/plugin-catalog-react
## 0.0.2
### Patch Changes
- 7fc89bae2: Display owner and system as entity page links in the tables of the `api-docs`
plugin.
Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to
`@backstage/plugin-catalog-react` and export it from there to use it by other
plugins.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/catalog-model@0.7.1
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-react",
"version": "0.0.1",
"version": "0.0.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,8 +29,8 @@
},
"dependencies": {
"@backstage/catalog-client": "^0.3.5",
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@material-ui/core": "^4.11.0",
"@types/react": "^16.9",
"react": "^16.13.1",
@@ -39,8 +39,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+4 -4
View File
@@ -34,13 +34,13 @@ export function isOwnerOf(owner: Entity, owned: Entity) {
const owners = getEntityRelations(owned, RELATION_OWNED_BY);
for (const owner of owners) {
for (const ownerItem of owners) {
if (
possibleOwners.find(
o =>
owner.kind.toLowerCase() === o.kind.toLowerCase() &&
owner.namespace.toLowerCase() === o.namespace.toLowerCase() &&
owner.name.toLowerCase() === o.name.toLowerCase(),
ownerItem.kind.toLowerCase() === o.kind.toLowerCase() &&
ownerItem.namespace.toLowerCase() === o.namespace.toLowerCase() &&
ownerItem.name.toLowerCase() === o.name.toLowerCase(),
) !== undefined
) {
return true;
+50
View File
@@ -1,5 +1,55 @@
# @backstage/plugin-catalog
## 0.3.0
### Minor Changes
- 019fe39a0: `@backstage/plugin-catalog` stopped exporting hooks and helpers for other
plugins. They are migrated to `@backstage/plugin-catalog-react`.
Change both your dependencies and imports to the new package.
### Patch Changes
- 7fc89bae2: Display owner and system as entity page links in the tables of the `api-docs`
plugin.
Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to
`@backstage/plugin-catalog-react` and export it from there to use it by other
plugins.
- b37501a3d: Add `children` option to `addPage`, which will be rendered as the children of the `Route`.
- b37501a3d: Finalize migration to new composability API, with the plugin instance now exported `catalogPlugin`.
- 54c7d02f7: Introduce `TabbedLayout` for creating tabs that are routed.
```typescript
<TabbedLayout>
<TabbedLayout.Route path="/example" title="Example tab">
<div>This is rendered under /example/anything-here route</div>
</TabbedLayout.Route>
</TabbedLayout>
```
- Updated dependencies [720149854]
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [019fe39a0]
- Updated dependencies [11cb5ef94]
- @backstage/plugin-scaffolder@0.4.2
- @backstage/core@0.6.0
- @backstage/plugin-catalog-react@0.0.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.2.14
### Patch Changes
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
"version": "0.2.14",
"version": "0.3.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,11 +31,11 @@
},
"dependencies": {
"@backstage/catalog-client": "^0.3.5",
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/plugin-catalog-react": "^0.0.1",
"@backstage/plugin-scaffolder": "^0.4.1",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/plugin-scaffolder": "^0.4.2",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -52,8 +52,8 @@
"swr": "^0.3.0"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@microsoft/microsoft-graph-types": "^1.25.0",
"@testing-library/jest-dom": "^5.10.1",
@@ -22,7 +22,7 @@ import {
ApiRegistry,
} from '@backstage/core';
import { catalogApiRef, EntityContext } from '@backstage/plugin-catalog-react';
import { renderInTestApp, withLogCollector } from '@backstage/test-utils';
import { renderInTestApp } from '@backstage/test-utils';
import { fireEvent } from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';
@@ -59,34 +59,11 @@ describe('EntityLayout', () => {
</ApiProvider>,
);
expect(rendered.getByText('my-entity')).toBeInTheDocument();
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
});
it('throws if any other component is a child of TabbedLayout', async () => {
const { error } = await withLogCollector(async () => {
await expect(
renderInTestApp(
<EntityLayout>
<EntityLayout.Route path="/" title="tabbed-test-title">
<div>tabbed-test-content</div>
</EntityLayout.Route>
<div>This will cause app to throw</div>
</EntityLayout>,
),
).rejects.toThrow(/Child of EntityLayout must be an EntityLayout.Route/);
});
expect(error).toEqual([
expect.stringMatching(
/Child of EntityLayout must be an EntityLayout.Route/,
),
expect.stringMatching(
/The above error occurred in the <EntityLayout> component/,
),
]);
});
it('navigates when user clicks different tab', async () => {
const rendered = await renderInTestApp(
<Routes>
@@ -16,12 +16,12 @@
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import {
attachComponentData,
Content,
Header,
HeaderLabel,
Page,
Progress,
TabbedLayout,
} from '@backstage/core';
import {
EntityContext,
@@ -29,12 +29,9 @@ import {
} from '@backstage/plugin-catalog-react';
import { Box } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import React, {
Children,
Fragment,
isValidElement,
import {
default as React,
PropsWithChildren,
ReactNode,
useContext,
useState,
} from 'react';
@@ -42,37 +39,6 @@ import { useNavigate } from 'react-router';
import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu';
import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity';
import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog';
import { TabbedLayout } from './TabbedLayout';
type SubRoute = {
path: string;
title: string;
children: JSX.Element;
};
const Route: (props: SubRoute) => null = () => null;
// This causes all mount points that are discovered within this route to use the path of the route itself
attachComponentData(Route, 'core.gatherMountPoints', true);
export function createSubRoutesFromChildren(children: ReactNode): SubRoute[] {
return Children.toArray(children).flatMap(child => {
if (!isValidElement(child)) {
return [];
}
if (child.type === Fragment) {
return createSubRoutesFromChildren(child.props.children);
}
if (child.type !== Route) {
throw new Error('Child of EntityLayout must be an EntityLayout.Route');
}
const { path, title, children } = child.props;
return [{ path, title, children }];
});
}
const EntityLayoutTitle = ({
entity,
@@ -132,7 +98,6 @@ export const EntityLayout = ({ children }: PropsWithChildren<{}>) => {
const { kind, namespace, name } = useEntityCompoundName();
const { entity, loading, error } = useContext(EntityContext);
const routes = createSubRoutesFromChildren(children);
const { headerTitle, headerType } = headerProps(
kind,
namespace,
@@ -174,7 +139,7 @@ export const EntityLayout = ({ children }: PropsWithChildren<{}>) => {
{loading && <Progress />}
{entity && <TabbedLayout routes={routes} />}
{entity && <TabbedLayout>{children}</TabbedLayout>}
{error && (
<Content>
@@ -191,4 +156,4 @@ export const EntityLayout = ({ children }: PropsWithChildren<{}>) => {
);
};
EntityLayout.Route = Route;
EntityLayout.Route = TabbedLayout.Route;
@@ -1,150 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { TabbedLayout } from './TabbedLayout';
import { renderInTestApp } from '@backstage/test-utils';
import { fireEvent } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { Routes, Route } from 'react-router';
const testRoute1 = {
path: '',
title: 'tabbed-test-title',
children: <div>tabbed-test-content</div>,
};
const testRoute2 = {
title: 'tabbed-test-title-2',
path: '/some-other-path',
children: <div>tabbed-test-content-2</div>,
};
describe('TabbedLayout', () => {
it('renders simplest case', async () => {
const rendered = await renderInTestApp(
<TabbedLayout routes={[testRoute1]} />,
);
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
});
it('navigates when user clicks different tab', async () => {
const rendered = await renderInTestApp(
<Routes>
<Route
path="/*"
element={<TabbedLayout routes={[testRoute1, testRoute2]} />}
/>
</Routes>,
);
const secondTab = rendered.queryAllByRole('tab')[1];
act(() => {
fireEvent.click(secondTab);
});
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();
expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
});
describe('correctly delegates nested links', () => {
const renderRoute = (route: string) =>
renderInTestApp(
<Routes>
<Route
path="/*"
element={
<TabbedLayout
routes={[
testRoute1,
{
...testRoute2,
children: (
<div>
tabbed-test-content-2
<Routes>
<Route
path="/nested"
element={<div>tabbed-test-nested-content-2</div>}
/>
</Routes>
</div>
),
},
]}
/>
}
/>
</Routes>,
{ routeEntries: [route] },
);
it('works for nested content', async () => {
const rendered = await renderRoute('/some-other-path/nested');
expect(
rendered.queryByText('tabbed-test-content'),
).not.toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
expect(
rendered.queryByText('tabbed-test-nested-content-2'),
).toBeInTheDocument();
});
it('works for non-nested content', async () => {
const rendered = await renderRoute('/some-other-path/');
expect(
rendered.queryByText('tabbed-test-content'),
).not.toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
expect(
rendered.queryByText('tabbed-test-nested-content-2'),
).not.toBeInTheDocument();
});
});
it('shows only one tab contents at a time', async () => {
const rendered = await renderInTestApp(
<TabbedLayout routes={[testRoute1, testRoute2]} />,
{ routeEntries: ['/some-other-path'] },
);
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content')).not.toBeInTheDocument();
expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
expect(rendered.queryByText('tabbed-test-content-2')).toBeInTheDocument();
});
it('redirects to the top level when no route is matching the url', async () => {
const rendered = await renderInTestApp(
<TabbedLayout routes={[testRoute1, testRoute2]} />,
{ routeEntries: ['/non-existing-path'] },
);
expect(rendered.getByText('tabbed-test-title')).toBeInTheDocument();
expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument();
expect(rendered.getByText('tabbed-test-title-2')).toBeInTheDocument();
expect(
rendered.queryByText('tabbed-test-content-2'),
).not.toBeInTheDocument();
});
});
@@ -1,75 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useMemo } from 'react';
import { useParams, useNavigate, matchRoutes, useRoutes } from 'react-router';
import { HeaderTabs, Content as LayoutContent } from '@backstage/core';
import { Helmet } from 'react-helmet';
import { SubRoute } from './types';
export function useSelectedSubRoute(
subRoutes: SubRoute[],
): { index: number; route: SubRoute; element: JSX.Element } {
const params = useParams();
const routes = subRoutes.map(({ path, children }) => ({
caseSensitive: false,
path: `${path}/*`,
element: children,
}));
const element = useRoutes(routes) ?? subRoutes[0].children;
const [matchedRoute] = matchRoutes(routes, `/${params['*']}`) ?? [];
const foundIndex = matchedRoute
? subRoutes.findIndex(t => `${t.path}/*` === matchedRoute.route.path)
: 0;
return {
index: foundIndex === -1 ? 0 : foundIndex,
element,
route: subRoutes[foundIndex] ?? subRoutes[0],
};
}
export const TabbedLayout = ({ routes }: { routes: SubRoute[] }) => {
const navigate = useNavigate();
const { index, route, element } = useSelectedSubRoute(routes);
const headerTabs = useMemo(
() => routes.map(t => ({ id: t.path, label: t.title })),
[routes],
);
const onTabChange = (index: number) =>
// Remove trailing /*
// And remove leading / for relative navigation
// Note! route resolves relative to the position in the React tree,
// not relative to current location
navigate(routes[index].path.replace(/\/\*$/, '').replace(/^\//, ''));
return (
<>
<HeaderTabs
tabs={headerTabs}
selectedIndex={index}
onChange={onTabChange}
/>
<LayoutContent>
<Helmet title={route.title} />
{element}
</LayoutContent>
</>
);
};
@@ -35,8 +35,8 @@ type SwitchCase = {
children: JSX.Element;
};
function createSwitchCasesFromChildren(children: ReactNode): SwitchCase[] {
return Children.toArray(children).flatMap(child => {
function createSwitchCasesFromChildren(childrenNode: ReactNode): SwitchCase[] {
return Children.toArray(childrenNode).flatMap(child => {
if (!isValidElement(child)) {
return [];
}
+23
View File
@@ -1,5 +1,28 @@
# @backstage/plugin-circleci
## 0.2.7
### Patch Changes
- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/plugin-catalog-react@0.0.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.2.6
### Patch Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-circleci",
"version": "0.2.6",
"version": "0.2.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"postpack": "backstage-cli postpack"
},
"dependencies": {
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/plugin-catalog-react": "^0.0.1",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -50,8 +50,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+21
View File
@@ -1,5 +1,26 @@
# @backstage/plugin-cloudbuild
## 0.2.8
### Patch Changes
- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.2.7
### Patch Changes
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-cloudbuild",
"version": "0.2.7",
"version": "0.2.8",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,9 +30,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -46,8 +46,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+20
View File
@@ -1,5 +1,25 @@
# @backstage/plugin-cost-insights
## 0.8.0
### Minor Changes
- 19172f5a9: add alert hooks
### Patch Changes
- 4c6a6dddd: Fixed date calculations incorrectly converting to UTC in some cases. This should be a transparent change.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [54c7d02f7]
- @backstage/core@0.6.0
- @backstage/theme@0.2.3
## 0.7.0
### Minor Changes
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-cost-insights",
"version": "0.7.0",
"version": "0.8.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,8 +31,8 @@
},
"dependencies": {
"@backstage/config": "^0.1.2",
"@backstage/core": "^0.5.0",
"@backstage/theme": "^0.2.2",
"@backstage/core": "^0.6.0",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -55,8 +55,8 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
@@ -124,18 +124,18 @@ export const AlertInsights = ({
setSnackbarOpen(!!error);
}, [error]);
function onSnooze(alert: Alert) {
setAlert(alert);
function onSnooze(alertToSnooze: Alert) {
setAlert(alertToSnooze);
setStatus(AlertStatus.Snoozed);
}
function onAccept(alert: Alert) {
setAlert(alert);
function onAccept(alertToAccept: Alert) {
setAlert(alertToAccept);
setStatus(AlertStatus.Accepted);
}
function onDismiss(alert: Alert) {
setAlert(alert);
function onDismiss(alertToDismiss: Alert) {
setAlert(alertToDismiss);
setStatus(AlertStatus.Dismissed);
}
@@ -148,8 +148,8 @@ export const AlertInsights = ({
setStatus(null);
}
function onDialogFormSubmit(data: any) {
setData(data);
function onDialogFormSubmit(formData: any) {
setData(formData);
}
function onSummaryButtonClick() {
@@ -175,10 +175,10 @@ export const AlertInsights = ({
</Grid>
{isAlertInsightSectionDisplayed && (
<Grid item container direction="column" spacing={4}>
{active.map((alert, index) => (
{active.map((activeAlert, index) => (
<Grid item key={`alert-insights-section-${index}`}>
<AlertInsightsSection
alert={alert}
alert={activeAlert}
number={index + 1}
onSnooze={onSnooze}
onAccept={onAccept}
@@ -18,7 +18,7 @@ import React, { PropsWithChildren } from 'react';
import { Box, Typography } from '@material-ui/core';
import { useBarChartLabelStyles } from '../../utils/styles';
type BarChartLabel = {
type BarChartLabelProps = {
x: number;
y: number;
height: number;
@@ -33,7 +33,7 @@ export const BarChartLabel = ({
width,
details,
children,
}: PropsWithChildren<BarChartLabel>) => {
}: PropsWithChildren<BarChartLabelProps>) => {
const classes = useBarChartLabelStyles();
const translateX = width * -0.5;
@@ -18,7 +18,7 @@ import React from 'react';
import { ButtonBase } from '@material-ui/core';
import { useBarChartStepperStyles as useStyles } from '../../utils/styles';
export type BarChartSteps = {
export type BarChartStepsProps = {
steps: number;
activeStep: number;
onClick: (index: number) => void;
@@ -28,7 +28,7 @@ export const BarChartSteps = ({
steps,
activeStep,
onClick,
}: BarChartSteps) => {
}: BarChartStepsProps) => {
const classes = useStyles();
const handleOnClick = (index: number) => (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
@@ -82,12 +82,7 @@ export const ProductInsights = ({
);
useEffect(() => {
async function getAllProductInsights(
group: string,
project: Maybe<string>,
products: Product[],
lastCompleteBillingDate: string,
) {
async function getAllProductInsights() {
try {
dispatchLoadingProducts(true);
const responses = await Promise.allSettled(
@@ -101,10 +96,10 @@ export const ProductInsights = ({
),
).then(settledResponseOf);
const initialStates = initialStatesOf(products, responses).sort(
const updatedInitialStates = initialStatesOf(products, responses).sort(
totalAggregationSort,
);
setStates(initialStates);
setStates(updatedInitialStates);
} catch (e) {
setError(e);
} finally {
@@ -112,7 +107,7 @@ export const ProductInsights = ({
}
}
getAllProductInsights(group, project, products, lastCompleteBillingDate);
getAllProductInsights();
}, [
client,
group,
@@ -125,8 +120,8 @@ export const ProductInsights = ({
useEffect(
function handleOnLoaded() {
if (onceRef.current) {
const products = initialStates.map(state => state.product);
onLoaded(products);
const initialProducts = initialStates.map(state => state.product);
onLoaded(initialProducts);
} else {
onceRef.current = true;
}
@@ -76,13 +76,13 @@ describe('getPreviousPeriodTotalCost', () => {
change: changeOf(MockAggregatedDailyCosts),
trendline: trendlineOf(MockAggregatedDailyCosts),
};
const exclusiveEndDate = '2020-09-30';
const inclusiveEndDate = '2020-09-30';
expect(
getPreviousPeriodTotalCost(
mockGroupDailyCost.aggregation,
Duration.P30D,
exclusiveEndDate,
inclusiveEndDate,
),
).toEqual(100_000);
).toEqual(96_600);
});
});
+3 -7
View File
@@ -22,14 +22,13 @@ import {
GrowthType,
MetricData,
Duration,
DEFAULT_DATE_FORMAT,
DateAggregation,
} from '../types';
import dayjs, { OpUnitType } from 'dayjs';
import duration from 'dayjs/plugin/duration';
import durationPlugin from 'dayjs/plugin/duration';
import { inclusiveStartDateOf } from './duration';
dayjs.extend(duration);
dayjs.extend(durationPlugin);
// Used for displaying status colors
export function growthOf(ratio: number, amount?: number) {
@@ -73,10 +72,7 @@ export function getPreviousPeriodTotalCost(
inclusiveEndDate: string,
): number {
const dayjsDuration = dayjs.duration(duration);
const startDate = inclusiveStartDateOf(
duration,
dayjs(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT),
);
const startDate = inclusiveStartDateOf(duration, inclusiveEndDate);
// dayjs doesn't allow adding an ISO 8601 period to dates.
const [amount, type]: [number, OpUnitType] = dayjsDuration.days()
? [dayjsDuration.days(), 'day']
+6 -13
View File
@@ -24,23 +24,21 @@ export const DEFAULT_DURATION = Duration.P30D;
* Derive the start date of a given period, assuming two repeating intervals.
*
* @param duration see comment on Duration enum
* @param exclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate + 1 day
* @param inclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate
*/
export function inclusiveStartDateOf(
duration: Duration,
exclusiveEndDate: string,
inclusiveEndDate: string,
): string {
switch (duration) {
case Duration.P7D:
case Duration.P30D:
case Duration.P90D:
return moment(exclusiveEndDate)
.utc()
return moment(inclusiveEndDate)
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(exclusiveEndDate)
.utc()
return moment(inclusiveEndDate)
.startOf('quarter')
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
@@ -57,13 +55,9 @@ export function exclusiveEndDateOf(
case Duration.P7D:
case Duration.P30D:
case Duration.P90D:
return moment(inclusiveEndDate)
.utc()
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
return moment(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(quarterEndDate(inclusiveEndDate))
.utc()
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
default:
@@ -76,7 +70,6 @@ export function inclusiveEndDateOf(
inclusiveEndDate: string,
): string {
return moment(exclusiveEndDateOf(duration, inclusiveEndDate))
.utc()
.subtract(1, 'day')
.format(DEFAULT_DATE_FORMAT);
}
@@ -94,7 +87,7 @@ export function intervalsOf(
}
export function quarterEndDate(inclusiveEndDate: string): string {
const endDate = moment(inclusiveEndDate).utc();
const endDate = moment(inclusiveEndDate);
const endOfQuarter = endDate.endOf('quarter').format(DEFAULT_DATE_FORMAT);
if (endOfQuarter === inclusiveEndDate) {
return endDate.format(DEFAULT_DATE_FORMAT);
@@ -16,7 +16,7 @@
import moment from 'moment';
import pluralize from 'pluralize';
import { Duration, DEFAULT_DATE_FORMAT } from '../types';
import { Duration } from '../types';
import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration';
export type Period = {
@@ -92,11 +92,8 @@ export function formatPercent(n: number): string {
}
export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string) {
const exclusiveEndDate = moment(inclusiveEndDate)
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
const start = moment(
inclusiveStartDateOf(Duration.P3M, exclusiveEndDate),
inclusiveStartDateOf(Duration.P3M, inclusiveEndDate),
).format('[Q]Q YYYY');
const end = moment(inclusiveEndDateOf(Duration.P3M, inclusiveEndDate)).format(
'[Q]Q YYYY',
+4 -3
View File
@@ -36,7 +36,7 @@ import {
getDefaultState as getDefaultLoadingState,
} from '../utils/loading';
import { findAlways } from '../utils/assert';
import { inclusiveStartDateOf } from './duration';
import { inclusiveEndDateOf, inclusiveStartDateOf } from './duration';
type mockAlertRenderer<T> = (alert: T) => T;
type mockEntityRenderer<T> = (entity: T) => T;
@@ -228,8 +228,9 @@ export function aggregationFor(
baseline: number,
): DateAggregation[] {
const { duration, endDate } = parseIntervals(intervals);
const inclusiveEndDate = inclusiveEndDateOf(duration, endDate);
const days = dayjs(endDate).diff(
inclusiveStartDateOf(duration, endDate),
inclusiveStartDateOf(duration, inclusiveEndDate),
'day',
);
@@ -244,7 +245,7 @@ export function aggregationFor(
return [...Array(days).keys()].reduce(
(values: DateAggregation[], i: number): DateAggregation[] => {
const last = values.length ? values[values.length - 1].amount : baseline;
const date = dayjs(inclusiveStartDateOf(duration, endDate))
const date = dayjs(inclusiveStartDateOf(duration, inclusiveEndDate))
.add(i, 'day')
.format(DEFAULT_DATE_FORMAT);
const amount = Math.max(0, last + nextDelta());
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+14
View File
@@ -0,0 +1,14 @@
# @backstage/plugin-explore-react
## 0.0.2
### Patch Changes
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [54c7d02f7]
- @backstage/core@0.6.0
+4
View File
@@ -0,0 +1,4 @@
# explore-react
This package provides helpers to the `explore` plugin that can be imported by
any other plugin or app.
+48
View File
@@ -0,0 +1,48 @@
{
"name": "@backstage/plugin-explore-react",
"version": "0.0.2",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/explore-react"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"clean": "backstage-cli clean",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack"
},
"dependencies": {
"@backstage/core": "^0.6.0"
},
"devDependencies": {
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"cross-fetch": "^3.0.6",
"msw": "^0.21.2"
},
"files": [
"dist"
]
}
@@ -14,8 +14,4 @@
* limitations under the License.
*/
export type SubRoute = {
path: string;
title: string;
children: JSX.Element;
};
export * from './tools';
@@ -14,13 +14,4 @@
* limitations under the License.
*/
import React from 'react';
import { Route, Routes } from 'react-router';
import { ExplorePluginPage } from './ExplorePluginPage';
import { rootRouteRef } from '../plugin';
export const Router = () => (
<Routes>
<Route path={`/${rootRouteRef.path}`} element={<ExplorePluginPage />} />
</Routes>
);
import '@testing-library/jest-dom';
@@ -0,0 +1,23 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { exploreToolsConfigRef } from './api';
describe('api', () => {
// This is a dummy test to have at least one test in the package.
it('api ref exists', () => {
expect(exploreToolsConfigRef.id).toBe('plugin.explore.toolsconfig');
});
});
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createApiRef } from '@backstage/core';
export const exploreToolsConfigRef = createApiRef<ExploreToolsConfig>({
id: 'plugin.explore.toolsconfig',
description: 'Used to configure tools displayed in the explore plugin',
});
export type ExploreTool = {
title: string;
description?: string;
url: string;
image: string;
tags?: string[];
lifecycle?: string;
newsTag?: string;
};
export interface ExploreToolsConfig {
getTools: () => Promise<ExploreTool[]>;
}
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { exploreToolsConfigRef } from './api';
export type { ExploreTool, ExploreToolsConfig } from './api';
+35
View File
@@ -1,5 +1,40 @@
# @backstage/plugin-explore
## 0.2.4
### Patch Changes
- 54c7d02f7: Introduce `TabbedLayout` for creating tabs that are routed.
```typescript
<TabbedLayout>
<TabbedLayout.Route path="/example" title="Example tab">
<div>This is rendered under /example/anything-here route</div>
</TabbedLayout.Route>
</TabbedLayout>
```
- 806929fe2: Rework the explore plugin to allow the user to explore things in the ecosystem,
including tools and domains.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/plugin-catalog-react@0.0.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
- @backstage/plugin-explore-react@0.0.2
## 0.2.3
### Patch Changes
+28 -3
View File
@@ -1,7 +1,32 @@
# Title
# explore
Welcome to the explore plugin!
This plugin helps to visualize the domains and tools in your ecosystem.
## Sub-section 1
## Getting started
## Sub-section 2
To install the plugin, include the following import your `plugins.ts`:
```typescript
export { explorePlugin } from '@backstage/plugin-explore';
```
Register the route in `App.tsx`:
```typescript
import { ExplorePage } from '@backstage/plugin-explore';
...
<Route path="/explore" element={<ExplorePage />} />
```
Add a link to the sidebar in `Root.tsx`:
```typescript
import LayersIcon from '@material-ui/icons/Layers';
...
<SidebarItem icon={LayersIcon} to="explore" text="Explore" />
```
+54 -2
View File
@@ -14,7 +14,59 @@
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { createDevApp } from '@backstage/dev-utils';
import { plugin } from '../src/plugin';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { exploreToolsConfigRef } from '@backstage/plugin-explore-react';
import React from 'react';
import { ExplorePage, explorePlugin } from '../src';
import { exampleTools } from '../src/util/examples';
createDevApp().registerPlugin(plugin).render();
createDevApp()
.registerPlugin(explorePlugin)
.registerApi({
api: exploreToolsConfigRef,
deps: {},
factory: () => ({
async getTools() {
return exampleTools;
},
}),
})
.registerApi({
api: catalogApiRef,
deps: {},
factory: () =>
({
async getEntities() {
const domainNames = [
'playback',
'artists',
'payments',
'analytics',
'songs',
'devops',
];
return {
items: domainNames.map(
(n, i) =>
({
apiVersion: 'backstage.io/v1alpha1',
kind: 'Domain',
metadata: {
name: n,
description: `Everything about ${n}`,
tags: i % 2 === 0 ? [n] : undefined,
},
spec: {
owner: `${n}@example.com`,
},
} as Entity),
),
};
},
} as CatalogApi),
})
.addPage({ element: <ExplorePage />, title: 'Explore' })
.render();
+9 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-explore",
"version": "0.2.3",
"version": "0.2.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,20 +30,23 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
"@backstage/core": "^0.5.0",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/plugin-explore-react": "^0.0.2",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"classnames": "^2.2.6",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
@@ -0,0 +1,49 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DomainEntity } from '@backstage/catalog-model';
import { render } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { DomainCard } from './DomainCard';
describe('<DomainCard />', () => {
it('renders a domain card', () => {
const entity: DomainEntity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Domain',
metadata: {
name: 'artists',
description: 'Everything about artists',
tags: ['a-tag'],
},
spec: {
owner: 'guest',
},
};
const { getByText } = render(<DomainCard entity={entity} />, {
wrapper: MemoryRouter,
});
expect(getByText('artists')).toBeInTheDocument();
expect(getByText('Everything about artists')).toBeInTheDocument();
expect(getByText('a-tag')).toBeInTheDocument();
expect(getByText('Explore').parentElement).toHaveAttribute(
'href',
'/catalog/default/domain/artists',
);
});
});
@@ -0,0 +1,41 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DomainEntity } from '@backstage/catalog-model';
import { ItemCard } from '@backstage/core';
import {
entityRoute,
entityRouteParams,
} from '@backstage/plugin-catalog-react';
import React from 'react';
import { generatePath } from 'react-router-dom';
type DomainCardProps = {
entity: DomainEntity;
};
export const DomainCard = ({ entity }: DomainCardProps) => (
<ItemCard
title={entity.metadata.name}
description={entity.metadata.description}
tags={entity.metadata.tags}
label="Explore"
// TODO: Use useRouteRef here to generate the path
href={generatePath(
`/catalog/${entityRoute.path}`,
entityRouteParams(entity),
)}
/>
);
@@ -0,0 +1,54 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DomainEntity } from '@backstage/catalog-model';
import { render } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { DomainCardGrid } from './DomainCardGrid';
describe('<DomainCardGrid />', () => {
it('renders a grid of domain cards', () => {
const entities: DomainEntity[] = [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Domain',
metadata: {
name: 'playback',
},
spec: {
owner: 'guest',
},
},
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Domain',
metadata: {
name: 'artists',
},
spec: {
owner: 'guest',
},
},
];
const { getByText } = render(<DomainCardGrid entities={entities} />, {
wrapper: MemoryRouter,
});
expect(getByText('artists')).toBeInTheDocument();
expect(getByText('playback')).toBeInTheDocument();
});
});
@@ -0,0 +1,33 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DomainEntity } from '@backstage/catalog-model';
import { Grid } from '@material-ui/core';
import React from 'react';
import { DomainCard } from '.';
type DomainCardGridProps = {
entities: DomainEntity[];
};
export const DomainCardGrid = ({ entities }: DomainCardGridProps) => (
<Grid container spacing={4}>
{entities.map((e, i) => (
<Grid item xs={12} md={3} key={i}>
<DomainCard entity={e} />
</Grid>
))}
</Grid>
);
@@ -0,0 +1,17 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { DomainCard } from './DomainCard';
export { DomainCardGrid } from './DomainCardGrid';
@@ -0,0 +1,93 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DomainEntity } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { DomainExplorerContent } from './DomainExplorerContent';
describe('<DomainExplorerContent />', () => {
const catalogApi: jest.Mocked<typeof catalogApiRef.T> = {
addLocation: jest.fn(_a => new Promise(() => {})),
getEntities: jest.fn(),
getLocationByEntity: jest.fn(),
getLocationById: jest.fn(),
removeEntityByUid: jest.fn(),
getEntityByName: jest.fn(),
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<MemoryRouter>
<ApiProvider apis={ApiRegistry.with(catalogApiRef, catalogApi)}>
{children}
</ApiProvider>
</MemoryRouter>
);
beforeEach(() => {
jest.resetAllMocks();
});
it('renders a grid of domains', async () => {
const entities: DomainEntity[] = [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Domain',
metadata: {
name: 'playback',
},
spec: {
owner: 'guest',
},
},
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Domain',
metadata: {
name: 'artists',
},
spec: {
owner: 'guest',
},
},
];
catalogApi.getEntities.mockResolvedValue({ items: entities });
const { getByText } = render(<DomainExplorerContent />, {
wrapper: Wrapper,
});
await waitFor(() => {
expect(getByText('artists')).toBeInTheDocument();
expect(getByText('playback')).toBeInTheDocument();
});
});
it('renders empty state', async () => {
catalogApi.getEntities.mockResolvedValue({ items: [] });
const { getByText } = render(<DomainExplorerContent />, {
wrapper: Wrapper,
});
await waitFor(() =>
expect(getByText('No domains to display')).toBeInTheDocument(),
);
});
});
@@ -0,0 +1,67 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DomainEntity } from '@backstage/catalog-model';
import {
Content,
ContentHeader,
EmptyState,
Progress,
SupportButton,
useApi,
} from '@backstage/core';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { Button } from '@material-ui/core';
import React from 'react';
import { useAsync } from 'react-use';
import { DomainCardGrid } from '../DomainCard';
export const DomainExplorerContent = () => {
const catalogApi = useApi(catalogApiRef);
const { value: entities, loading } = useAsync(async () => {
const response = await catalogApi.getEntities({
filter: { kind: 'domain' },
});
return response.items as DomainEntity[];
}, [catalogApi]);
return (
<Content noPadding>
<ContentHeader title="Domains">
<SupportButton>Discover the domains in your ecosystem.</SupportButton>
</ContentHeader>
{loading && <Progress />}
{!loading && (!entities || entities.length === 0) && (
<EmptyState
missing="info"
title="No domains to display"
description={`You haven't added any domains yet.`}
action={
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/features/software-catalog/descriptor-format#kind-domain"
>
Read more
</Button>
}
/>
)}
{!loading && entities && <DomainCardGrid entities={entities} />}
</Content>
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { DomainExplorerContent } from './DomainExplorerContent';
@@ -1,5 +1,5 @@
/*
* Copyright 2020 Spotify AB
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,28 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { configApiRef, Header, Page, useApi } from '@backstage/core';
import React from 'react';
import { Content, Header, Page } from '@backstage/core';
import { RollbarProjectTable } from '../RollbarProjectTable/RollbarProjectTable';
import { useRollbarEntities } from '../../hooks/useRollbarEntities';
export const RollbarHome = () => {
const { entities, loading, error } = useRollbarEntities();
import { ExploreTabs } from './ExploreTabs';
export const ExplorePage = () => {
const configApi = useApi(configApiRef);
const organizationName =
configApi.getOptionalString('organization.name') ?? 'Backstage';
return (
<Page themeId="tool">
<Page themeId="home">
<Header
title="Rollbar"
subtitle="Real-time error tracking & debugging tools for developers"
title={`Explore the ${organizationName} ecosystem`}
subtitle="Discover solutions available in your ecosystem"
/>
<Content>
<RollbarProjectTable
entities={entities || []}
loading={loading}
error={error}
/>
</Content>
<ExploreTabs />
</Page>
);
};
@@ -0,0 +1,30 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TabbedLayout } from '@backstage/core';
import React from 'react';
import { DomainExplorerContent } from '../DomainExplorerContent';
import { ToolExplorerContent } from '../ToolExplorerContent';
export const ExploreTabs = () => (
<TabbedLayout>
<TabbedLayout.Route path="domains" title="Domains">
<DomainExplorerContent />
</TabbedLayout.Route>
<TabbedLayout.Route path="tools" title="Tools">
<ToolExplorerContent />
</TabbedLayout.Route>
</TabbedLayout>
);
@@ -0,0 +1,16 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ExplorePage } from './ExplorePage';
@@ -14,11 +14,10 @@
* limitations under the License.
*/
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
import ExploreCard from './ExploreCard';
import { render } from '@testing-library/react';
import React from 'react';
import { ToolCard } from './ToolCard';
const minProps = {
card: {
@@ -30,37 +29,31 @@ const minProps = {
},
};
describe('<ExploreCard />', () => {
describe('<ToolCard />', () => {
it('renders without exploding', () => {
const { getByText } = render(wrapInTestApp(<ExploreCard {...minProps} />));
const { getByText } = render(wrapInTestApp(<ToolCard {...minProps} />));
expect(getByText('Explore')).toBeInTheDocument();
});
it('renders props correctly', () => {
const { getByText } = render(wrapInTestApp(<ExploreCard {...minProps} />));
const { getByText } = render(wrapInTestApp(<ToolCard {...minProps} />));
expect(getByText(minProps.card.title)).toBeInTheDocument();
expect(getByText(minProps.card.description)).toBeInTheDocument();
});
it('should link out', () => {
const rendered = render(wrapInTestApp(<ExploreCard {...minProps} />));
const rendered = render(wrapInTestApp(<ToolCard {...minProps} />));
const anchor = rendered.container.querySelector('a');
expect(anchor.href).toBe(minProps.card.url);
expect(anchor).toHaveAttribute('href', minProps.card.url);
});
it('renders default description when missing', () => {
const propsWithoutDescription = {
card: {
card: {
title: 'Title',
url: 'http://spotify.com/',
image: 'https://developer.spotify.com/assets/WebAPI_intro.png',
},
},
const card = {
title: 'Title',
url: 'http://spotify.com/',
image: 'https://developer.spotify.com/assets/WebAPI_intro.png',
};
const { getByText } = render(
wrapInTestApp(<ExploreCard {...propsWithoutDescription} />),
);
const { getByText } = render(wrapInTestApp(<ToolCard card={card} />));
expect(getByText('Description missing')).toBeInTheDocument();
});
@@ -74,13 +67,13 @@ describe('<ExploreCard />', () => {
},
};
const { queryByText } = render(
wrapInTestApp(<ExploreCard {...propsWithLifecycle} />),
wrapInTestApp(<ToolCard {...propsWithLifecycle} />),
);
expect(queryByText('GA')).not.toBeInTheDocument();
});
it('renders tags correctly', () => {
const { getByText } = render(wrapInTestApp(<ExploreCard {...minProps} />));
const { getByText } = render(wrapInTestApp(<ToolCard {...minProps} />));
expect(getByText(minProps.card.tags[0])).toBeInTheDocument();
expect(getByText(minProps.card.tags[1])).toBeInTheDocument();
});
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import classNames from 'classnames';
import { ExploreTool } from '@backstage/plugin-explore-react';
import { BackstageTheme } from '@backstage/theme';
import {
Button,
Card,
@@ -23,10 +23,13 @@ import {
CardContent,
CardMedia,
Chip,
Typography,
makeStyles,
Typography,
} from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
import classNames from 'classnames';
import React from 'react';
// TODO: Align styling between Domain and ToolCard
const useStyles = makeStyles<BackstageTheme>(theme => ({
card: {
@@ -65,22 +68,12 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
},
}));
export type CardData = {
title: string;
description: string;
url: string;
image: string;
tags?: string[];
lifecycle?: string;
newsTag?: string;
};
type Props = {
card: CardData;
card: ExploreTool;
objectFit?: 'cover' | 'contain';
};
const ExploreCard = ({ card, objectFit }: Props) => {
export const ToolCard = ({ card, objectFit }: Props) => {
const classes = useStyles();
const { title, description, url, image, lifecycle, newsTag, tags } = card;
@@ -130,5 +123,3 @@ const ExploreCard = ({ card, objectFit }: Props) => {
</Card>
);
};
export default ExploreCard;
@@ -0,0 +1,46 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExploreTool } from '@backstage/plugin-explore-react';
import { BackstageTheme } from '@backstage/theme';
import { makeStyles } from '@material-ui/core';
import React from 'react';
import { ToolCard } from './ToolCard';
const useStyles = makeStyles<BackstageTheme>(theme => ({
container: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, 296px)',
gridGap: theme.spacing(3),
marginBottom: theme.spacing(6),
},
}));
type ToolCardGridProps = {
tools: ExploreTool[];
};
export const ToolCardGrid = ({ tools }: ToolCardGridProps) => {
const classes = useStyles();
return (
<div className={classes.container}>
{tools.map((card: ExploreTool, ix: any) => (
<ToolCard card={card} key={ix} />
))}
</div>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ToolCard } from './ToolCard';
export { ToolCardGrid } from './ToolCardGrid';
@@ -0,0 +1,94 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ApiProvider, ApiRegistry } from '@backstage/core';
import {
ExploreTool,
exploreToolsConfigRef,
} from '@backstage/plugin-explore-react';
import { lightTheme } from '@backstage/theme';
import { ThemeProvider } from '@material-ui/core';
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { ToolExplorerContent } from './ToolExplorerContent';
describe('<ToolExplorerContent />', () => {
const exploreToolsConfigApi: jest.Mocked<typeof exploreToolsConfigRef.T> = {
getTools: jest.fn(),
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ThemeProvider theme={lightTheme}>
<MemoryRouter>
<ApiProvider
apis={ApiRegistry.with(exploreToolsConfigRef, exploreToolsConfigApi)}
>
{children}
</ApiProvider>
</MemoryRouter>
</ThemeProvider>
);
beforeEach(() => {
jest.resetAllMocks();
});
it('renders a grid of tools', async () => {
const tools: ExploreTool[] = [
{
title: 'Lighthouse',
description:
"Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website.",
url: '/lighthouse',
image:
'https://raw.githubusercontent.com/GoogleChrome/lighthouse/8b3d7f052b2e64dd857e741d7395647f487697e7/assets/lighthouse-logo.png',
tags: ['web', 'seo', 'accessibility', 'performance'],
},
{
title: 'Tech Radar',
description:
'Tech Radar is a list of technologies, complemented by an assessment result, called ring assignment.',
url: '/tech-radar',
image:
'https://storage.googleapis.com/wf-blogs-engineering-media/2018/09/fe13bb32-wf-tech-radar-hero-1024x597.png',
tags: ['standards', 'landscape'],
},
];
exploreToolsConfigApi.getTools.mockResolvedValue(tools);
const { getByText } = render(<ToolExplorerContent />, {
wrapper: Wrapper,
});
await waitFor(() => {
expect(getByText('Lighthouse')).toBeInTheDocument();
expect(getByText('Tech Radar')).toBeInTheDocument();
});
});
it('renders empty state', async () => {
exploreToolsConfigApi.getTools.mockResolvedValue([]);
const { getByText } = render(<ToolExplorerContent />, {
wrapper: Wrapper,
});
await waitFor(() =>
expect(getByText('No tools to display')).toBeInTheDocument(),
);
});
});
@@ -0,0 +1,52 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Content,
ContentHeader,
EmptyState,
Progress,
SupportButton,
useApi,
} from '@backstage/core';
import { exploreToolsConfigRef } from '@backstage/plugin-explore-react';
import React from 'react';
import { useAsync } from 'react-use';
import { ToolCardGrid } from '../ToolCard';
export const ToolExplorerContent = () => {
const exploreToolsConfigApi = useApi(exploreToolsConfigRef);
const { value: tools, loading } = useAsync(async () => {
return await exploreToolsConfigApi.getTools();
}, [exploreToolsConfigApi]);
return (
<Content noPadding>
<ContentHeader title="Tools">
<SupportButton>Discover the tools in your ecosystem.</SupportButton>
</ContentHeader>
{loading && <Progress />}
{!loading && (!tools || tools.length === 0) && (
<EmptyState
missing="info"
title="No tools to display"
description={`You haven't added any tools yet.`}
/>
)}
{!loading && tools && <ToolCardGrid tools={tools} />}
</Content>
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { ToolExplorerContent } from './ToolExplorerContent';
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createRoutableExtension } from '@backstage/core';
import { explorePlugin } from './plugin';
import { exploreRouteRef } from './routes';
export const ExplorePage = explorePlugin.provide(
createRoutableExtension({
component: () =>
import('./components/ExplorePage').then(m => m.ExplorePage),
mountPoint: exploreRouteRef,
}),
);
+3 -2
View File
@@ -14,5 +14,6 @@
* limitations under the License.
*/
export { plugin } from './plugin';
export { Router } from './components/Router';
export * from './extensions';
export { explorePlugin } from './plugin';
export * from './routes';
+2 -2
View File
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import { plugin } from './plugin';
import { explorePlugin } from './plugin';
describe('explore', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
expect(explorePlugin).toBeDefined();
});
});
+21 -3
View File
@@ -14,9 +14,27 @@
* limitations under the License.
*/
import { createPlugin, createRouteRef } from '@backstage/core';
import { createApiFactory, createPlugin } from '@backstage/core';
import { exploreToolsConfigRef } from '@backstage/plugin-explore-react';
import { exploreRouteRef } from './routes';
import { exampleTools } from './util/examples';
export const rootRouteRef = createRouteRef({ path: '', title: 'Explore' });
export const plugin = createPlugin({
export const explorePlugin = createPlugin({
id: 'explore',
apis: [
// Register a default for exploreToolsConfigRef, you may want to override
// the API locally in your app.
createApiFactory({
api: exploreToolsConfigRef,
deps: {},
factory: () => ({
async getTools() {
return exampleTools;
},
}),
}),
],
routes: {
explore: exploreRouteRef,
},
});
@@ -18,19 +18,7 @@ import { createRouteRef } from '@backstage/core';
const NoIcon = () => null;
export const rootRouteRef = createRouteRef({
export const exploreRouteRef = createRouteRef({
icon: NoIcon,
path: '/rollbar',
title: 'Rollbar Home',
});
export const entityRouteRef = createRouteRef({
path: '/rollbar/:optionalNamespaceAndName',
title: 'Rollbar',
});
export const catalogRouteRef = createRouteRef({
icon: NoIcon,
path: '',
title: 'Rollbar',
title: 'Explore',
});
@@ -14,28 +14,7 @@
* limitations under the License.
*/
import React from 'react';
import { makeStyles, Typography } from '@material-ui/core';
import {
Content,
ContentHeader,
Header,
Page,
SupportButton,
} from '@backstage/core';
import ExploreCard, { CardData } from './ExploreCard';
import { BackstageTheme } from '@backstage/theme';
const useStyles = makeStyles<BackstageTheme>(theme => ({
container: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, 296px)',
gridGap: theme.spacing(3),
marginBottom: theme.spacing(6),
},
}));
const toolsCards = [
export const exampleTools = [
{
title: 'New Relic',
description:
@@ -112,28 +91,3 @@ const toolsCards = [
tags: ['rollbar', 'monitoring', 'errors'],
},
];
export const ExplorePluginPage = () => {
const classes = useStyles();
return (
<Page themeId="home">
<Header
title="Explore"
subtitle="Tools and services available in Backstage"
/>
<Content>
<ContentHeader title="Tools">
<SupportButton>
<Typography>Explore tools available in Backstage</Typography>
</SupportButton>
</ContentHeader>
<div className={classes.container}>
{toolsCards.map((card: CardData, ix: any) => (
<ExploreCard card={card} key={ix} />
))}
</div>
</Content>
</Page>
);
};
+29
View File
@@ -1,5 +1,34 @@
# @backstage/plugin-fossa
## 0.2.0
### Minor Changes
- 5ac9df899: Port FOSSA plugin to new extension model.
If you are using the FOSSA plugin adjust the plugin import from `plugin` to
`fossaPlugin` and replace `<FossaCard entity={...} ...>` with `<EntityFossaCard />`.
### Patch Changes
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/plugin-catalog-react@0.0.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.1.2
### Patch Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-fossa",
"version": "0.1.2",
"version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/plugin-catalog-react": "^0.0.1",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -44,8 +44,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+16
View File
@@ -1,5 +1,21 @@
# @backstage/plugin-gcp-projects
## 0.2.4
### Patch Changes
- bc5082a00: Migrate to new composability API, exporting the plugin as `gcpProjectsPlugin` and page as `GcpProjectsPage`.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [54c7d02f7]
- @backstage/core@0.6.0
- @backstage/theme@0.2.3
## 0.2.3
### Patch Changes
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-gcp-projects",
"version": "0.2.3",
"version": "0.2.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,8 +30,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.5.0",
"@backstage/theme": "^0.2.2",
"@backstage/core": "^0.6.0",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -41,8 +41,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+26
View File
@@ -1,5 +1,31 @@
# @backstage/plugin-github-actions
## 0.3.1
### Patch Changes
- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
- Updated dependencies [12ece98cd]
- Updated dependencies [c4abcdb60]
- Updated dependencies [d82246867]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [064c513e1]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [3149bfe63]
- Updated dependencies [54c7d02f7]
- Updated dependencies [2e62aea6f]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/integration@0.3.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.3.0
### Minor Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-github-actions",
"version": "0.3.0",
"version": "0.3.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,10 +32,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/integration": "^0.3.1",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/integration": "^0.3.2",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -49,8 +49,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+15
View File
@@ -1,5 +1,20 @@
# @backstage/plugin-gitops-profiles
## 0.2.4
### Patch Changes
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [54c7d02f7]
- @backstage/core@0.6.0
- @backstage/theme@0.2.3
## 0.2.3
### Patch Changes
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-gitops-profiles",
"version": "0.2.3",
"version": "0.2.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.5.0",
"@backstage/theme": "^0.2.2",
"@backstage/core": "^0.6.0",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -42,8 +42,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+16
View File
@@ -1,5 +1,21 @@
# @backstage/plugin-graphiql
## 0.2.7
### Patch Changes
- 87b189d00: Finalized composability API migration, now exporting the plugin as `graphiqlPlugin`.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [54c7d02f7]
- @backstage/core@0.6.0
- @backstage/theme@0.2.3
## 0.2.6
### Patch Changes
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-graphiql",
"description": "Backstage plugin for browsing GraphQL APIs",
"version": "0.2.6",
"version": "0.2.7",
"private": false,
"publishConfig": {
"access": "public",
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.5.0",
"@backstage/theme": "^0.2.2",
"@backstage/core": "^0.6.0",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -43,8 +43,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+2 -2
View File
@@ -29,7 +29,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.5.1",
"@backstage/backend-common": "^0.5.2",
"@backstage/config": "^0.1.2",
"@backstage/plugin-catalog-graphql": "^0.2.6",
"@graphql-modules/core": "^0.7.17",
@@ -45,7 +45,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/cli": "^0.6.0",
"@types/supertest": "^2.0.8",
"eslint-plugin-graphql": "^4.0.0",
"msw": "^0.20.5",
+23
View File
@@ -1,5 +1,28 @@
# @backstage/plugin-jenkins
## 0.3.8
### Patch Changes
- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/plugin-catalog-react@0.0.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.3.7
### Patch Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-jenkins",
"version": "0.3.7",
"version": "0.3.8",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/plugin-catalog-react": "^0.0.1",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -47,8 +47,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+5 -5
View File
@@ -36,17 +36,17 @@ export function useBuilds(owner: string, repo: string, branch?: string) {
const { loading, value: builds, retry } = useAsyncRetry(async () => {
try {
let builds;
let build;
if (branch) {
builds = await api.getLastBuild(`${owner}/${repo}/${branch}`);
build = await api.getLastBuild(`${owner}/${repo}/${branch}`);
} else {
builds = await api.getFolder(`${owner}/${repo}`);
build = await api.getFolder(`${owner}/${repo}`);
}
const size = Array.isArray(builds) ? builds?.[0].build_num! : 1;
const size = Array.isArray(build) ? build?.[0].build_num! : 1;
setTotal(size);
return builds || [];
return build || [];
} catch (e) {
errorApi.post(e);
throw e;
+3 -3
View File
@@ -31,8 +31,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/backend-common": "^0.5.1",
"@backstage/catalog-model": "^0.7.0",
"@backstage/backend-common": "^0.5.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/config": "^0.1.2",
"@types/express": "^4.17.6",
"express": "^4.17.1",
@@ -42,7 +42,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/cli": "^0.6.0",
"@types/jest-when": "^2.7.2",
"@types/lodash": "^4.14.151",
"jest-when": "^3.1.0",
+23
View File
@@ -1,5 +1,28 @@
# @backstage/plugin-kafka
## 0.2.1
### Patch Changes
- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
- Updated dependencies [12ece98cd]
- Updated dependencies [d82246867]
- Updated dependencies [7fc89bae2]
- Updated dependencies [c810082ae]
- Updated dependencies [5fa3bdb55]
- Updated dependencies [6e612ce25]
- Updated dependencies [025e122c3]
- Updated dependencies [21e624ba9]
- Updated dependencies [da9f53c60]
- Updated dependencies [32c95605f]
- Updated dependencies [7881f2117]
- Updated dependencies [54c7d02f7]
- Updated dependencies [11cb5ef94]
- @backstage/core@0.6.0
- @backstage/plugin-catalog-react@0.0.2
- @backstage/theme@0.2.3
- @backstage/catalog-model@0.7.1
## 0.2.0
### Minor Changes
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kafka",
"version": "0.2.0",
"version": "0.2.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,10 +20,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/plugin-catalog-react": "^0.0.1",
"@backstage/theme": "^0.2.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/core": "^0.6.0",
"@backstage/plugin-catalog-react": "^0.0.2",
"@backstage/theme": "^0.2.3",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -33,8 +33,8 @@
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/cli": "^0.6.0",
"@backstage/dev-utils": "^0.1.9",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
+3 -3
View File
@@ -32,8 +32,8 @@
},
"dependencies": {
"@aws-sdk/credential-provider-node": "^3.3.0",
"@backstage/backend-common": "^0.5.1",
"@backstage/catalog-model": "^0.7.0",
"@backstage/backend-common": "^0.5.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/config": "^0.1.2",
"@kubernetes/client-node": "^0.13.2",
"@types/express": "^4.17.6",
@@ -51,7 +51,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/cli": "^0.6.0",
"@types/aws4": "^1.5.1",
"supertest": "^4.0.2"
},

Some files were not shown because too many files have changed in this diff Show More