Merge branch 'master' into samiram/fix-banner-pos

This commit is contained in:
Samira Mokaram
2020-09-23 17:17:06 +02:00
128 changed files with 2531 additions and 1027 deletions
+1
View File
@@ -16,6 +16,7 @@
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.22",
"@backstage/plugin-graphiql": "^0.1.1-alpha.22",
"@backstage/plugin-jenkins": "^0.1.1-alpha.22",
"@backstage/plugin-kubernetes": "^0.1.1-alpha.22",
"@backstage/plugin-lighthouse": "^0.1.1-alpha.22",
"@backstage/plugin-newrelic": "^0.1.1-alpha.22",
"@backstage/plugin-register-component": "^0.1.1-alpha.22",
@@ -29,6 +29,7 @@ import {
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
import { Router as SentryRouter } from '@backstage/plugin-sentry';
import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes';
import React from 'react';
import {
AboutCard,
@@ -99,6 +100,11 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
title="Docs"
element={<DocsRouter entity={entity} />}
/>
<EntityPageLayout.Content
path="/kubernetes/*"
title="Kubernetes"
element={<KubernetesRouter entity={entity} />}
/>
</EntityPageLayout>
);
@@ -124,6 +130,11 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
title="Docs"
element={<DocsRouter entity={entity} />}
/>
<EntityPageLayout.Content
path="/kubernetes/*"
title="Kubernetes"
element={<KubernetesRouter entity={entity} />}
/>
</EntityPageLayout>
);
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
+1
View File
@@ -33,3 +33,4 @@ export { plugin as Jenkins } from '@backstage/plugin-jenkins';
export { plugin as ApiDocs } from '@backstage/plugin-api-docs';
export { plugin as GithubPullRequests } from '@roadiehq/backstage-plugin-github-pull-requests';
export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects';
export { plugin as Kubernetes } from '@backstage/plugin-kubernetes';
+1
View File
@@ -26,6 +26,7 @@
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.22",
"@backstage/plugin-graphql-backend": "^0.1.1-alpha.22",
"@backstage/plugin-identity-backend": "^0.1.1-alpha.22",
"@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.22",
"@backstage/plugin-proxy-backend": "^0.1.1-alpha.22",
"@backstage/plugin-rollbar-backend": "^0.1.1-alpha.22",
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.22",
+3
View File
@@ -34,6 +34,7 @@ import healthcheck from './plugins/healthcheck';
import auth from './plugins/auth';
import catalog from './plugins/catalog';
import identity from './plugins/identity';
import kubernetes from './plugins/kubernetes';
import rollbar from './plugins/rollbar';
import scaffolder from './plugins/scaffolder';
import sentry from './plugins/sentry';
@@ -73,6 +74,7 @@ async function main() {
const rollbarEnv = useHotMemoize(module, () => createEnv('rollbar'));
const sentryEnv = useHotMemoize(module, () => createEnv('sentry'));
const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
const graphqlEnv = useHotMemoize(module, () => createEnv('graphql'));
const appEnv = useHotMemoize(module, () => createEnv('app'));
@@ -86,6 +88,7 @@ async function main() {
.addRouter('/auth', await auth(authEnv))
.addRouter('/identity', await identity(identityEnv))
.addRouter('/techdocs', await techdocs(techdocsEnv))
.addRouter('/kubernetes', await kubernetes(kubernetesEnv))
.addRouter('/proxy', await proxy(proxyEnv, '/proxy'))
.addRouter('/graphql', await graphql(graphqlEnv))
.addRouter('', await app(appEnv));
@@ -0,0 +1,22 @@
/*
* 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 { createRouter } from '@backstage/plugin-kubernetes-backend';
import { PluginEnvironment } from '../types';
export default async function createPlugin({ logger }: PluginEnvironment) {
return await createRouter({ logger });
}
+2 -2
View File
@@ -26,7 +26,7 @@ import {
GitlabPublisher,
CreateReactAppTemplater,
Templaters,
RepoVisilityOptions,
RepoVisibilityOptions,
} from '@backstage/plugin-scaffolder-backend';
import { Octokit } from '@octokit/rest';
import { Gitlab } from '@gitbeaker/node';
@@ -61,7 +61,7 @@ export default async function createPlugin({
try {
const repoVisibility = githubConfig.getString(
'visibility',
) as RepoVisilityOptions;
) as RepoVisibilityOptions;
const githubToken = githubConfig.getString('token');
const githubClient = new Octokit({ auth: githubToken });
@@ -21,6 +21,7 @@ import inquirer, { Answers, Question } from 'inquirer';
import { exec as execCb } from 'child_process';
import { resolve as resolvePath } from 'path';
import os from 'os';
import { Command } from 'commander';
import {
parseOwnerIds,
addCodeownersEntry,
@@ -32,12 +33,12 @@ import { version as backstageVersion } from '../../lib/version';
const exec = promisify(execCb);
async function checkExists(rootDir: string, id: string) {
await Task.forItem('checking', id, async () => {
const destination = resolvePath(rootDir, 'plugins', id);
async function checkExists(destination: string) {
await Task.forItem('checking', destination, async () => {
if (await fs.pathExists(destination)) {
const existing = chalk.cyan(destination.replace(`${rootDir}/`, ''));
const existing = chalk.cyan(
destination.replace(`${paths.targetRoot}/`, ''),
);
throw new Error(
`A plugin with the same name already exists: ${existing}\nPlease try again with a different plugin ID`,
);
@@ -86,10 +87,9 @@ export const addExportStatement = async (
export async function addPluginDependencyToApp(
rootDir: string,
pluginName: string,
pluginPackage: string,
versionStr: string,
) {
const pluginPackage = `@backstage/plugin-${pluginName}`;
const packageFilePath = 'packages/app/package.json';
const packageFile = resolvePath(rootDir, packageFilePath);
@@ -116,8 +116,11 @@ export async function addPluginDependencyToApp(
});
}
export async function addPluginToApp(rootDir: string, pluginName: string) {
const pluginPackage = `@backstage/plugin-${pluginName}`;
export async function addPluginToApp(
rootDir: string,
pluginName: string,
pluginPackage: string,
) {
const pluginNameCapitalized = pluginName
.split('-')
.map(name => capitalize(name))
@@ -175,7 +178,7 @@ export async function movePlugin(
});
}
export default async () => {
export default async (cmd: Command) => {
const codeownersPath = await getCodeownersFilePath(paths.targetRoot);
const questions: Question[] = [
@@ -221,20 +224,29 @@ export default async () => {
}
const answers: Answers = await inquirer.prompt(questions);
const name = cmd.scope
? `@${cmd.scope.replace(/^@/, '')}/plugin-${answers.id}`
: `plugin-${answers.id}`;
const npmRegistry = cmd.npmRegistry && cmd.scope ? cmd.npmRegistry : '';
const privatePackage = cmd.private === false ? false : true;
const isMonoRepo = await fs.pathExists(paths.resolveTargetRoot('lerna.json'));
const appPackage = paths.resolveTargetRoot('packages/app');
const templateDir = paths.resolveOwn('templates/default-plugin');
const tempDir = resolvePath(os.tmpdir(), answers.id);
const pluginDir = paths.resolveTargetRoot('plugins', answers.id);
const pluginDir = isMonoRepo
? paths.resolveTargetRoot('plugins', answers.id)
: paths.resolveTargetRoot(answers.id);
const ownerIds = parseOwnerIds(answers.owner);
const { version } = await fs.readJson(paths.resolveTargetRoot('lerna.json'));
const { version } = isMonoRepo
? await fs.readJson(paths.resolveTargetRoot('lerna.json'))
: { version: '0.1.0' };
Task.log();
Task.log('Creating the plugin...');
try {
Task.section('Checking if the plugin ID is available');
await checkExists(paths.targetRoot, answers.id);
await checkExists(pluginDir);
Task.section('Creating a temporary plugin directory');
await createTemporaryPluginFolder(tempDir);
@@ -244,6 +256,9 @@ export default async () => {
...answers,
version,
backstageVersion,
name,
privatePackage,
npmRegistry,
});
Task.section('Moving to final location');
@@ -254,10 +269,10 @@ export default async () => {
if (await fs.pathExists(appPackage)) {
Task.section('Adding plugin as dependency in app');
await addPluginDependencyToApp(paths.targetRoot, answers.id, version);
await addPluginDependencyToApp(paths.targetRoot, name, version);
Task.section('Import plugin in app');
await addPluginToApp(paths.targetRoot, answers.id);
await addPluginToApp(paths.targetRoot, answers.id, name);
}
if (ownerIds && ownerIds.length) {
@@ -269,11 +284,7 @@ export default async () => {
}
Task.log();
Task.log(
`🥇 Successfully created ${chalk.cyan(
`@backstage/plugin-${answers.id}`,
)}`,
);
Task.log(`🥇 Successfully created ${chalk.cyan(`${name}`)}`);
Task.log();
Task.exit();
} catch (error) {
+3
View File
@@ -62,6 +62,9 @@ export function registerCommands(program: CommanderStatic) {
program
.command('create-plugin')
.description('Creates a new plugin in the current repository')
.option('--scope <scope>', 'NPM scope')
.option('--npm-registry <URL>', 'NPM registry URL')
.option('--no-private', 'Public NPM Package')
.action(
lazy(() => import('./create-plugin/createPlugin').then(m => m.default)),
);
+14 -4
View File
@@ -30,6 +30,9 @@ import { version as backstageVersion } from '../../lib/version';
export type PluginData = {
id: string;
name: string;
privatePackage: string;
version: string;
npmRegistry: string;
};
const fileHandlers = [
@@ -62,11 +65,8 @@ export default async (cmd: Command) => {
promptFunc = yesPromptFunc;
}
const { version } = await fs.readJson(paths.resolveTargetRoot('lerna.json'));
const data = await readPluginData();
const templateFiles = await diffTemplateFiles('default-plugin', {
version,
backstageVersion,
...data,
});
@@ -77,9 +77,19 @@ export default async (cmd: Command) => {
// Reads templating data from the existing plugin
async function readPluginData(): Promise<PluginData> {
let name: string;
let privatePackage: string;
let version: string;
let npmRegistry: string;
try {
const pkg = require(paths.resolveTarget('package.json'));
name = pkg.name;
privatePackage = pkg.private;
version = pkg.version;
const scope = name.split('/')[0];
if (`${scope}:registry` in pkg.publishConfig) {
const registryURL = pkg.publishConfig[`${scope}:registry`];
npmRegistry = `"${scope}:registry" : "${registryURL}"`;
} else npmRegistry = '';
} catch (error) {
throw new Error(`Failed to read target package, ${error}`);
}
@@ -96,5 +106,5 @@ async function readPluginData(): Promise<PluginData> {
const id = pluginIdMatch[1];
return { id, name };
return { id, name, privatePackage, version, npmRegistry };
}
@@ -1,11 +1,14 @@
{
"name": "@backstage/plugin-{{id}}",
"name": "{{name}}",
"version": "{{version}}",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
{{#if privatePackage}} "private": {{privatePackage}},
{{/if}}
"publishConfig": {
{{#if npmRegistry}} "registry": "{{npmRegistry}}",
{{/if}}
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
@@ -46,9 +46,9 @@ export type IdentityApi = {
// TODO: getProfile(): Promise<Profile> - We want this to be async when added, but needs more work.
/**
* Log out the current user
* Sign out the current user
*/
logout(): Promise<void>;
signOut(): Promise<void>;
};
export const identityApiRef = createApiRef<IdentityApi>({
+24 -22
View File
@@ -90,11 +90,6 @@ export type OAuthApi = {
scope?: OAuthScope,
options?: AuthRequestOptions,
): Promise<string>;
/**
* Log out the user's session. This will reload the page.
*/
logout(): Promise<void>;
};
/**
@@ -114,11 +109,6 @@ export type OpenIdConnectApi = {
* The returned promise can be rejected, but only if the user rejects the login request.
*/
getIdToken(options?: AuthRequestOptions): Promise<string>;
/**
* Log out the user's session. This will reload the page.
*/
logout(): Promise<void>;
};
/**
@@ -187,7 +177,7 @@ export type ProfileInfo = {
};
/**
* Session state values passed to subscribers of the SessionStateApi.
* Session state values passed to subscribers of the SessionApi.
*/
export enum SessionState {
SignedIn = 'SignedIn',
@@ -195,10 +185,22 @@ export enum SessionState {
}
/**
* This API provides access to an sessionState$ observable which provides an update when the
* user performs a sign in or sign out from an auth provider.
* The SessionApi provides basic controls for any auth provider that is tied to a persistent session.
*/
export type SessionStateApi = {
export type SessionApi = {
/**
* Sign in with a minimum set of permissions.
*/
signIn(): Promise<void>;
/**
* Sign out from the current session. This will reload the page.
*/
signOut(): Promise<void>;
/**
* Observe the current state of the auth session. Emits the current state on subscription.
*/
sessionState$(): Observable<SessionState>;
};
@@ -215,7 +217,7 @@ export const googleAuthApiRef = createApiRef<
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionStateApi
SessionApi
>({
id: 'core.auth.google',
description: 'Provides authentication towards Google APIs and identities',
@@ -228,7 +230,7 @@ export const googleAuthApiRef = createApiRef<
* for a full list of supported scopes.
*/
export const githubAuthApiRef = createApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>({
id: 'core.auth.github',
description: 'Provides authentication towards GitHub APIs',
@@ -245,7 +247,7 @@ export const oktaAuthApiRef = createApiRef<
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionStateApi
SessionApi
>({
id: 'core.auth.okta',
description: 'Provides authentication towards Okta APIs',
@@ -258,7 +260,7 @@ export const oktaAuthApiRef = createApiRef<
* for a full list of supported scopes.
*/
export const gitlabAuthApiRef = createApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>({
id: 'core.auth.gitlab',
description: 'Provides authentication towards GitLab APIs',
@@ -271,7 +273,7 @@ export const gitlabAuthApiRef = createApiRef<
* for a full list of supported scopes.
*/
export const auth0AuthApiRef = createApiRef<
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
>({
id: 'core.auth.auth0',
description: 'Provides authentication towards Auth0 APIs',
@@ -289,7 +291,7 @@ export const microsoftAuthApiRef = createApiRef<
OpenIdConnectApi &
ProfileInfoApi &
BackstageIdentityApi &
SessionStateApi
SessionApi
>({
id: 'core.auth.microsoft',
description: 'Provides authentication towards Microsoft APIs and identities',
@@ -302,8 +304,8 @@ export const oauth2ApiRef = createApiRef<
OAuthApi &
OpenIdConnectApi &
ProfileInfoApi &
SessionStateApi &
BackstageIdentityApi
BackstageIdentityApi &
SessionApi
>({
id: 'core.auth.oauth2',
description: 'Example of how to use oauth2 custom provider',
@@ -19,7 +19,7 @@ import { DefaultAuthConnector } from '../../../../lib/AuthConnector';
import { GithubSession } from './types';
import {
OAuthApi,
SessionStateApi,
SessionApi,
SessionState,
ProfileInfo,
BackstageIdentity,
@@ -61,7 +61,7 @@ const DEFAULT_PROVIDER = {
icon: GithubIcon,
};
class GithubAuth implements OAuthApi, SessionStateApi {
class GithubAuth implements OAuthApi, SessionApi {
static create({
discoveryApi,
environment = 'development',
@@ -102,12 +102,20 @@ class GithubAuth implements OAuthApi, SessionStateApi {
return new GithubAuth(authSessionStore);
}
constructor(private readonly sessionManager: SessionManager<GithubSession>) {}
async signIn() {
await this.getAccessToken();
}
async signOut() {
await this.sessionManager.removeSession();
}
sessionState$(): Observable<SessionState> {
return this.sessionManager.sessionState$();
}
constructor(private readonly sessionManager: SessionManager<GithubSession>) {}
async getAccessToken(scope?: string, options?: AuthRequestOptions) {
const session = await this.sessionManager.getSession({
...options,
@@ -128,10 +136,6 @@ class GithubAuth implements OAuthApi, SessionStateApi {
return session?.profile;
}
async logout() {
await this.sessionManager.removeSession();
}
static normalizeScope(scope?: string): Set<string> {
if (!scope) {
return new Set();
@@ -32,7 +32,7 @@ import {
ProfileInfo,
ProfileInfoApi,
SessionState,
SessionStateApi,
SessionApi,
BackstageIdentityApi,
} from '../../../definitions/auth';
import { OAuth2Session } from './types';
@@ -75,7 +75,7 @@ class OAuth2
OpenIdConnectApi,
ProfileInfoApi,
BackstageIdentityApi,
SessionStateApi {
SessionApi {
static create({
discoveryApi,
environment = 'development',
@@ -129,6 +129,14 @@ class OAuth2
this.scopeTransform = options.scopeTransform;
}
async signIn() {
await this.getAccessToken();
}
async signOut() {
await this.sessionManager.removeSession();
}
sessionState$(): Observable<SessionState> {
return this.sessionManager.sessionState$();
}
@@ -150,10 +158,6 @@ class OAuth2
return session?.providerInfo.idToken ?? '';
}
async logout() {
await this.sessionManager.removeSession();
}
async getBackstageIdentity(
options: AuthRequestOptions = {},
): Promise<BackstageIdentity | undefined> {
+5 -5
View File
@@ -26,7 +26,7 @@ export class AppIdentity implements IdentityApi {
private userId?: string;
private profile?: ProfileInfo;
private idTokenFunc?: () => Promise<string>;
private logoutFunc?: () => Promise<void>;
private signOutFunc?: () => Promise<void>;
getUserId(): string {
if (!this.hasIdentity) {
@@ -55,13 +55,13 @@ export class AppIdentity implements IdentityApi {
return this.idTokenFunc?.();
}
async logout(): Promise<void> {
async signOut(): Promise<void> {
if (!this.hasIdentity) {
throw new Error(
'Tried to access IdentityApi logoutFunc before app was loaded',
'Tried to access IdentityApi signOutFunc before app was loaded',
);
}
await this.logoutFunc?.();
await this.signOutFunc?.();
location.reload();
}
@@ -80,6 +80,6 @@ export class AppIdentity implements IdentityApi {
this.userId = result.userId;
this.profile = result.profile;
this.idTokenFunc = result.getIdToken;
this.logoutFunc = result.logout;
this.signOutFunc = result.signOut;
}
}
+3 -2
View File
@@ -38,10 +38,11 @@ export type SignInResult = {
* Function used to retrieve an ID token for the signed in user.
*/
getIdToken?: () => Promise<string>;
/**
* Logout handler that will be called if the user requests a logout.
* Sign out handler that will be called if the user requests to sign out.
*/
logout?: () => Promise<void>;
signOut?: () => Promise<void>;
};
export type SignInPageProps = {
+1 -1
View File
@@ -30,7 +30,7 @@
},
"dependencies": {
"@backstage/config": "^0.1.1-alpha.22",
"@backstage/core-api": "^0.1.1-alpha.22",
"@backstage/core-api": "0.1.1-alpha.22",
"@backstage/theme": "^0.1.1-alpha.22",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -37,6 +37,7 @@ const useStyles = makeStyles(theme => ({
},
},
header: {
display: 'inline-block',
padding: theme.spacing(2, 2, 2, 2.5),
},
headerTitle: {
@@ -202,7 +203,7 @@ export const InfoCard = ({
}}
title={title}
subheader={subheader}
style={{ display: 'inline-block', ...headerStyle }}
style={{ ...headerStyle }}
{...headerProps}
/>
<Divider />
@@ -25,7 +25,7 @@ import {
} from '@backstage/core-api';
import Star from '@material-ui/icons/Star';
import React from 'react';
import { OAuthProviderSettings, OIDCProviderSettings } from './Settings';
import { ProviderSettingsItem } from './Settings';
export const DefaultProviderSettings = () => {
const configApi = useApi(configApiRef);
@@ -35,42 +35,42 @@ export const DefaultProviderSettings = () => {
return (
<>
{providers.includes('google') && (
<OIDCProviderSettings
<ProviderSettingsItem
title="Google"
apiRef={googleAuthApiRef}
icon={Star}
/>
)}
{providers.includes('microsoft') && (
<OIDCProviderSettings
<ProviderSettingsItem
title="Microsoft"
apiRef={microsoftAuthApiRef}
icon={Star}
/>
)}
{providers.includes('github') && (
<OAuthProviderSettings
<ProviderSettingsItem
title="Github"
apiRef={githubAuthApiRef}
icon={Star}
/>
)}
{providers.includes('gitlab') && (
<OAuthProviderSettings
<ProviderSettingsItem
title="Gitlab"
apiRef={gitlabAuthApiRef}
icon={Star}
/>
)}
{providers.includes('okta') && (
<OIDCProviderSettings
<ProviderSettingsItem
title="Okta"
apiRef={oktaAuthApiRef}
icon={Star}
/>
)}
{providers.includes('oauth2') && (
<OIDCProviderSettings
<ProviderSettingsItem
title="YourOrg"
apiRef={oauth2ApiRef}
icon={Star}
@@ -1,80 +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 {
ApiRef,
OAuthApi,
SessionStateApi,
useApi,
Subscription,
IconComponent,
SessionState,
} from '@backstage/core-api';
import React, { FC, useState, useEffect } from 'react';
import { ProviderSettingsItem } from './ProviderSettingsItem';
type OAuthProviderSidebarProps = {
title: string;
icon: IconComponent;
apiRef: ApiRef<OAuthApi & SessionStateApi>;
};
export const OAuthProviderSettings: FC<OAuthProviderSidebarProps> = ({
title,
icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
let didCancel = false;
const checkSession = async () => {
const session = await api.getAccessToken('', { optional: true });
if (!didCancel) {
setSignedIn(!!session);
}
};
let subscription: Subscription;
const observeSession = () => {
subscription = api
.sessionState$()
.subscribe((sessionState: SessionState) => {
if (!didCancel) {
setSignedIn(sessionState === SessionState.SignedIn);
}
});
};
checkSession();
observeSession();
return () => {
didCancel = true;
subscription.unsubscribe();
};
}, [api]);
return (
<ProviderSettingsItem
title={title}
icon={icon}
signedIn={signedIn}
api={api}
signInHandler={() => api.getAccessToken()}
/>
);
};
@@ -1,81 +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 {
ApiRef,
OpenIdConnectApi,
SessionStateApi,
useApi,
Subscription,
IconComponent,
SessionState,
} from '@backstage/core-api';
import React, { FC, useState, useEffect } from 'react';
import { ProviderSettingsItem } from './ProviderSettingsItem';
export type OIDCProviderSidebarProps = {
title: string;
icon: IconComponent;
apiRef: ApiRef<OpenIdConnectApi & SessionStateApi>;
};
export const OIDCProviderSettings: FC<OIDCProviderSidebarProps> = ({
title,
icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
let didCancel = false;
const checkSession = async () => {
const session = await api.getIdToken({ optional: true });
if (!didCancel) {
setSignedIn(!!session);
}
};
let subscription: Subscription;
const observeSession = () => {
subscription = api
.sessionState$()
.subscribe((sessionState: SessionState) => {
if (!didCancel) {
setSignedIn(sessionState === SessionState.SignedIn);
}
});
};
checkSession();
observeSession();
return () => {
didCancel = true;
subscription.unsubscribe();
};
}, [api]);
return (
<ProviderSettingsItem
title={title}
icon={icon}
signedIn={signedIn}
api={api}
signInHandler={() => api.getIdToken()}
/>
);
};
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React from 'react';
import { IconComponent, OAuthApi, OpenIdConnectApi } from '@backstage/core-api';
import React, { FC, useState, useEffect } from 'react';
import {
ListItem,
ListItemIcon,
@@ -25,42 +24,67 @@ import {
} from '@material-ui/core';
import PowerButton from '@material-ui/icons/PowerSettingsNew';
import { ToggleButton } from '@material-ui/lab';
import {
ApiRef,
SessionApi,
useApi,
IconComponent,
SessionState,
} from '@backstage/core-api';
type Props = {
type OAuthProviderSidebarProps = {
title: string;
icon: IconComponent;
signedIn: boolean;
api: OAuthApi | OpenIdConnectApi;
signInHandler: Function;
apiRef: ApiRef<SessionApi>;
};
export const ProviderSettingsItem = ({
export const ProviderSettingsItem: FC<OAuthProviderSidebarProps> = ({
title,
icon: Icon,
signedIn,
api,
signInHandler,
}: Props) => (
<ListItem>
<ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText primary={title} />
<ListItemSecondaryAction>
<ToggleButton
size="small"
value={title}
selected={signedIn}
onChange={() => (signedIn ? api.logout() : signInHandler())}
>
<Tooltip
placement="top"
arrow
title={signedIn ? `Sign out from ${title}` : `Sign in to ${title}`}
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
let didCancel = false;
const subscription = api
.sessionState$()
.subscribe((sessionState: SessionState) => {
if (!didCancel) {
setSignedIn(sessionState === SessionState.SignedIn);
}
});
return () => {
didCancel = true;
subscription.unsubscribe();
};
}, [api]);
return (
<ListItem>
<ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText primary={title} />
<ListItemSecondaryAction>
<ToggleButton
size="small"
value={title}
selected={signedIn}
onChange={() => (signedIn ? api.signOut() : api.signIn())}
>
<PowerButton />
</Tooltip>
</ToggleButton>
</ListItemSecondaryAction>
</ListItem>
);
<Tooltip
placement="top"
arrow
title={signedIn ? `Sign out from ${title}` : `Sign in to ${title}`}
>
<PowerButton />
</Tooltip>
</ToggleButton>
</ListItemSecondaryAction>
</ListItem>
);
};
@@ -43,7 +43,7 @@ export const UserSettingsMenu = () => {
<MoreVertIcon />
</IconButton>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={() => identityApi.logout()}>
<MenuItem onClick={() => identityApi.signOut()}>
<ListItemIcon>
<SignOutIcon />
</ListItemIcon>
@@ -15,6 +15,4 @@
*/
export { ProviderSettingsItem } from './ProviderSettingsItem';
export { OAuthProviderSettings } from './OAuthProviderSettings';
export { OIDCProviderSettings } from './OIDCProviderSettings';
export { SidebarUserSettings } from './UserSettings';
@@ -23,7 +23,7 @@ import {
SidebarSearchField,
SidebarSpace,
SidebarUserSettings,
OAuthProviderSettings,
ProviderSettingsItem,
} from '.';
import HomeOutlinedIcon from '@material-ui/icons/HomeOutlined';
import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline';
@@ -60,7 +60,7 @@ export const SampleSidebar = () => (
<SidebarDivider />
<SidebarUserSettings
providerSettings={
<OAuthProviderSettings
<ProviderSettingsItem
title="Github"
apiRef={githubAuthApiRef}
icon={Star}
@@ -37,8 +37,8 @@ const Component: ProviderComponent = ({ onResult }) => {
profile: profile!,
getIdToken: () =>
auth0AuthApi.getBackstageIdentity().then(i => i!.idToken),
logout: async () => {
await auth0AuthApi.logout();
signOut: async () => {
await auth0AuthApi.signOut();
},
});
} catch (error) {
@@ -79,8 +79,8 @@ const loader: ProviderLoader = async apis => {
userId: identity.id,
profile: profile!,
getIdToken: () => auth0AuthApi.getBackstageIdentity().then(i => i!.idToken),
logout: async () => {
await auth0AuthApi.logout();
signOut: async () => {
await auth0AuthApi.signOut();
},
};
};
@@ -44,8 +44,8 @@ const Component: ProviderComponent = ({ config, onResult }) => {
getIdToken: () => {
return authApi.getBackstageIdentity().then(i => i!.idToken);
},
logout: async () => {
await authApi.logout();
signOut: async () => {
await authApi.signOut();
},
});
} catch (error) {
@@ -87,8 +87,8 @@ const loader: ProviderLoader = async (apis, apiRef) => {
userId: identity.id,
profile: profile!,
getIdToken: () => authApi.getBackstageIdentity().then(i => i!.idToken),
logout: async () => {
await authApi.logout();
signOut: async () => {
await authApi.signOut();
},
};
};
@@ -83,14 +83,14 @@ export const useSignInProviders = (
const apiHolder = useApiHolder();
const [loading, setLoading] = useState(true);
// This decorates the result with logout logic from this hook
// This decorates the result with sign out logic from this hook
const handleWrappedResult = useCallback(
(result: SignInResult) => {
onResult({
...result,
logout: async () => {
signOut: async () => {
localStorage.removeItem(PROVIDER_STORAGE_KEY);
await result.logout?.();
await result.signOut?.();
},
});
},
+3 -8
View File
@@ -20,19 +20,16 @@ import {
SignInResult,
ApiHolder,
ApiRef,
OAuthApi,
ProfileInfoApi,
BackstageIdentityApi,
SessionStateApi,
SessionApi,
} from '@backstage/core-api';
export type SignInConfig = {
id: string;
title: string;
message: string;
apiRef: ApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
>;
apiRef: ApiRef<ProfileInfoApi & BackstageIdentityApi & SessionApi>;
};
export type IdentityProviders = ('guest' | 'custom' | SignInConfig)[];
@@ -43,9 +40,7 @@ export type ProviderComponent = ComponentType<
export type ProviderLoader = (
apis: ApiHolder,
apiRef: ApiRef<
OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi
>,
apiRef: ApiRef<ProfileInfoApi & BackstageIdentityApi & SessionApi>,
) => Promise<SignInResult | undefined>;
export type SignInProvider = {
@@ -1,4 +1,3 @@
# Logs
logs
*.log
@@ -31,4 +30,4 @@ dist-types
site
# Local configuration files
*.local.yaml
*.local.yaml
@@ -16,7 +16,7 @@
"test:all": "lerna run test -- --coverage",
"lint": "lerna run lint --since origin/master --",
"lint:all": "lerna run lint --",
"create-plugin": "backstage-cli create-plugin",
"create-plugin": "backstage-cli create-plugin --scope backstage --no-private",
"remove-plugin": "backstage-cli remove-plugin"
},
"workspaces": {
@@ -10,7 +10,7 @@ import {
GitlabPublisher,
CreateReactAppTemplater,
Templaters,
RepoVisilityOptions,
RepoVisibilityOptions,
} from '@backstage/plugin-scaffolder-backend';
import { Octokit } from '@octokit/rest';
import { Gitlab } from '@gitbeaker/node';
@@ -42,7 +42,7 @@ export default async function createPlugin({
const githubToken = config.getString('scaffolder.github.token');
const repoVisibility = config.getString(
'scaffolder.github.visibility',
) as RepoVisilityOptions;
) as RepoVisibilityOptions;
const githubClient = new Octokit({ auth: githubToken });
const githubPublisher = new GithubPublisher({
+5 -1
View File
@@ -81,7 +81,11 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) {
const path = paths.resolveOwnRoot(pkgJsonPath);
const pkgTemplate = await fs.readFile(path, 'utf8');
const { dependencies = {}, devDependencies = {} } = JSON.parse(
handlebars.compile(pkgTemplate)({ version: '0.0.0' }),
handlebars.compile(pkgTemplate)({
version: '0.0.0',
privatePackage: true,
scopeName: '@backstage',
}),
);
Array<string>()
+1 -1
View File
@@ -36,7 +36,7 @@ builder.add(identityApiRef, {
getUserId: () => 'guest',
getProfile: () => ({ email: 'guest@example.com' }),
getIdToken: () => undefined,
logout: async () => {},
signOut: async () => {},
});
const oauthRequestApi = builder.add(
+1 -1
View File
@@ -18,7 +18,7 @@ FROM python:3.8-alpine
RUN apk update && apk --no-cache add gcc musl-dev openjdk11-jdk curl graphviz ttf-dejavu fontconfig
RUN curl -L http://sourceforge.net/projects/plantuml/files/plantuml.1.2020.16.jar/download > /opt/plantuml.jar
RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==0.0.7
RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==0.0.8
# Create script to call plantuml.jar from a location in path
@@ -3,10 +3,9 @@
!!! test
Testing somethin
Abbreviations:
Some text about MOCDOC
\*[MOCDOC]: Mock Documentation
This is a paragraph.
{: #test_id .test_class }
@@ -61,3 +60,40 @@ digraph G {
```
:bulb:
=== "JavaScript"
```javascript
import { test } from 'something';
const addThingToThing = (a, b) a + b;
```
=== "Java"
```java
public void function() {
test();
}
```
```java tab="java"
public void function() {
test();
}
```
```java tab="java 2"
public void function() {
test();
}
```
```javascript
import { test } from 'something';
const addThingToThing = (a, b) a + b;
```
<!-- prettier-ignore -->
*[MOCDOC]: Mock Documentation
@@ -48,8 +48,72 @@ python -m black src/
**Note:** This will write to all Python files in `src/` with the formatted code. If you would like to only check to see if it passes, simply append the `--check` flag.
## MkDocs plugins and extensions
The TechDocs Core MkDocs plugin comes with a set of extensions and plugins that mkdocs supports. Below you can find a list of all extensions and plugins that are included in the
TechDocs Core plugin:
Plugins:
- [search](https://www.mkdocs.org/user-guide/configuration/#search)
- [mkdocs-monorepo-plugin](https://github.com/spotify/mkdocs-monorepo-plugin)
Extensions:
- [admonition](https://squidfunk.github.io/mkdocs-material/reference/admonitions/#admonitions)
- [toc](https://python-markdown.github.io/extensions/toc/)
- [pymdown](https://facelessuser.github.io/pymdown-extensions/)
- caret
- critic
- details
- emoji
- superfences
- inlinehilite
- magiclink
- mark
- smartsymobls
- highlight
- extra
- tabbed
- tasklist
- tilde
- [markdown_inline_graphviz](https://pypi.org/project/markdown-inline-graphviz/)
- [plantuml_markdown](https://pypi.org/project/plantuml-markdown/)
## Changelog
### 0.0.8
- Superfences and Codehilite doesn't work very well together (squidfunk/mkdocs-material#1604) so therefore the codehilite extension is replaced by pymdownx.highlight
* Uses pymdownx extensions v.7.1 instead of 8.0.0 to allow legacy_tab_classes config. This makes the techdocs core plugin compatible with the usage of tabs for grouping markdown with the following syntax:
````
```java tab="java 2"
public void function() {
....
}
```
````
as well as the new
````
=== "Java"
```java
public void function() {
....
}
```
````
The pymdownx extension will be bumped too 8.0.0 in the near future.
- pymdownx.tabbed is added to support tabs to group markdown content, such as codeblocks.
- "PyMdown Extensions includes three extensions that are meant to replace their counterpart in the default Python Markdown extensions." Therefore some extensions has been taken away in this version that comes by default from pymdownx.extra which is added now (https://facelessuser.github.io/pymdown-extensions/usage_notes/#incompatible-extensions)
### 0.0.7
- Fix an issue with configuration of emoji support
@@ -7,7 +7,7 @@ mkdocs-monorepo-plugin==0.4.5
plantuml-markdown==3.1.2
markdown_inline_graphviz_extension==1.1
pygments==2.6.1
pymdown-extensions==8.0.0
pymdown-extensions==7.1
# The linter using for Python
# Note: This requires Python 3.6+ to run, but can format Python 2 code too.
@@ -17,38 +17,34 @@ from setuptools import setup, find_packages
setup(
name='mkdocs-techdocs-core',
version='0.0.7',
description='A Mkdocs package that contains TechDocs defaults',
long_description='',
keywords='mkdocs',
url='https://github.com/spotify/backstage',
author='TechDocs Core',
author_email='pulp-fiction@spotify.com',
license='Apache-2.0',
python_requires='>=3.7',
name="mkdocs-techdocs-core",
version="0.0.8",
description="A Mkdocs package that contains TechDocs defaults",
long_description="",
keywords="mkdocs",
url="https://github.com/spotify/backstage",
author="TechDocs Core",
author_email="pulp-fiction@spotify.com",
license="Apache-2.0",
python_requires=">=3.7",
install_requires=[
'mkdocs>=1.1.2',
'mkdocs-material==5.3.2',
'mkdocs-monorepo-plugin==0.4.5',
'plantuml-markdown==3.1.2',
'markdown_inline_graphviz_extension==1.1',
'pygments==2.6.1',
'pymdown-extensions==8.0.0'
"mkdocs>=1.1.2",
"mkdocs-material==5.3.2",
"mkdocs-monorepo-plugin==0.4.5",
"plantuml-markdown==3.1.2",
"markdown_inline_graphviz_extension==1.1",
"pygments==2.6.1",
"pymdown-extensions==7.1",
],
classifiers=[
'Development Status :: 1 - Planning',
'Intended Audience :: Developers',
'Intended Audience :: Information Technology',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.7'
"Development Status :: 1 - Planning",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.7",
],
packages=find_packages(),
entry_points={
'mkdocs.plugins': [
'techdocs-core = src.core:TechDocsCore'
]
}
entry_points={"mkdocs.plugins": ["techdocs-core = src.core:TechDocsCore"]},
)
@@ -14,7 +14,7 @@
* limitations under the License.
"""
from mkdocs.plugins import BasePlugin, PluginCollection
from mkdocs.plugins import BasePlugin
from mkdocs.theme import Theme
from mkdocs.contrib.search import SearchPlugin
from mkdocs_monorepo_plugin.plugin import MonorepoPlugin
@@ -54,25 +54,11 @@ class TechDocsCore(BasePlugin):
# Markdown Extensions
config["markdown_extensions"].append("admonition")
config["markdown_extensions"].append("abbr")
config["markdown_extensions"].append("attr_list")
config["markdown_extensions"].append("def_list")
config["markdown_extensions"].append("codehilite")
config["mdx_configs"]["codehilite"] = {
"linenums": True,
"guess_lang": False,
"pygments_style": "friendly",
}
config["markdown_extensions"].append("toc")
config["mdx_configs"]["toc"] = {
"permalink": True,
}
config["markdown_extensions"].append("footnotes")
config["markdown_extensions"].append("markdown.extensions.tables")
config["markdown_extensions"].append("pymdownx.betterem")
config["mdx_configs"]["pymdownx.betterem"] = {
"smart_enable": "all",
}
config["markdown_extensions"].append("pymdownx.caret")
config["markdown_extensions"].append("pymdownx.critic")
config["markdown_extensions"].append("pymdownx.details")
@@ -83,6 +69,18 @@ class TechDocsCore(BasePlugin):
config["markdown_extensions"].append("pymdownx.mark")
config["markdown_extensions"].append("pymdownx.smartsymbols")
config["markdown_extensions"].append("pymdownx.superfences")
config["mdx_configs"]["pymdownx.superfences"] = {
"legacy_tab_classes": True,
}
config["markdown_extensions"].append("pymdownx.highlight")
config["mdx_configs"]["pymdownx.highlight"] = {
"linenums": True,
}
config["markdown_extensions"].append("pymdownx.extra")
config["mdx_configs"]["pymdownx.betterem"] = {
"smart_enable": "all",
}
config["markdown_extensions"].append("pymdownx.tabbed")
config["markdown_extensions"].append("pymdownx.tasklist")
config["mdx_configs"]["pymdownx.tasklist"] = {
"custom_checkbox": True,