Skip to content

Commit e9b0b80

Browse files
authored
Merge pull request #189 from cloudblue/allow_web_for_hubs_plus_fixes
Allow web app for hub ext; other fixes
2 parents fd7bffe + 44f10d8 commit e9b0b80

File tree

17 files changed

+355
-302
lines changed

17 files changed

+355
-302
lines changed

connect/cli/plugins/project/commands.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(self, *args, **kwargs):
2525
assert self.conflict_with, "'conflict_with' parameter required"
2626

2727
help_str = kwargs.get("help", "")
28-
help_str += f' Option is mutually exclusive with {", ".join(self.conflict_with)}.'
28+
help_str += f' This option is mutually exclusive with {", ".join(self.conflict_with)}.'
2929
kwargs["help"] = help_str.strip()
3030
super(Mutex, self).__init__(*args, **kwargs)
3131

@@ -35,7 +35,7 @@ def handle_parse_result(self, ctx, opts, args):
3535
if mutex_opt in opts:
3636
if current_opt:
3737
raise click.UsageError(
38-
f'Illegal usage: {self.name} is mutually exclusive with {mutex_opt}.',
38+
f'Illegal usage: options {self.name} and {mutex_opt} are mutually exclusive.',
3939
)
4040
else:
4141
self.prompt = None
@@ -141,7 +141,7 @@ def grp_project_extension():
141141
default=None,
142142
type=click.Path(exists=False, file_okay=True, dir_okay=False),
143143
required=False,
144-
help='Directory or file where to entered save answers as json file.',
144+
help='Path to JSON file where to save wizard answers.',
145145
cls=Mutex,
146146
conflict_with=['load_answers'],
147147
)
@@ -150,7 +150,7 @@ def grp_project_extension():
150150
default=None,
151151
type=click.Path(exists=True, file_okay=True, dir_okay=False),
152152
required=False,
153-
help='Json file to load answers from.',
153+
help='Path to JSON file from where load wizard answers.',
154154
cls=Mutex,
155155
conflict_with=['save_answers'],
156156
)

