adding Confluence search functionality upstream v1
Signed-off-by: su-gupta <sugupta@brex.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Search
|
||||
|
||||
Contributions/extensions to the Search plugin
|
||||
@@ -0,0 +1,87 @@
|
||||
import { DocumentCollator } from '@backstage/search-common';
|
||||
import fetch from 'cross-fetch'
|
||||
|
||||
export class ConfluenceCollator implements DocumentCollator {
|
||||
public readonly type: string = 'confluence';
|
||||
|
||||
async execute() {
|
||||
|
||||
const ConfluenceUrlBase = 'https://{CONFLUENCE-ORG-NAME}.atlassian.net/wiki/rest/api'
|
||||
|
||||
async function getConfluenceData(requestUrl: string) {
|
||||
var emptyJson = {}
|
||||
try {
|
||||
const res = await fetch(requestUrl, {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'Authorization': `Basic ${process.env.CONFLUENCE_TOKEN}`
|
||||
},
|
||||
});
|
||||
if (res.ok) {
|
||||
return await res.json();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return emptyJson
|
||||
}
|
||||
|
||||
async function getSpaces(): Promise<string[]> {
|
||||
const data = await getConfluenceData(`${ConfluenceUrlBase}/space?&limit=1000&type=global&status=current`);
|
||||
let spacesList = []
|
||||
if (data["results"]) {
|
||||
const results = data["results"];
|
||||
for (const result of results) {
|
||||
spacesList.push(result["key"])
|
||||
}
|
||||
}
|
||||
return spacesList
|
||||
}
|
||||
|
||||
async function getDocumentsFromSpaces(spaces: string[]): Promise<string[]> {
|
||||
let documentsList = []
|
||||
for (var space of spaces) {
|
||||
let next = true
|
||||
let requestUrl = `${ConfluenceUrlBase}/content?limit=1000&status=current&spaceKey=${space}`
|
||||
while (next) {
|
||||
const data = await getConfluenceData(requestUrl)
|
||||
if (data["results"]) {
|
||||
const results = data["results"]
|
||||
for (const result of results) {
|
||||
documentsList.push(result["_links"]["self"])
|
||||
}
|
||||
if (data["_links"]["next"]) {
|
||||
requestUrl = data["_links"]["base"] + data["_links"]["next"]
|
||||
} else {
|
||||
next = false
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return documentsList
|
||||
}
|
||||
|
||||
async function getDocumentInfo(documents: string[]) {
|
||||
let documentInfo = []
|
||||
for (var documentUrl of documents) {
|
||||
const data = await getConfluenceData(documentUrl + '?expand=body.storage')
|
||||
if (data["status"] && data["status"]=="current") {
|
||||
const documentMetaData = {
|
||||
title: data["title"],
|
||||
text: data["body"]["storage"]["value"],
|
||||
location: data["_links"]["base"] + data["_links"]["webui"],
|
||||
}
|
||||
documentInfo.push(documentMetaData)
|
||||
}
|
||||
}
|
||||
return documentInfo
|
||||
}
|
||||
|
||||
const spacesList = await getSpaces();
|
||||
const documentsList = await getDocumentsFromSpaces(spacesList);
|
||||
const documentMetaDataList = await getDocumentInfo(documentsList);
|
||||
return documentMetaDataList
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { Link } from '@backstage/core-components';
|
||||
import { IndexableDocument } from '@backstage/search-common';
|
||||
import {
|
||||
Divider,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
} from '@material-ui/core';
|
||||
|
||||
type Props = {
|
||||
result: IndexableDocument;
|
||||
};
|
||||
|
||||
export const ConfluenceResultListItem = ({ result }: Props) => {
|
||||
// Remove html tags from document text before displaying
|
||||
const chars = [];
|
||||
let isTag = false;
|
||||
for (const c of result.text.substring(0, 500)) {
|
||||
if (c === "<") {
|
||||
isTag = true;
|
||||
continue;
|
||||
}
|
||||
if (c === ">") {
|
||||
isTag = false;
|
||||
chars.push(" ")
|
||||
continue;
|
||||
}
|
||||
if (!isTag) {
|
||||
chars.push(c);
|
||||
}
|
||||
}
|
||||
const excerpt = chars.join("").substring(0, 80) + (result.text.length > 80 ? "..." : "");
|
||||
|
||||
return (
|
||||
<Link to={result.location}>
|
||||
<ListItem alignItems="center">
|
||||
<ListItemIcon>
|
||||
<img width="20" height="20" src="https://cdn.worldvectorlogo.com/logos/confluence-1.svg" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primaryTypographyProps={{ variant: 'h6' }}
|
||||
primary={result.title}
|
||||
secondary={excerpt}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
# Confluence
|
||||
|
||||
These files help you add Confluence as a source to the Backstage Search plugin.
|
||||
To do so, add both files in this directory under the packages/backend/src/plugins/search/ pathway in your Backstage app as TypeScript files.
|
||||
Then, update your packages/app/src/components/search/SearchPage.tsx and packages/backend/src/plugins/search.ts
|
||||
to include the new Search source.
|
||||
Reference in New Issue
Block a user