diff --git a/README.md b/README.md index 2be2dbe..2c05ccb 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ You can find the UP9 Extension in the Visual Studio Code Marketplace. To install ## Quick Start -* Press `Command-Shift-P` to access VS Code command launcher and select **UP9: Open Code Browser** from the list. This will open a right-pane window offering you to sign in to your UP9 account: +* Press `Command-Shift-P` to access VS Code command launcher and select **UP9: Code Browser** from the list. This will open a right-pane window offering you to sign in to your UP9 account: ![The right-pane upon opening the code browser](images/image1.png) diff --git a/package.json b/package.json index 3bcf941..22dffb8 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "commands": [ { "command": "up9.openTestsBrowser", - "title": "UP9: Open Code Browser" + "title": "UP9: Code Browser" }, { "command": "up9.runTest", diff --git a/src/commands/runInCloud.ts b/src/commands/runInCloud.ts index 1fcc734..00d84fd 100644 --- a/src/commands/runInCloud.ts +++ b/src/commands/runInCloud.ts @@ -47,7 +47,7 @@ export class CloudRunner { } //TODO: reuse the same terminal (will require having only 1 simultaneous test run) - const terminalOutputter = this.createAndShowTerminal("Running test through UP9...\n\r", promise); + const terminalOutputter = this.createAndShowTerminal(`Running Code in UP9 - ${this._up9Auth.getEnv()} on workspace ${defaultWorkspace}\n\r(in order to change env/workspace, please change configuration in the UP9:Code browser)\n\r`, promise); // terminal takes a second to properly initialize, calls to print before its ready will result in nothing being printed await delay(1000); diff --git a/src/webview/src/components/testsBrowserComponent.tsx b/src/webview/src/components/testsBrowserComponent.tsx index 7c6eb7a..686fbeb 100644 --- a/src/webview/src/components/testsBrowserComponent.tsx +++ b/src/webview/src/components/testsBrowserComponent.tsx @@ -12,11 +12,12 @@ import $ from "jquery"; const TestsBrowserComponent: React.FC<{}> = observer(() => { const [workspaces, setWorkspaces] = useState(null); - const [workspaceFilterInput, setWorkspaceFilterInput] = useState(""); const [workspaceOAS, setWorkspaceOAS] = useState(null); + const [services, setServices] = useState(null); + const [selectedService, setSelectedService] = useState(null); + const [endpoints, setEndpoints] = useState(null); - const [endpointFilterInput, setEndpointFilterInput] = useState(""); const [selectedEndpoint, setSelectedEndpoint] = useState(null); const [workspaceSpans, setWorkspaceSpans] = useState(null); @@ -24,6 +25,13 @@ const TestsBrowserComponent: React.FC<{}> = observer(() => { const [isLoading, setIsLoading] = useState(true); + const serviceEndpoints = useMemo(() => { + if (!selectedService || !endpoints) { + return []; + } + return endpoints.filter(endpoint => endpoint.service === selectedService); + }, [selectedService, endpoints]); + useEffect(() => { if (up9AuthStore.defaultWorkspace) { setLastSelectedWorkspace(up9AuthStore.defaultWorkspace); @@ -37,23 +45,9 @@ const TestsBrowserComponent: React.FC<{}> = observer(() => { } const getEndpointDisplayText = (endpoint) => { - return `${endpoint.method.toUpperCase()} ${endpoint.service}${endpoint.path}`; + return `${endpoint.method.toUpperCase()} ${endpoint.path}`; } - const filteredEndpoints = useMemo(() => { - if (!endpoints || !endpointFilterInput) { - return endpoints; - } - return endpoints.filter(endpoint => getEndpointDisplayText(endpoint).toLocaleLowerCase().indexOf(endpointFilterInput.toLowerCase()) > -1); - }, [endpoints, endpointFilterInput]); - - const filteredWorkspaces = useMemo(() => { - if (!workspaces || !workspaceFilterInput) { - return workspaces; - } - return workspaces.filter(workspace => workspace.toLocaleLowerCase().indexOf(workspaceFilterInput.toLowerCase()) > -1); - }, [workspaces, workspaceFilterInput]); - useEffect(() => { if (workspaces && !up9AuthStore.defaultWorkspace) { setDefaultWorkspace(workspaces?.[0]); @@ -62,7 +56,6 @@ const TestsBrowserComponent: React.FC<{}> = observer(() => { const refreshWorkspaces = async () => { setIsLoading(true); - setWorkspaceFilterInput(""); try { const workspaces = await sendApiMessage(ApiMessageType.WorkspacesList, null); setWorkspaces(workspaces); @@ -75,8 +68,8 @@ const TestsBrowserComponent: React.FC<{}> = observer(() => { useEffect(() => { (async () => { + setSelectedService(""); setSelectedEndpoint(null); - setEndpointFilterInput(""); setEndpoints(null); setWorkspaceOAS(null); @@ -84,6 +77,8 @@ const TestsBrowserComponent: React.FC<{}> = observer(() => { try { const endpoints = await sendApiMessage(ApiMessageType.EndpointsList, {workspaceId: up9AuthStore.defaultWorkspace}); setEndpoints(endpoints); + + setServices(Array.from(new Set(endpoints.map(endpoint => endpoint.service)))); } catch (error) { console.error('error loading workspace endpoints', error); } @@ -112,25 +107,19 @@ const TestsBrowserComponent: React.FC<{}> = observer(() => { }, [up9AuthStore.isAuthConfigured, up9AuthStore.up9Env]); const onWorkspaceDropdownToggle = (isOpen: boolean) => { - setIsWorkspaceDropDownOpen(isOpen); if (!isOpen && !up9AuthStore.defaultWorkspace && lastSelectedWorkspace) { // prevent user from reaching state where nothing is selected setDefaultWorkspace(lastSelectedWorkspace); } } - // TODO: refactor this - // ugly workaround for having the dropdowns apply focus to the filter form repeatedly - const [isWorkspaceDropDownOpen, setIsWorkspaceDropDownOpen] = useState(false); - const [isEndpointsDropdownOpen, setIsEndpointsDropdownOpen] = useState(false); - if (isLoading) { return ; } return <>
-
+
{logoIcon}
@@ -150,43 +139,83 @@ const TestsBrowserComponent: React.FC<{}> = observer(() => {

-
- - Workspace -
- onWorkspaceDropdownToggle(isOpen)}> - {up9AuthStore.defaultWorkspace ? up9AuthStore.defaultWorkspace : "Select a workspace"} - {isWorkspaceDropDownOpen && - {isWorkspaceDropDownOpen && setWorkspaceFilterInput(e.target.value)} />} - - {filteredWorkspaces?.map((workspace) => {return {setWorkspaceFilterInput(""); setDefaultWorkspace(workspace ?? lastSelectedWorkspace)}}>{workspace}})} - } - -
- - - Endpoint -
- { - setIsEndpointsDropdownOpen(isOpen) - if (isOpen) { - $('.select-dropdown .dropdown-menu').hide().show(0); //this is a very strange workaround for a very strange html bug, without this the drop down sometimes shifts the entire page until anything changes in the dom - } - }}> - - {selectedEndpoint ? getEndpointDisplayText(selectedEndpoint) : "Select an endpoint"} - - {isEndpointsDropdownOpen && - {isEndpointsDropdownOpen && setEndpointFilterInput(e.target.value)} />} - - {filteredEndpoints?.map((endpoint) => {return {setEndpointFilterInput(""); setSelectedEndpoint(endpoint)}}>{getEndpointDisplayText(endpoint)}})} - } - -
+
+
+ ({key: workspace, value: workspace, label: workspace}))} + onDropdownToggle={onWorkspaceDropdownToggle} onSelect={setDefaultWorkspace} value={up9AuthStore.defaultWorkspace} /> +
+
+ ({key: service, value: service, label: service}))} + disabled={!up9AuthStore.defaultWorkspace || services?.length < 1} onSelect={setSelectedService} value={selectedService} /> + + {return {key: endpoint.uuid, value: endpoint, label: getEndpointDisplayText(endpoint)}})} + disabled={!selectedService} onSelect={setSelectedEndpoint} value={selectedEndpoint} /> +
+

