Merge branch 'backstage:master' into master
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-adr': patch
|
||||
---
|
||||
|
||||
Updated readme instructions
|
||||
+38
-8
@@ -28,7 +28,7 @@ yarn --cwd packages/app add @backstage/plugin-adr
|
||||
import { EntityAdrContent, isAdrAvailable } from '@backstage/plugin-adr';
|
||||
|
||||
...
|
||||
|
||||
// Note: Add to any other Pages as well (e.g. defaultEntityPage and websiteEntityPage)
|
||||
const serviceEntityPage = (
|
||||
<EntityLayout>
|
||||
{/* other tabs... */}
|
||||
@@ -69,16 +69,46 @@ Afterwards, add the following code snippet to use `AdrSearchResultListItem` when
|
||||
```tsx
|
||||
// In packages/app/src/components/search/SearchPage.tsx
|
||||
import { AdrSearchResultListItem } from '@backstage/plugin-adr';
|
||||
import { AdrDocument } from '@backstage/plugin-adr-common';
|
||||
|
||||
...
|
||||
// Optional - Add type to side pane
|
||||
<SearchType.Accordion
|
||||
name="Result Type"
|
||||
defaultValue="software-catalog"
|
||||
types={[
|
||||
...
|
||||
{
|
||||
value: 'adr',
|
||||
name: 'Architecture Decision Records',
|
||||
icon: <DocsIcon />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
...
|
||||
|
||||
case 'adr':
|
||||
return (
|
||||
<AdrSearchResultListItem
|
||||
key={document.location}
|
||||
result={document}
|
||||
/>
|
||||
);
|
||||
// In results
|
||||
<SearchResult>
|
||||
{({ results }) => (
|
||||
<List>
|
||||
{results.map(({ type, document, highlight, rank }) => {
|
||||
switch (type) {
|
||||
...
|
||||
case 'adr':
|
||||
return (
|
||||
<AdrSearchResultListItem
|
||||
key={document.location}
|
||||
// Not required if you're leveraging the new search results extensions available in v1.11+
|
||||
// https://backstage.io/docs/features/search/how-to-guides#2-using-an-extension-in-your-backstage-app
|
||||
result={document as AdrDocument}
|
||||
/>
|
||||
);
|
||||
...
|
||||
}
|
||||
})}
|
||||
</List>
|
||||
)}
|
||||
</SearchResult>
|
||||
```
|
||||
|
||||
## Custom ADR formats
|
||||
|
||||
@@ -61,7 +61,15 @@ export type GitlabProviderConfig = {
|
||||
host: string;
|
||||
group: string;
|
||||
id: string;
|
||||
branch: string;
|
||||
/**
|
||||
* @deprecated use `fallbackBranch` instead
|
||||
*/
|
||||
branch?: string;
|
||||
/**
|
||||
* If there is no default branch defined at the project, this fallback is used to discover catalog files.
|
||||
* Defaults to: `master`
|
||||
*/
|
||||
fallbackBranch: string;
|
||||
catalogFile: string;
|
||||
projectPattern: RegExp;
|
||||
userPattern: RegExp;
|
||||
|
||||
+11
-4
@@ -61,7 +61,10 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider {
|
||||
throw new Error('Either schedule or scheduler must be provided.');
|
||||
}
|
||||
|
||||
const providerConfigs = readGitlabConfigs(config);
|
||||
const providerConfigs = readGitlabConfigs(
|
||||
config,
|
||||
options.logger.child({ target: 'GitlabDiscoveryEntityProvider' }),
|
||||
);
|
||||
const integrations = ScmIntegrations.fromConfig(config).gitlab;
|
||||
const providers: GitlabDiscoveryEntityProvider[] = [];
|
||||
|
||||
@@ -177,11 +180,15 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.config.branch === '*' && project.default_branch === undefined) {
|
||||
if (
|
||||
this.config.fallbackBranch === '*' &&
|
||||
project.default_branch === undefined
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const project_branch = project.default_branch ?? this.config.branch;
|
||||
const project_branch =
|
||||
project.default_branch ?? this.config.fallbackBranch;
|
||||
|
||||
const projectHasFile: boolean = await client.hasFile(
|
||||
project.path_with_namespace ?? '',
|
||||
@@ -204,7 +211,7 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider {
|
||||
}
|
||||
|
||||
private createLocationSpec(project: GitLabProject): LocationSpec {
|
||||
const project_branch = project.default_branch ?? this.config.branch;
|
||||
const project_branch = project.default_branch ?? this.config.fallbackBranch;
|
||||
return {
|
||||
type: 'url',
|
||||
target: `${project.web_url}/-/blob/${project_branch}/${this.config.catalogFile}`,
|
||||
|
||||
+4
-1
@@ -72,7 +72,10 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider {
|
||||
throw new Error('Either schedule or scheduler must be provided.');
|
||||
}
|
||||
|
||||
const providerConfigs = readGitlabConfigs(config);
|
||||
const providerConfigs = readGitlabConfigs(
|
||||
config,
|
||||
options.logger.child({ target: 'GitlabOrgDiscoveryEntityProvider' }),
|
||||
);
|
||||
const integrations = ScmIntegrations.fromConfig(config).gitlab;
|
||||
const providers: GitlabOrgDiscoveryEntityProvider[] = [];
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readGitlabConfigs } from './config';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
describe('config', () => {
|
||||
it('empty gitlab config', () => {
|
||||
@@ -26,7 +29,7 @@ describe('config', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const result = readGitlabConfigs(config);
|
||||
const result = readGitlabConfigs(config, logger);
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -44,13 +47,13 @@ describe('config', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const result = readGitlabConfigs(config);
|
||||
const result = readGitlabConfigs(config, logger);
|
||||
expect(result).toHaveLength(1);
|
||||
result.forEach(r =>
|
||||
expect(r).toStrictEqual({
|
||||
id: 'test',
|
||||
group: 'group',
|
||||
branch: 'master',
|
||||
fallbackBranch: 'master',
|
||||
host: 'host',
|
||||
catalogFile: 'catalog-info.yaml',
|
||||
projectPattern: /[\s\S]*/,
|
||||
@@ -78,13 +81,13 @@ describe('config', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const result = readGitlabConfigs(config);
|
||||
const result = readGitlabConfigs(config, logger);
|
||||
expect(result).toHaveLength(1);
|
||||
result.forEach(r =>
|
||||
expect(r).toStrictEqual({
|
||||
id: 'test',
|
||||
group: 'group',
|
||||
branch: 'not-master',
|
||||
fallbackBranch: 'not-master',
|
||||
host: 'host',
|
||||
catalogFile: 'custom-file.yaml',
|
||||
projectPattern: /[\s\S]*/,
|
||||
@@ -116,13 +119,13 @@ describe('config', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const result = readGitlabConfigs(config);
|
||||
const result = readGitlabConfigs(config, logger);
|
||||
expect(result).toHaveLength(1);
|
||||
result.forEach(r =>
|
||||
expect(r).toStrictEqual({
|
||||
id: 'test',
|
||||
group: 'group',
|
||||
branch: 'master',
|
||||
fallbackBranch: 'master',
|
||||
host: 'host',
|
||||
catalogFile: 'catalog-info.yaml',
|
||||
projectPattern: /[\s\S]*/,
|
||||
@@ -155,7 +158,7 @@ describe('config', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => readGitlabConfigs(config)).toThrow(
|
||||
expect(() => readGitlabConfigs(config, logger)).toThrow(
|
||||
"Missing required config value at 'catalog.providers.gitlab.test.host'",
|
||||
);
|
||||
});
|
||||
@@ -174,7 +177,7 @@ describe('config', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const result = readGitlabConfigs(config);
|
||||
const result = readGitlabConfigs(config, logger);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].group).toEqual('');
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks';
|
||||
import { Config } from '@backstage/config';
|
||||
import { GitlabProviderConfig } from '../lib';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
/**
|
||||
* Extracts the gitlab config from a config object
|
||||
@@ -25,11 +26,25 @@ import { GitlabProviderConfig } from '../lib';
|
||||
*
|
||||
* @param id - The provider key
|
||||
* @param config - The config object to extract from
|
||||
* @param logger - The logger
|
||||
*/
|
||||
function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
|
||||
function readGitlabConfig(
|
||||
id: string,
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): GitlabProviderConfig {
|
||||
const group = config.getOptionalString('group') ?? '';
|
||||
const host = config.getString('host');
|
||||
const branch = config.getOptionalString('branch') ?? 'master';
|
||||
const branch = config.getOptionalString('branch');
|
||||
|
||||
if (branch) {
|
||||
logger.warn(
|
||||
'The configuration key `branch` has been deprecated in favor of the configuration key `fallbackBranch`.',
|
||||
);
|
||||
}
|
||||
|
||||
const fallbackBranch =
|
||||
config.getOptionalString('fallbackBranch') ?? branch ?? 'master';
|
||||
const catalogFile =
|
||||
config.getOptionalString('entityFilename') ?? 'catalog-info.yaml';
|
||||
const projectPattern = new RegExp(
|
||||
@@ -50,7 +65,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
|
||||
return {
|
||||
id,
|
||||
group,
|
||||
branch,
|
||||
fallbackBranch,
|
||||
host,
|
||||
catalogFile,
|
||||
projectPattern,
|
||||
@@ -67,8 +82,12 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig {
|
||||
* @public
|
||||
*
|
||||
* @param config - The config object to extract from
|
||||
* @param logger - The logger
|
||||
*/
|
||||
export function readGitlabConfigs(config: Config): GitlabProviderConfig[] {
|
||||
export function readGitlabConfigs(
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): GitlabProviderConfig[] {
|
||||
const configs: GitlabProviderConfig[] = [];
|
||||
|
||||
const providerConfigs = config.getOptionalConfig('catalog.providers.gitlab');
|
||||
@@ -78,7 +97,13 @@ export function readGitlabConfigs(config: Config): GitlabProviderConfig[] {
|
||||
}
|
||||
|
||||
for (const id of providerConfigs.keys()) {
|
||||
configs.push(readGitlabConfig(id, providerConfigs.getConfig(id)));
|
||||
configs.push(
|
||||
readGitlabConfig(
|
||||
id,
|
||||
providerConfigs.getConfig(id),
|
||||
logger.child({ target: id }),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return configs;
|
||||
|
||||
@@ -8,13 +8,12 @@ Lighthouse Backend allows you to run scheduled lighthouse Tests for each Website
|
||||
|
||||
```bash
|
||||
# From your Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/lighthouse-backend
|
||||
yarn add --cwd packages/backend @backstage/plugin-lighthouse-backend
|
||||
```
|
||||
|
||||
2. Create a `lighthouse.ts` file inside `packages/backend/src/plugins/`:
|
||||
|
||||
```typescript
|
||||
import { Router } from 'express';
|
||||
import { createScheduler } from '@backstage/plugin-lighthouse-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
|
||||
@@ -67,6 +67,14 @@ return createRouter({ schedule: schedule, batchSize: 40 }, { ...env });
|
||||
|
||||
**Note:** The default batch size is 20
|
||||
|
||||
## Kind
|
||||
|
||||
The default setup only processes entities of kind `['API', 'Component', 'Template']`. To control the `kind` that are processed provide that to the `createRouter` function in your `packages/backend/src/plugins/linguist.ts` like this:
|
||||
|
||||
```ts
|
||||
return createRouter({ schedule: schedule, kind: ['Component'] }, { ...env });
|
||||
```
|
||||
|
||||
## Refresh
|
||||
|
||||
The default setup will only generate the language breakdown for entities with the linguist annotation that have not been generated yet. If you want this process to also refresh the data you can do so by adding the `age` (as a `HumanDuration`) in your `packages/backend/src/plugins/linguist.ts` when you call `createRouter`:
|
||||
|
||||
@@ -34,6 +34,7 @@ export class LinguistBackendApi {
|
||||
age?: HumanDuration,
|
||||
batchSize?: number,
|
||||
useSourceLocation?: boolean,
|
||||
kind?: string[],
|
||||
);
|
||||
// (undocumented)
|
||||
getEntityLanguages(entityRef: string): Promise<Languages>;
|
||||
@@ -79,6 +80,8 @@ export interface PluginOptions {
|
||||
// (undocumented)
|
||||
batchSize?: number;
|
||||
// (undocumented)
|
||||
kind?: string[];
|
||||
// (undocumented)
|
||||
schedule?: TaskScheduleDefinition;
|
||||
// (undocumented)
|
||||
useSourceLocation?: boolean;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 { kindOrDefault } from './LinguistBackendApi';
|
||||
|
||||
describe('kindOrDefault', () => {
|
||||
it('should return default kind when undefined', () => {
|
||||
expect(kindOrDefault()).toEqual(['API', 'Component', 'Template']);
|
||||
});
|
||||
it('should return the default kind when empty', () => {
|
||||
expect(kindOrDefault([])).toEqual(['API', 'Component', 'Template']);
|
||||
});
|
||||
it('should return provided kind when not empty', () => {
|
||||
expect(kindOrDefault(['API'])).toEqual(['API']);
|
||||
});
|
||||
});
|
||||
@@ -56,6 +56,7 @@ export class LinguistBackendApi {
|
||||
private readonly age?: HumanDuration;
|
||||
private readonly batchSize?: number;
|
||||
private readonly useSourceLocation?: boolean;
|
||||
private readonly kind: string[];
|
||||
public constructor(
|
||||
logger: Logger,
|
||||
store: LinguistBackendStore,
|
||||
@@ -65,6 +66,7 @@ export class LinguistBackendApi {
|
||||
age?: HumanDuration,
|
||||
batchSize?: number,
|
||||
useSourceLocation?: boolean,
|
||||
kind?: string[],
|
||||
) {
|
||||
this.logger = logger;
|
||||
this.store = store;
|
||||
@@ -75,6 +77,7 @@ export class LinguistBackendApi {
|
||||
this.batchSize = batchSize;
|
||||
this.age = age;
|
||||
this.useSourceLocation = useSourceLocation;
|
||||
this.kind = kindOrDefault(kind);
|
||||
}
|
||||
|
||||
public async getEntityLanguages(entityRef: string): Promise<Languages> {
|
||||
@@ -99,7 +102,7 @@ export class LinguistBackendApi {
|
||||
: LINGUIST_ANNOTATION;
|
||||
const request: GetEntitiesRequest = {
|
||||
filter: {
|
||||
kind: ['API', 'Component', 'Template'],
|
||||
kind: this.kind,
|
||||
[`metadata.annotations.${annotationKey}`]: CATALOG_FILTER_EXISTS,
|
||||
},
|
||||
fields: ['kind', 'metadata'],
|
||||
@@ -228,3 +231,10 @@ export class LinguistBackendApi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function kindOrDefault(kind?: string[]) {
|
||||
if (!kind || kind.length === 0) {
|
||||
return ['API', 'Component', 'Template'];
|
||||
}
|
||||
return kind;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface PluginOptions {
|
||||
age?: HumanDuration;
|
||||
batchSize?: number;
|
||||
useSourceLocation?: boolean;
|
||||
kind?: string[];
|
||||
}
|
||||
|
||||
/** @public */
|
||||
@@ -56,7 +57,7 @@ export async function createRouter(
|
||||
pluginOptions: PluginOptions,
|
||||
routerOptions: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { schedule, age, batchSize, useSourceLocation } = pluginOptions;
|
||||
const { schedule, age, batchSize, useSourceLocation, kind } = pluginOptions;
|
||||
|
||||
const { logger, reader, database, discovery, scheduler, tokenManager } =
|
||||
routerOptions;
|
||||
@@ -76,6 +77,7 @@ export async function createRouter(
|
||||
age,
|
||||
batchSize,
|
||||
useSourceLocation,
|
||||
kind,
|
||||
);
|
||||
|
||||
if (scheduler && schedule) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 { SvgIcon, SvgIconProps } from '@material-ui/core';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export const OctopusDeployIcon = (props: SvgIconProps) => (
|
||||
<SvgIcon {...props} viewBox="0 0 140 140">
|
||||
<defs>
|
||||
<style>.cls-1{fill:#fff;}</style>
|
||||
</defs>
|
||||
<path
|
||||
className="cls-1"
|
||||
d="M19.13 103.13c9.35-6 20.33-16.34 16.16-28.5C33 68 29.87 62.33 29.45 55.17a43.84 43.84 0 0 1 2.66-17.83C40.49 14.85 65.4 4.2 87.92 11c20.85 6.26 35.2 30.48 26.56 51.58-5 12.18-7.22 21.58 3.9 31.05 3 2.57 10.33 6.42 10.31 11 0 6-11.74-1.27-13-2.31 1.49 2.6 16.24 18 6.85 19.08-8.64 1-16.27-11.06-21.47-16.25-8.72-8.7-7.21 10.55-7.25 14.53-.06 6.28-4.5 19-12.46 10.72-6.58-6.84-4.09-17.76-8.66-25.36-5-8.33-13.34 8.33-15.42 11.37-2.33 3.4-14 19.89-18.59 11.1-3.76-7.13 2.25-18.3 5.2-24.75-1.08 2.34-8.71 5.8-10.94 6.93-5 2.54-10.15 4.05-15.82 3.65-11.73-.84-2.73-7.07 2-10.15z"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 { OctopusDeployIcon } from './OctopusDeployIcon';
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
Table,
|
||||
TableColumn,
|
||||
} from '@backstage/core-components';
|
||||
import { OctopusDeployIcon } from '../OctopusDeployIcon';
|
||||
|
||||
type ReleaseTableProps = {
|
||||
environments?: OctopusEnvironment[];
|
||||
@@ -141,6 +142,8 @@ export const ReleaseTable = ({
|
||||
}}
|
||||
title={
|
||||
<Box display="flex" alignItems="center">
|
||||
<OctopusDeployIcon style={{ fontSize: 30 }} />
|
||||
<Box mr={1} />
|
||||
Octopus Deploy - Releases ({releases ? releases.length : 0})
|
||||
</Box>
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
|
||||
import { IssueLink } from './IssueLink';
|
||||
|
||||
const defaultProps = {
|
||||
const defaultGithubProps = {
|
||||
repository: {
|
||||
type: 'github',
|
||||
name: 'backstage',
|
||||
@@ -40,14 +40,28 @@ const defaultProps = {
|
||||
},
|
||||
};
|
||||
|
||||
const defaultGitlabProps = {
|
||||
repository: {
|
||||
type: 'gitlab',
|
||||
name: 'backstageSubgroup/backstage',
|
||||
owner: 'backstage',
|
||||
protocol: 'https',
|
||||
resource: 'gitlab.com',
|
||||
},
|
||||
template: {
|
||||
title: 'Documentation feedback',
|
||||
body: '## Documentation Feedback 📝',
|
||||
},
|
||||
};
|
||||
|
||||
describe('FeedbackLink', () => {
|
||||
const apiSpy = new MockAnalyticsApi();
|
||||
|
||||
it('Should open new issue tab', () => {
|
||||
it('Should open new Github issue tab', () => {
|
||||
render(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, apiSpy]]}>
|
||||
<IssueLink {...defaultProps} />
|
||||
<IssueLink {...defaultGithubProps} />
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
@@ -55,19 +69,39 @@ describe('FeedbackLink', () => {
|
||||
const link = screen.getByText(/Open new Github issue/);
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
const encodedTitle = encodeURIComponent(defaultProps.template.title);
|
||||
const encodedBody = encodeURIComponent(defaultProps.template.body);
|
||||
const encodedTitle = encodeURIComponent(defaultGithubProps.template.title);
|
||||
const encodedBody = encodeURIComponent(defaultGithubProps.template.body);
|
||||
expect(link).toHaveAttribute(
|
||||
'href',
|
||||
`https://github.com/backstage/backstage/issues/new?title=${encodedTitle}&body=${encodedBody}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('Should open new Gitlab issue tab', () => {
|
||||
render(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, apiSpy]]}>
|
||||
<IssueLink {...defaultGitlabProps} />
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
const link = screen.getByText(/Open new Gitlab issue/);
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
const encodedTitle = encodeURIComponent(defaultGithubProps.template.title);
|
||||
const encodedBody = encodeURIComponent(defaultGithubProps.template.body);
|
||||
expect(link).toHaveAttribute(
|
||||
'href',
|
||||
`https://gitlab.com/backstage/backstageSubgroup/backstage/issues/new?issue[title]=${encodedTitle}&issue[description]=${encodedBody}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('Should track click events', async () => {
|
||||
render(
|
||||
wrapInTestApp(
|
||||
<TestApiProvider apis={[[analyticsApiRef, apiSpy]]}>
|
||||
<IssueLink {...defaultProps} />
|
||||
<IssueLink {...defaultGithubProps} />
|
||||
</TestApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -59,14 +59,13 @@ const getUrl = (repository: Repository, template: ReportIssueTemplate) => {
|
||||
const encodedTitle = encodeURIComponent(title);
|
||||
const encodedBody = encodeURIComponent(body);
|
||||
const { protocol, resource, owner, name, type } = repository;
|
||||
const encodedOwner = encodeURIComponent(owner);
|
||||
const encodedName = encodeURIComponent(name);
|
||||
|
||||
const url = `${protocol}://${resource}/${encodedOwner}/${encodedName}`;
|
||||
const url = `${protocol}://${resource}/${owner}/${name}`;
|
||||
const encodedUrl = encodeURI(url);
|
||||
if (type === 'github') {
|
||||
return `${url}/issues/new?title=${encodedTitle}&body=${encodedBody}`;
|
||||
return `${encodedUrl}/issues/new?title=${encodedTitle}&body=${encodedBody}`;
|
||||
}
|
||||
return `${url}/issues/new?issue[title]=${encodedTitle}&issue[description]=${encodedBody}`;
|
||||
return `${encodedUrl}/issues/new?issue[title]=${encodedTitle}&issue[description]=${encodedBody}`;
|
||||
};
|
||||
|
||||
export const IssueLink = ({ template, repository }: IssueLinkProps) => {
|
||||
|
||||
Reference in New Issue
Block a user