From 2e4fdf5a054bac0ccb39b1978afa2e5d02850525 Mon Sep 17 00:00:00 2001 From: Bryan Pakulski Date: Wed, 22 Mar 2023 16:21:24 +1100 Subject: [PATCH] Add customer data source for confluence cloud --- app/data_sources/confluence_cloud.py | 37 ++++++++ .../data_source_icons/confluence_cloud.png | Bin 0 -> 9582 bytes ui/src/components/data-source-panel.tsx | 87 ++++++++++-------- 3 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 app/data_sources/confluence_cloud.py create mode 100644 app/static/data_source_icons/confluence_cloud.png diff --git a/app/data_sources/confluence_cloud.py b/app/data_sources/confluence_cloud.py new file mode 100644 index 0000000..4a2eea0 --- /dev/null +++ b/app/data_sources/confluence_cloud.py @@ -0,0 +1,37 @@ +from typing import List, Dict +from data_sources.confluence import ConfluenceDataSource + +from atlassian import Confluence + +from data_source_api.base_data_source import BaseDataSource, ConfigField, HTMLInputType +from data_source_api.exception import InvalidDataSourceConfig +from pydantic import BaseModel + +class ConfluenceCloudConfig(BaseModel): + url: str + token: str + username: str + +class ConfluenceCloudDataSource(ConfluenceDataSource): + + @staticmethod + def get_config_fields() -> List[ConfigField]: + return [ + ConfigField(label="Confluence URL", name="url", placeholder="https://example.confluence.com"), + ConfigField(label="Personal Access Token", name="token", input_type=HTMLInputType.PASSWORD), + ConfigField(label="Username", name="username", placeholder="example.user@email.com") + ] + + @staticmethod + def validate_config(config: Dict) -> None: + try: + parsed_config = ConfluenceCloudConfig(**config) + confluence = Confluence(url=parsed_config.url, username=parsed_config.username, password=parsed_config.token, cloud=True) + ConfluenceCloudDataSource.list_spaces(confluence=confluence) + except Exception as e: + raise InvalidDataSourceConfig from e + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + confluence_config = ConfluenceCloudConfig(**self._config) + self._confluence = Confluence(url=confluence_config.url, username=confluence_config.username, password=confluence_config.token, verify_ssl=False, cloud=True) diff --git a/app/static/data_source_icons/confluence_cloud.png b/app/static/data_source_icons/confluence_cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..27a5da07bd1402f48dc9cc39d26225505dd85242 GIT binary patch literal 9582 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 J)#~=K{|7a%wvPY+ literal 0 HcmV?d00001 diff --git a/ui/src/components/data-source-panel.tsx b/ui/src/components/data-source-panel.tsx index 7e5e03d..e9ff67a 100644 --- a/ui/src/components/data-source-panel.tsx +++ b/ui/src/components/data-source-panel.tsx @@ -28,6 +28,12 @@ export interface ConfluenceConfig { token: string; } +export interface ConfluenceCloudConfig { + url: string; + token: string; + username: string; +} + export interface SlackConfig { token: string; } @@ -99,7 +105,7 @@ export default class DataSourcePanel extends React.Component { - let dataSource = this.props.dataSourceTypesDict[key]; - if (!this.props.connectedDataSources.includes(dataSource.name)) { - return ( -
this.dataSourceToAddSelected(dataSource)} className="flex hover:text-[#9875d4] py-2 pl-5 pr-3 m-2 flex-row items-center justify-center bg-[#36323b] hover:border-[#9875d4] rounded-lg font-poppins leading-[28px] border-[#777777] border-b-[.5px] transition duration-300 ease-in-out"> - - {/*

Add

*/} -

{dataSource.display_name}

- -
- ) - } - return null; + Object.keys(this.props.dataSourceTypesDict).map((key) => { + let dataSource = this.props.dataSourceTypesDict[key]; + if (!this.props.connectedDataSources.includes(dataSource.name)) { + return ( +
this.dataSourceToAddSelected(dataSource)} className="flex hover:text-[#9875d4] py-2 pl-5 pr-3 m-2 flex-row items-center justify-center bg-[#36323b] hover:border-[#9875d4] rounded-lg font-poppins leading-[28px] border-[#777777] border-b-[.5px] transition duration-300 ease-in-out"> + + {/*

Add

*/} +

{dataSource.display_name}

+ +
+ ) + } + return null; - }) + }) } ) @@ -207,7 +213,16 @@ export default class DataSourcePanel extends React.Component - 1. {'Go to your Confluene -> top-right profile picture -> Settings'} + 1. {'Go to your Confluence -> top-right profile picture -> Settings'} + 2. {'Personal Access Tokens -> Create token -> Name it'} + 3. {"Uncheck 'Automatic expiry', create and copy the token"} + + ) + } + { + this.state.selectedDataSource.value === 'confluence_cloud' && ( + + 1. {'Go to your Confluence -> top-right profile picture -> Settings'} 2. {'Personal Access Tokens -> Create token -> Name it'} 3. {"Uncheck 'Automatic expiry', create and copy the token"} @@ -270,26 +285,26 @@ export default class DataSourcePanel extends React.Component {/* for each field */} { - this.state.selectedDataSource.configFields.map((field, index) => { - if(field.input_type === 'text' || field.input_type === 'password') { - return ( -
-

{field.label}

- {field.value = event.target.value }} - className="w-96 h-10 rounded-lg bg-[#352C45] text-white p-2" - placeholder={field.placeholder}> -
- ) - } else if (field.input_type === 'textarea') { - return ( -
-

{field.label}

- -
- )} + this.state.selectedDataSource.configFields.map((field, index) => { + if(field.input_type === 'text' || field.input_type === 'password') { + return ( +
+

{field.label}

+ {field.value = event.target.value }} + className="w-96 h-10 rounded-lg bg-[#352C45] text-white p-2" + placeholder={field.placeholder}> +
+ ) + } else if (field.input_type === 'textarea') { + return ( +
+

{field.label}

+ +
+ )} return null; - }) + }) }
@@ -330,7 +345,7 @@ export default class DataSourcePanel extends React.Component { config[field.name] = field.value; }); - + let payload = { name: this.state.selectedDataSource.value, config: config