[TechDocs] TechDocs custom homepage with user owned filter support (#5994)
* add emptystate to docs card grid Signed-off-by: Emma Indal <emma.indahl@gmail.com> * adjust filterpredicate to accept a string, adjust custom home page based on owned documents filter Signed-off-by: Emma Indal <emma.indahl@gmail.com> * update the techdocs home page to use the new ownedByUser filter Signed-off-by: Emma Indal <emma.indahl@gmail.com> * fix tests Signed-off-by: Emma Indal <emma.indahl@gmail.com> * use useOwnUser hook from catalog react plugin and delete duplicate one Signed-off-by: Emma Indal <emma.indahl@gmail.com> * add changeset Signed-off-by: Emma Indal <emma.indahl@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Adding support for user owned document filter for TechDocs custom Homepage
|
||||
@@ -21,12 +21,32 @@ import { screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { TechDocsCustomHome, PanelType } from './TechDocsCustomHome';
|
||||
|
||||
describe('TechDocsCustomHome', () => {
|
||||
const catalogApi: Partial<CatalogApi> = {
|
||||
getEntities: async () => ({ items: [] }),
|
||||
jest.mock('@backstage/plugin-catalog-react', () => {
|
||||
const actual = jest.requireActual('@backstage/plugin-catalog-react');
|
||||
return {
|
||||
...actual,
|
||||
useOwnUser: () => 'test-user',
|
||||
};
|
||||
});
|
||||
|
||||
const apiRegistry = ApiRegistry.with(catalogApiRef, catalogApi);
|
||||
const mockCatalogApi = {
|
||||
getEntityByName: jest.fn(),
|
||||
getEntities: async () => ({
|
||||
items: [
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'owned',
|
||||
namespace: 'default',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
} as Partial<CatalogApi>;
|
||||
|
||||
describe('TechDocsCustomHome', () => {
|
||||
const apiRegistry = ApiRegistry.with(catalogApiRef, mockCatalogApi);
|
||||
|
||||
it('should render a TechDocs home page', async () => {
|
||||
const tabsConfig = [
|
||||
|
||||
@@ -18,7 +18,12 @@ import React, { useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { CSSProperties } from '@material-ui/styles';
|
||||
import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
catalogApiRef,
|
||||
CatalogApi,
|
||||
isOwnerOf,
|
||||
useOwnUser,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
CodeSnippet,
|
||||
@@ -49,7 +54,7 @@ export interface PanelConfig {
|
||||
description: string;
|
||||
panelType: PanelType;
|
||||
panelCSS?: CSSProperties;
|
||||
filterPredicate: (entity: Entity) => boolean;
|
||||
filterPredicate: ((entity: Entity) => boolean) | string;
|
||||
}
|
||||
|
||||
export interface TabConfig {
|
||||
@@ -75,8 +80,24 @@ const CustomPanel = ({
|
||||
},
|
||||
});
|
||||
const classes = useStyles();
|
||||
const { value: user } = useOwnUser();
|
||||
|
||||
const Panel = panels[config.panelType];
|
||||
const shownEntities = entities.filter(config.filterPredicate);
|
||||
|
||||
const shownEntities = entities.filter(entity => {
|
||||
if (config.filterPredicate === 'ownedByUser') {
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return isOwnerOf(user, entity);
|
||||
}
|
||||
|
||||
return (
|
||||
typeof config.filterPredicate === 'function' &&
|
||||
config.filterPredicate(entity)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContentHeader title={config.title} description={config.description}>
|
||||
|
||||
@@ -27,45 +27,41 @@ import { screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { TechDocsHome } from './TechDocsHome';
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
useOwnUser: () => {
|
||||
return {
|
||||
value: {
|
||||
jest.mock('@backstage/plugin-catalog-react', () => {
|
||||
const actual = jest.requireActual('@backstage/plugin-catalog-react');
|
||||
return {
|
||||
...actual,
|
||||
useOwnUser: () => 'test-user',
|
||||
};
|
||||
});
|
||||
|
||||
const mockCatalogApi = {
|
||||
getEntityByName: jest.fn(),
|
||||
getEntities: async () => ({
|
||||
items: [
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
name: 'owned',
|
||||
namespace: 'default',
|
||||
},
|
||||
relations: [
|
||||
{
|
||||
target: {
|
||||
kind: 'TestKind',
|
||||
name: 'testName',
|
||||
},
|
||||
type: 'ownerOf',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
}));
|
||||
],
|
||||
}),
|
||||
} as Partial<CatalogApi>;
|
||||
|
||||
describe('TechDocs Home', () => {
|
||||
const catalogApi: Partial<CatalogApi> = {
|
||||
getEntities: async () => ({ items: [] }),
|
||||
};
|
||||
|
||||
const configApi: ConfigApi = new ConfigReader({
|
||||
organization: {
|
||||
name: 'My Company',
|
||||
},
|
||||
});
|
||||
|
||||
const apiRegistry = ApiRegistry.with(catalogApiRef, catalogApi).with(
|
||||
configApiRef,
|
||||
configApi,
|
||||
);
|
||||
const apiRegistry = ApiRegistry.from([
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[configApiRef, configApi],
|
||||
]);
|
||||
|
||||
it('should render a TechDocs home page', async () => {
|
||||
await renderInTestApp(
|
||||
|
||||
@@ -15,14 +15,9 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useOwnUser } from '../hooks';
|
||||
import { isOwnerOf } from '@backstage/plugin-catalog-react';
|
||||
import { PanelType, TechDocsCustomHome } from './TechDocsCustomHome';
|
||||
|
||||
export const TechDocsHome = () => {
|
||||
const { value: user } = useOwnUser();
|
||||
|
||||
const tabsConfig = [
|
||||
{
|
||||
label: 'Overview',
|
||||
@@ -34,6 +29,13 @@ export const TechDocsHome = () => {
|
||||
panelType: 'DocsCardGrid' as PanelType,
|
||||
filterPredicate: () => true,
|
||||
},
|
||||
// uncomment this if you would like to have a secondary panel with owned documents
|
||||
// {
|
||||
// title: 'Owned',
|
||||
// description: 'Explore your owned internal documentation.',
|
||||
// panelType: 'DocsCardGrid' as PanelType,
|
||||
// filterPredicate: 'ownedByUser',
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -43,12 +45,8 @@ export const TechDocsHome = () => {
|
||||
title: 'Owned documents',
|
||||
description: 'Access your documentation.',
|
||||
panelType: 'DocsTable' as PanelType,
|
||||
filterPredicate: (entity: Entity) => {
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return isOwnerOf(user, entity);
|
||||
},
|
||||
// ownedByUser filters out entities owned by signed in user
|
||||
filterPredicate: 'ownedByUser',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,17 +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.
|
||||
*/
|
||||
|
||||
export { useOwnUser } from './useOwnUser';
|
||||
@@ -1,41 +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 { UserEntity } from '@backstage/catalog-model';
|
||||
import { identityApiRef, useApi } from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
|
||||
/**
|
||||
* Get the catalog User entity (if any) that matches the logged-in user.
|
||||
*/
|
||||
export function useOwnUser(): AsyncState<UserEntity | undefined> {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const identityApi = useApi(identityApiRef);
|
||||
|
||||
// TODO: get the full entity (or at least the full entity name) from the
|
||||
// identityApi
|
||||
return useAsync(
|
||||
() =>
|
||||
catalogApi.getEntityByName({
|
||||
kind: 'User',
|
||||
namespace: 'default',
|
||||
name: identityApi.getUserId(),
|
||||
}) as Promise<UserEntity | undefined>,
|
||||
[catalogApi, identityApi],
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user