; }); +interface TestBrowserParameterDropdownProps { + label: string; + className: string; + placeholder: string; + disabled?: boolean; + items: {label: string, value: any, key: any}[]; + value: any; + onSelect: (value: any) => void; + onDropdownToggle?: (isOpen: boolean) => void; +} + +const TestBrowserParameterDropdown: React.FC = ({label, className, placeholder, disabled, items, value, onSelect, onDropdownToggle}) => { + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [filterInputValue, setFilterInputValue] = useState(""); + + const selectedItem = useMemo(() => { + if (value) { + return items.find(item => item.value === value); + } else { + return null; + } + }, [value, items]); + + const filteredItems = useMemo(() => { + if (!items || !filterInputValue) { + return items; + } + return items.filter(item => item.label.toLocaleLowerCase().indexOf(filterInputValue.toLowerCase()) > -1); + }, [items, filterInputValue]); + + const onDropdownToggled = (isOpen: boolean) => { + if (onDropdownToggle) { + onDropdownToggle(isOpen); + } + setIsDropdownOpen(isOpen); + } + + return + {label} + { + onDropdownToggled(isOpen) + if (isOpen) { + $('.select-dropdown .dropdown-menu').hide().show(0); //this is a very strange workaround for a very strange html bug, without this the drop down sometimes shifts the entire page until anything changes in the dom + } + }}> + + {selectedItem ? selectedItem.label : placeholder} + + {isDropdownOpen && + {isDropdownOpen && setFilterInputValue(e.target.value)} />} + + {filteredItems.map((item) => {setFilterInputValue(""); onSelect(item.value)}}>{item.label})} + } + + +}; + export default TestsBrowserComponent; diff --git a/src/webview/src/main.css b/src/webview/src/main.css index ab730b5..81b6aef 100644 --- a/src/webview/src/main.css +++ b/src/webview/src/main.css @@ -46,12 +46,12 @@ table { } .select-test-form { - height: 5%; + height: 105px; min-height: 65px; margin: 15px 20px 24px; - display: flex; - justify-content: space-around; + /* display: flex; + justify-content: space-around; */ gap: 10px; } @@ -144,7 +144,7 @@ table { .anchor-button { color: var(--vscode-textLink-foreground); text-decoration: none; - font-size: 0.9em; + font-size: 0.5em; } .anchor-button:active, .anchor-button:hover, .anchor-button:focus { @@ -208,9 +208,15 @@ hr { opacity: 0.75; } -.workspaces-form-group .form-label, .endpoints-form-group .form-label { +.workspaces-form-group .form-label, .endpoints-form-group .form-label, .services-form-group .form-label { font-size: 0.9em; - opacity: 0.8 + opacity: 0.8; + margin-bottom: 0; + margin-right: 0.9em; +} + +.services-form-group .form-label { + margin-right: 38px; } .select-dropdown { @@ -231,7 +237,7 @@ hr { text-align: left; padding-right: 8px; outline: none !important; - border: none !important; + border: 1px solid var(--vscode-dropdown-border) !important; } .show>.btn-primary.dropdown-toggle { @@ -259,17 +265,43 @@ hr { color: var(--vscode-button-foreground); } +.dropdown-container { + display: flex; + align-items: center; +} + .workspaces-form-group { - flex-grow: 1; - flex-shrink: 1; - flex-basis: 33%; + margin-bottom: 12px; + flex-grow: 2; + flex-shrink: 2; +} + +.services-form-group { + margin-bottom: 12px; + flex-grow: 2; + flex-shrink: 0; + min-width: 0; } .endpoints-form-group { flex-grow: 2; flex-shrink: 2; - flex-basis: 66%; - max-width: 50%; + min-width: 0; +} + +.endpoints-form-group .form-label { + margin-right: 25px; +} + +.endpoints-form-group, .services-form-group, .workspaces-form-group { + flex-basis: 100%; + max-width: 100%; + margin-right: 0; +} + +.endpoints-services-container { + display: flex; + flex-wrap: wrap; } .select-dropdown > button { @@ -335,7 +367,7 @@ hr { } @media screen and (max-width: 400px) { - .auth-icon-container { + .auth-icon-container, .user-icon { display: none !important; } }