connect/cli/plugins/project/extension/helpers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def bootstrap_extension_project( # noqa: CCR001
2929
console.secho('Bootstraping extension project...\n', fg='blue')
3030

3131
if save_answers and os.path.exists(save_answers):
32-
raise ClickException(f'Answers can not be saved, because {save_answers} already exists.')
32+
raise ClickException(f'Answers cannot be saved, because {save_answers} already exists.')
3333

3434
statuses_by_event = collections.defaultdict(lambda: collections.defaultdict())
3535

@@ -82,8 +82,10 @@ def bootstrap_extension_project( # noqa: CCR001
8282

8383
if save_answers:
8484
with open(save_answers, 'w') as fp:
85-
json.dump(answers, fp)
85+
json.dump(answers, fp, indent=2)
86+
console.echo()
8687
console.secho(f'Answers saved to {save_answers}', fg='green')
88+
console.echo()
8789

8890
exclude = []
8991

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
from typing import List, Optional
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) {% now 'utc', '%Y' %}, {{ author }}
4+
# All rights reserved.
5+
#
6+
from typing import {% if extension_type == 'multiaccount' %}List, {% endif %}Optional
27

38
from pydantic import BaseModel, validator
49

@@ -11,11 +16,9 @@ class Marketplace(BaseModel):
1116

1217
@validator('icon')
1318
def set_icon(cls, icon):
14-
return icon or (
15-
'https://unpkg.com/@cloudblueconnect'
16-
'/material-svg@latest/icons/google/language/baseline.svg'
17-
)
18-
19+
return icon or '/static/images/mkp.svg'
1920

21+
{% if extension_type == 'multiaccount' %}
2022
class Settings(BaseModel):
2123
marketplaces: List[Marketplace] = []
24+
{% endif -%}

connect/cli/plugins/project/extension/templates/bootstrap/${project_slug}/${package_name}/webapp.py.j2

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,36 @@
55
#
66
from typing import List
77

8-
from connect.client import {% if use_asyncio == 'y' %}Async{% endif %}ConnectClient, R
8+
from connect.client import {% if use_asyncio == 'y' %}Async{% endif %}ConnectClient{% if extension_type == 'multiaccount' %}, R{% endif %}
99
from connect.eaas.core.decorators import (
10+
{%- if webapp_supports_ui == 'y' %}
1011
account_settings_page,
1112
module_pages,
13+
{%- endif %}
1214
router,
1315
{%- if include_variables_example == 'y' %}
1416
variables,
1517
{%- endif %}
1618
web_app,
1719
)
1820
from connect.eaas.core.extension import WebApplicationBase
19-
{%- if use_asyncio == 'y' %}
21+
{%- if use_asyncio == 'y' and extension_type == 'multiaccount' %}
2022
from connect.eaas.core.inject.asynchronous import get_installation, get_installation_client
23+
{%- elif use_asyncio == 'y' and extension_type != 'multiaccount' %}
24+
from connect.eaas.core.inject.asynchronous import get_extension_client
2125
{%- endif %}
26+
{%- if extension_type == 'multiaccount' %}
2227
from connect.eaas.core.inject.common import get_call_context
2328
from connect.eaas.core.inject.models import Context
24-
{%- if use_asyncio != 'y' %}
29+
{%- endif %}
30+
{%- if use_asyncio != 'y' and extension_type == 'multiaccount' %}
2531
from connect.eaas.core.inject.synchronous import get_installation, get_installation_client
32+
{%- elif use_asyncio != 'y' and extension_type != 'multiaccount' %}
33+
from connect.eaas.core.inject.synchronous import get_extension_client
2634
{%- endif %}
2735
from fastapi import Depends
2836

29-
from {{ package_name }}.schemas import Marketplace, Settings
37+
from {{ package_name }}.schemas import Marketplace{% if extension_type == 'multiaccount' %}, Settings{% endif %}
3038

3139

3240
{% if include_variables_example == 'y' -%}
@@ -50,6 +58,23 @@ from {{ package_name }}.schemas import Marketplace, Settings
5058
{% endif -%}
5159
class {{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication(WebApplicationBase):
5260

61+
@router.get(
62+
'/marketplaces',
63+
summary='List all available marketplaces',
64+
response_model=List[Marketplace],
65+
)
66+
{% if use_asyncio == 'y' %}async {% endif %}def list_marketplaces(
67+
self,
68+
client: {% if use_asyncio == 'y' %}Async{% endif %}ConnectClient = Depends(get_{% if extension_type == 'multiaccount' %}installation{% else %}extension{% endif %}_client),
69+
):
70+
return [
71+
Marketplace(**marketplace)
72+
{% if use_asyncio == 'y' %}async {% endif %}for marketplace in client.marketplaces.all().values_list(
73+
'id', 'name', 'description', 'icon',
74+
)
75+
]
76+
77+
{% if extension_type == 'multiaccount' -%}
5378
@router.get(
5479
'/settings',
5580
summary='Retrive charts settings',
@@ -79,22 +104,6 @@ class {{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication(
79104
)
80105
return settings
81106

82-
@router.get(
83-
'/marketplaces',
84-
summary='List all available marketplaces',
85-
response_model=List[Marketplace],
86-
)
87-
{% if use_asyncio == 'y' %}async {% endif %}def list_marketplaces(
88-
self,
89-
client: {% if use_asyncio == 'y' %}Async{% endif %}ConnectClient = Depends(get_installation_client),
90-
):
91-
return [
92-
Marketplace(**marketplace)
93-
{% if use_asyncio == 'y' %}async {% endif %}for marketplace in client.marketplaces.all().values_list(
94-
'id', 'name', 'description', 'icon',
95-
)
96-
]
97-
98107
@router.get(
99108
'/chart',
100109
summary='Generate chart data',
@@ -123,3 +132,4 @@ class {{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication(
123132
],
124133
},
125134
}
135+
{% endif %}

connect/cli/plugins/project/extension/templates/bootstrap/${project_slug}/package.json.j2

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"mini-css-extract-plugin": "^2.6.1",
3838
"style-loader": "^3.3.1",
3939
"webpack": "^5.74.0",
40-
"webpack-cli": "^4.10.0"
40+
"webpack-cli": "^4.10.0",
41+
"copy-webpack-plugin": "^11.0.0"
4142
}
4243
}

connect/cli/plugins/project/extension/templates/bootstrap/${project_slug}/pyproject.toml.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ readme = "./README.md"
2222

2323
[tool.poetry.dependencies]
2424
python = ">=3.8,<4"
25-
connect-eaas-core = ">=26.10,<27"
25+
connect-eaas-core = ">=26.13,<27"
2626
2727
[tool.poetry.dev-dependencies]
2828
pytest = ">=6.1.2,<8"

connect/cli/plugins/project/extension/templates/bootstrap/${project_slug}/tests/test_webapp.py.j2

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,66 @@
33
# Copyright (c) {% now 'utc', '%Y' %}, {{ author }}
44
# All rights reserved.
55
#
6+
{% if extension_type == 'multiaccount' -%}
67
from connect.client import R
78

89
from {{ package_name }}.schemas import Marketplace, Settings
10+
{% endif -%}
911
from {{ package_name }}.webapp import {{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication
1012

1113

14+
def test_list_marketplaces(test_client_factory, {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory):
15+
marketplaces = [
16+
{
17+
'id': 'MP-000',
18+
'name': 'MP 000',
19+
'description': 'MP 000 description',
20+
'icon': 'mp_000.png',
21+
},
22+
{
23+
'id': 'MP-001',
24+
'name': 'MP 001',
25+
'description': 'MP 001 description',
26+
'icon': 'mp_001.png',
27+
},
28+
]
29+
client_mocker = {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory()
30+
31+
client_mocker.marketplaces.all().mock(return_value=marketplaces)
32+
33+
client = test_client_factory({{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication)
34+
response = client.get('/api/marketplaces')
35+
36+
assert response.status_code == 200
37+
38+
data = response.json()
39+
40+
assert data == marketplaces
41+
42+
43+
def test_list_marketplaces_api_error(test_client_factory, {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory):
44+
client_mocker = {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory()
45+
46+
error_data = {
47+
'error_code': 'AUTH_001',
48+
'errors': [
49+
'API request is unauthorized.',
50+
],
51+
}
52+
53+
client_mocker.marketplaces.all().mock(
54+
status_code=401,
55+
return_value=error_data,
56+
)
57+
58+
client = test_client_factory({{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication)
59+
response = client.get('/api/marketplaces')
60+
61+
assert response.status_code == 401
62+
assert response.json() == error_data
63+
64+
65+
{% if extension_type == 'multiaccount' -%}
1266
def test_retrieve_settings_empty(test_client_factory):
1367
installation = {
1468
'id': 'EIN-000',
@@ -68,64 +122,17 @@ def test_save_settings(test_client_factory, {% if use_asyncio == 'y' %}async_{%
68122

69123
client = test_client_factory({{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication)
70124

71-
response = client.post('/api/settings', json=settings.dict())
125+
response = client.post(
126+
'/api/settings',
127+
json=settings.dict(),
128+
context={'installation_id': 'EIN-000'},
129+
)
72130
assert response.status_code == 200
73131

74132
data = response.json()
75133
assert data == settings.dict()
76134

77135

78-
def test_list_marketplaces(test_client_factory, {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory):
79-
marketplaces = [
80-
{
81-
'id': 'MP-000',
82-
'name': 'MP 000',
83-
'description': 'MP 000 description',
84-
'icon': 'mp_000.png',
85-
},
86-
{
87-
'id': 'MP-001',
88-
'name': 'MP 001',
89-
'description': 'MP 001 description',
90-
'icon': 'mp_001.png',
91-
},
92-
]
93-
client_mocker = {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory()
94-
95-
client_mocker.marketplaces.all().mock(return_value=marketplaces)
96-
97-
client = test_client_factory({{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication)
98-
response = client.get('/api/marketplaces')
99-
100-
assert response.status_code == 200
101-
102-
data = response.json()
103-
104-
assert data == marketplaces
105-
106-
107-
def test_list_marketplaces_api_error(test_client_factory, {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory):
108-
client_mocker = {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory()
109-
110-
error_data = {
111-
'error_code': 'AUTH_001',
112-
'errors': [
113-
'API request is unauthorized.',
114-
],
115-
}
116-
117-
client_mocker.marketplaces.all().mock(
118-
status_code=401,
119-
return_value=error_data,
120-
)
121-
122-
client = test_client_factory({{ project_slug|replace("_", " ")|title|replace(" ", "") }}WebApplication)
123-
response = client.get('/api/marketplaces')
124-
125-
assert response.status_code == 401
126-
assert response.json() == error_data
127-
128-
129136
def test_generate_chart_data(test_client_factory, {% if use_asyncio == 'y' %}async_{% endif %}client_mocker_factory):
130137
marketplaces = [
131138
{
@@ -172,3 +179,4 @@ def test_generate_chart_data(test_client_factory, {% if use_asyncio == 'y' %}asy
172179
],
173180
},
174181
}
182+
{% endif %}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/></svg>

connect/cli/plugins/project/extension/templates/bootstrap/${project_slug}/webpack.config.js.j2

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const htmlWebpackPlugin = require('html-webpack-plugin');
77
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
88
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
99
const ESLintPlugin = require('eslint-webpack-plugin');
10+
const CopyWebpackPlugin = require('copy-webpack-plugin');
1011

1112
const generateHtmlPlugin = (title) => {
1213
const moduleName = title.toLowerCase();
@@ -56,6 +57,11 @@ module.exports = {
5657
},
5758
plugins: [
5859
...pages,
60+
new CopyWebpackPlugin({
61+
patterns: [
62+
{ from: __dirname + "/ui/images", to: "images" },
63+
],
64+
}),
5965
new MiniCssExtractPlugin({
6066
filename: "index.css",
6167
chunkFilename: "[id].css",

0 commit comments

Comments
 (0)