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 0000000..27a5da0 Binary files /dev/null and b/app/static/data_source_icons/confluence_cloud.png differ 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