refactor: Invite modal component to support single & multi-select dropdown options (#17073)

* refactored code for invite modal

* updated invite users modal component

* minor change

* optimized addition of links in invite modal dropdown

* fixed a cypress test

* removed msw

* fixed a cypress test

* fixed a cypress test

* fixed a cypress test

* fixed a test
This commit is contained in:
Ankita Kinger 2022-09-29 15:23:25 +05:30 committed by GitHub
parent e67ab8eea5
commit a1b24ab7dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 78 additions and 839 deletions

View File

@ -48,9 +48,8 @@ describe("Create new workspace and invite user & validate all roles", () => {
cy.wait(2000);
cy.xpath(HomePage.selectRole).click();
cy.get(".t--dropdown-option")
.should("have.length", 2)
.should("have.length", 1)
.and("contain.text", `App Viewer - ${workspaceId}`);
cy.get(".t--dropdown-option").should("contain.text", `Select a role`);
cy.get(HomePage.closeBtn).click();
homePage.LaunchAppFromAppHover();
@ -88,13 +87,12 @@ describe("Create new workspace and invite user & validate all roles", () => {
cy.wait(2000);
cy.xpath(HomePage.selectRole).click();
cy.get(".t--dropdown-option")
.should("have.length", 3)
.should("have.length", 2)
.and(
"contain.text",
`App Viewer - ${workspaceId}`,
`Developer - ${workspaceId}`,
);
cy.get(".t--dropdown-option").should("contain.text", `Select a role`);
cy.get(HomePage.closeBtn).click();
homePage.LogOutviaAPI();
});
@ -117,19 +115,27 @@ describe("Create new workspace and invite user & validate all roles", () => {
Cypress.env("TESTPASSWORD1"),
"Administrator",
);
homePage.FilterApplication(appid, workspaceId);
cy.get(homePage._applicationCard)
.first()
.trigger("mouseover");
homePage.InviteUserToWorkspace(
workspaceId,
Cypress.env("TESTUSERNAME2"),
"App Viewer",
);
cy.get(HomePage.closeBtn).click();
cy.wait(2000);
homePage.FilterApplication(appid, workspaceId);
cy.get(homePage._applicationCard)
.first()
.trigger("mouseover");
cy.get(homePage._appHoverIcon("edit"))
.first()
.click({ force: true });
// cy.xpath(homePage._editPageLanding).should("exist");
cy.wait(4000);
cy.xpath("//span[text()='SHARE']").click();
cy.wait(2000);
cy.xpath(HomePage.selectRole).click();
cy.get(".t--dropdown-option")
.should("have.length", 4)
.should("have.length", 3)
.should(
"contain.text",
`App Viewer - ${workspaceId}`,
@ -139,7 +145,6 @@ describe("Create new workspace and invite user & validate all roles", () => {
"contain.text",
`Administrator - ${workspaceId}`,
);
cy.get(".t--dropdown-option").should("contain.text", `Select a role`);
cy.get(HomePage.closeBtn).click();
homePage.LogOutviaAPI();
});

View File

@ -182,7 +182,6 @@
"test:unit": "$(npm bin)/jest -b --colors --no-cache --silent --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'",
"test:jest": "$(npm bin)/jest --watch",
"generate:widget": "plop --plopfile generators/index.js",
"postbuild": "rm build/mockServiceWorker.js",
"postinstall": "patch-package && CURRENT_SCOPE=client node ../shared/install-dependencies.js",
"preinstall": "CURRENT_SCOPE=client node ../shared/build-shared-dep.js",
"install": "node cypress/apply-patches.js"
@ -307,8 +306,5 @@
"json-schema": "0.4.0",
"node-fetch": "2.6.7",
"babel-plugin-styled-components": "2.0.7"
},
"msw": {
"workerDirectory": "public"
}
}

View File

@ -1,322 +0,0 @@
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
/* eslint-disable */
/* tslint:disable */
const INTEGRITY_CHECKSUM = '795882c72c7304f6fa1d4a65a2418900'
const bypassHeaderName = 'x-msw-bypass'
const activeClientIds = new Set()
self.addEventListener('install', function () {
return self.skipWaiting()
})
self.addEventListener('activate', async function (event) {
return self.clients.claim()
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
if (!clientId || !self.clients) {
return
}
const client = await self.clients.get(clientId)
if (!client) {
return
}
const allClients = await self.clients.matchAll()
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
})
break
}
case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: true,
})
break
}
case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}
case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
// Resolve the "master" client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMasterClient(event) {
const client = await self.clients.get(event.clientId)
if (client.frameType === 'top-level') {
return client
}
const allClients = await self.clients.matchAll()
return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}
async function handleRequest(event, requestId) {
const client = await resolveMasterClient(event)
const response = await getResponse(event, client, requestId)
// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (activeClientIds.has(client.id)) {
;(async function () {
const clonedResponse = response.clone()
sendToClient(client, {
type: 'RESPONSE',
payload: {
requestId,
type: clonedResponse.type,
ok: clonedResponse.ok,
status: clonedResponse.status,
statusText: clonedResponse.statusText,
body:
clonedResponse.body === null ? null : await clonedResponse.text(),
headers: serializeHeaders(clonedResponse.headers),
redirected: clonedResponse.redirected,
},
})
})()
}
return response
}
async function getResponse(event, client, requestId) {
const { request } = event
const requestClone = request.clone()
const getOriginalResponse = () => fetch(requestClone)
// Bypass mocking when the request client is not active.
if (!client) {
return getOriginalResponse()
}
// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return await getOriginalResponse()
}
// Bypass requests with the explicit bypass header
if (requestClone.headers.get(bypassHeaderName) === 'true') {
const cleanRequestHeaders = serializeHeaders(requestClone.headers)
// Remove the bypass header to comply with the CORS preflight check.
delete cleanRequestHeaders[bypassHeaderName]
const originalRequest = new Request(requestClone, {
headers: new Headers(cleanRequestHeaders),
})
return fetch(originalRequest)
}
// Send the request to the client-side MSW.
const reqHeaders = serializeHeaders(request.headers)
const body = await request.text()
const clientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
method: request.method,
headers: reqHeaders,
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body,
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
},
})
switch (clientMessage.type) {
case 'MOCK_SUCCESS': {
return delayPromise(
() => respondWithMock(clientMessage),
clientMessage.payload.delay,
)
}
case 'MOCK_NOT_FOUND': {
return getOriginalResponse()
}
case 'NETWORK_ERROR': {
const { name, message } = clientMessage.payload
const networkError = new Error(message)
networkError.name = name
// Rejecting a request Promise emulates a network error.
throw networkError
}
case 'INTERNAL_ERROR': {
const parsedBody = JSON.parse(clientMessage.payload.body)
console.error(
`\
[MSW] Request handler function for "%s %s" has thrown the following exception:
${parsedBody.errorType}: ${parsedBody.message}
(see more detailed error stack trace in the mocked response body)
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error.
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
request.method,
request.url,
)
return respondWithMock(clientMessage)
}
}
return getOriginalResponse()
}
self.addEventListener('fetch', function (event) {
const { request } = event
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}
const requestId = uuidv4()
return event.respondWith(
handleRequest(event, requestId).catch((error) => {
console.error(
'[MSW] Failed to mock a "%s" request to "%s": %s',
request.method,
request.url,
error,
)
}),
)
})
function serializeHeaders(headers) {
const reqHeaders = {}
headers.forEach((value, name) => {
reqHeaders[name] = reqHeaders[name]
? [].concat(reqHeaders[name]).concat(value)
: value
})
return reqHeaders
}
function sendToClient(client, message) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}
resolve(event.data)
}
client.postMessage(JSON.stringify(message), [channel.port2])
})
}
function delayPromise(cb, duration) {
return new Promise((resolve) => {
setTimeout(() => resolve(cb()), duration)
})
}
function respondWithMock(clientMessage) {
return new Response(clientMessage.payload.body, {
...clientMessage.payload,
headers: clientMessage.payload.headers,
})
}
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (Math.random() * 16) | 0
const v = c == 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
}

View File

@ -39,5 +39,3 @@ export const WELCOME_NON_SUPER_FORM_NAME = "WelcomeNonSuperSetupForm";
export const SAVE_THEME_FORM_NAME = "SaveThemeForm";
export const REDIRECT_URL_FORM = "RedirectURLForm";
export const ENTITYID_URL_FORM = "EntityIdURLForm";
export const inviteModalLinks: any[] = [];

View File

@ -1,8 +1,3 @@
import { rest } from "msw";
import testMockApi from "./mockJsons/testMockApi.json";
/* import { rest } from "msw"; */
export const handlers = [
rest.get("/api/testMockApi", (req, res, ctx) => {
return res(ctx.status(200), ctx.json(testMockApi));
}),
];
export const handlers = [];

View File

@ -1,5 +0,0 @@
{
"data": {
"environment": "CE"
}
}

View File

@ -49,11 +49,7 @@ import {
Size,
Text,
TextType,
Icon,
IconSize,
SegmentHeader,
TextProps,
TooltipComponent,
DropdownOption,
} from "design-system";
import { Classes, Variant } from "components/ads/common";
@ -65,8 +61,7 @@ import { ScrollIndicator } from "design-system";
import UserApi from "@appsmith/api/UserApi";
import { Colors } from "constants/Colors";
import { fetchWorkspace } from "actions/workspaceActions";
import { SubTextPosition } from "components/constants";
import { Link } from "react-router-dom";
import { useHistory } from "react-router-dom";
import { Tooltip } from "@blueprintjs/core";
import { isEllipsisActive } from "utils/helpers";
@ -228,16 +223,6 @@ export const LabelText = styled(Text)`
letter-spacing: -0.24px;
`;
/*const LinksWrapper = styled.div`
&:before {
border-top: 1px solid var(--appsmith-color-black-200);
content: "";
position: absolute;
left: 12px;
right: 12px;
}
`;*/
export const LeftIconWrapper = styled.span`
font-size: 20px;
line-height: 19px;
@ -247,145 +232,12 @@ export const LeftIconWrapper = styled.span`
top: 1px;
`;
export const SelectedIcon = styled(Icon)<{ name: string }>`
margin-right: 6px;
& > div:first-child {
height: 18px;
width: 18px;
svg {
height: 18px;
width: 18px;
rect {
fill: ${(props) => props.theme.colors.dropdownIconBg};
rx: 0;
}
path {
fill: ${(props) => props.theme.colors.propertyPane.label};
}
}
}
svg {
${(props) =>
props.name === "right-arrow" ? `transform: rotate(-45deg);` : ``}
path {
fill: ${(props) =>
props.fillColor
? props.fillColor
: props.theme.colors.dropdown.selected.icon};
}
}
`;
export const StyledSubText = styled(Text)<{
showDropIcon?: boolean;
subTextPosition?: SubTextPosition;
}>`
${(props) =>
props.subTextPosition === SubTextPosition.BOTTOM
? "margin-top: 3px"
: "margin-left: auto"};
&&& {
color: ${(props) => props.theme.colors.dropdown.menu.subText};
}
&.sub-text {
color: ${(props) => props.theme.colors.dropdown.selected.subtext};
text-align: end;
margin-right: ${(props) => `${props.theme.spaces[4]}px`};
}
`;
export const OptionWrapper = styled.div<{
disabled?: boolean;
selected: boolean;
subTextPosition?: SubTextPosition;
selectedHighlightBg?: string;
}>`
padding: ${(props) => props.theme.spaces[3] + 1}px
${(props) => props.theme.spaces[5]}px;
${(props) => (!props.disabled ? "cursor: pointer" : "")};
display: flex;
width: 100%;
min-height: 36px;
flex-direction: ${(props) =>
props.subTextPosition === SubTextPosition.BOTTOM ? "column" : "row"};
align-items: ${(props) =>
props.subTextPosition === SubTextPosition.BOTTOM ? "flex-start" : "center"};
background-color: ${(props) =>
props.selected
? props.selectedHighlightBg || `var(--appsmith-color-black-200)`
: `initial`};
&&& svg {
rect {
fill: ${(props) => props.theme.colors.dropdownIconBg};
}
}
.bp3-popover-wrapper {
width: 100%;
}
.${Classes.TEXT} {
color: ${(props) =>
props.disabled
? Colors.GRAY2
: props.selected
? props.theme.colors.dropdown.menu.hoverText
: props.theme.colors.dropdown.menu.text};
}
.${Classes.ICON} {
margin-right: ${(props) => props.theme.spaces[5]}px;
svg {
path {
${(props) =>
props.selected
? `fill: ${props.theme.colors.dropdown.selected.icon}`
: `fill: ${props.theme.colors.dropdown.icon}`};
}
}
}
&:hover,
&.highlighted {
background-color: ${(props) =>
props.selectedHighlightBg || `var(--appsmith-color-black-200)`};
&&& svg {
rect {
fill: ${(props) => props.theme.colors.textOnDarkBG};
}
}
.${Classes.TEXT} {
color: ${(props) => props.theme.colors.dropdown.menu.hoverText};
}
${StyledSubText} {
color: ${(props) => props.theme.colors.dropdown.menu.subText};
}
.${Classes.ICON} {
svg {
path {
fill: ${(props) => props.theme.colors.dropdown.hovered.icon};
}
}
}
}
`;
export const StyledText = styled(Text)`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const LabelWrapper = styled.div<{ label?: string }>`
display: flex;
flex-direction: column;
align-items: flex-start;
span:last-child {
margin-top: ${(props) => props.theme.spaces[2] - 1}px;
}
&:hover {
.${Classes.TEXT} {
color: ${(props) => props.theme.colors.dropdown.selected.text};
}
}
`;
export function TooltipWrappedText(
props: TextProps & {
label: string;
@ -428,19 +280,7 @@ const validateFormValues = (values: {
});
}
if (
typeof values.roles === "undefined" &&
(typeof values.role === "undefined" || values.role?.trim().length === 0)
) {
throw new SubmissionError({
_error: createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY),
});
}
if (
typeof values.role === "undefined" &&
(typeof values.roles === "undefined" || values.roles.length === 0)
) {
if (typeof values.role === "undefined" || values.role.length === 0) {
throw new SubmissionError({
_error: createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY),
});
@ -453,20 +293,10 @@ const validate = (values: any) => {
errors["users"] = createMessage(INVITE_USERS_VALIDATION_EMAILS_EMPTY);
}
if (
typeof values.roles === "undefined" &&
(typeof values.role === "undefined" || values.role?.trim().length === 0)
) {
if (typeof values.role === "undefined" || values.role.length === 0) {
errors["role"] = createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY);
}
if (
typeof values.role === "undefined" &&
(typeof values.roles === "undefined" || values.roles.length === 0)
) {
errors["roles"] = createMessage(INVITE_USERS_VALIDATION_ROLE_EMPTY);
}
if (values.users && values.users.length > 0) {
const _users = values.users.split(",").filter(Boolean);
@ -485,13 +315,10 @@ export const InviteButtonWidth = "88px";
function WorkspaceInviteUsersForm(props: any) {
const [emailError, setEmailError] = useState("");
const [selectedOption, setSelectedOption] = useState<any>({});
const [selectedOption, setSelectedOption] = useState<any[]>([]);
const userRef = React.createRef<HTMLDivElement>();
const history = useHistory();
const selectedId = props?.selected?.id;
const multiSelectDropdownOptions: Partial<DropdownOption>[] =
props.options && props.options.length > 0 && props.isMultiSelectDropdown
? props.options
: [];
const selected = useMemo(
() =>
@ -515,11 +342,9 @@ function WorkspaceInviteUsersForm(props: any) {
fetchCurrentWorkspace,
fetchUser,
handleSubmit,
isAclFlow = false,
isApplicationInvite,
isLoading,
isMultiSelectDropdown = false,
links = [],
message = "",
placeholder = "",
submitFailed,
@ -528,8 +353,6 @@ function WorkspaceInviteUsersForm(props: any) {
valid,
} = props;
const [selectedItems, setSelectedItems] = useState<any[]>([]);
// set state for checking number of users invited
const [numberOfUsersInvited, updateNumberOfUsersInvited] = useState(0);
const currentWorkspace = useSelector(getCurrentAppWorkspace);
@ -541,31 +364,30 @@ function WorkspaceInviteUsersForm(props: any) {
);
useEffect(() => {
if (!isAclFlow) {
fetchUser(props.workspaceId);
fetchAllRoles(props.workspaceId);
fetchCurrentWorkspace(props.workspaceId);
}
fetchUser(props.workspaceId);
fetchAllRoles(props.workspaceId);
fetchCurrentWorkspace(props.workspaceId);
}, [props.workspaceId, fetchUser, fetchAllRoles, fetchCurrentWorkspace]);
useEffect(() => {
if (selected) {
setSelectedItems([selected]);
setSelectedOption([selected]);
props.initialize({
roles: [selected],
role: [selected],
});
}
}, []);
const styledRoles = props.roles.map((role: any) => {
return {
id: role.id,
value: role.name,
label: role.description,
};
});
styledRoles.push(...links);
const styledRoles =
props.options && props.options.length > 0
? props.options
: props.roles.map((role: any) => {
return {
id: role.id,
value: role.name,
label: role.description,
};
});
const theme = useContext(ThemeContext);
@ -592,12 +414,15 @@ function WorkspaceInviteUsersForm(props: any) {
[allUsers, theme],
);
const onSelect = (_value?: string, options?: any) => {
setSelectedItems(options);
const onSelect = (_value?: string, option?: any) => {
if (option.link) {
history.push(option.link);
}
setSelectedOption(isMultiSelectDropdown ? option : [option]);
};
const onRemoveOptions = (updatedItems: any) => {
setSelectedItems(updatedItems);
setSelectedOption(updatedItems);
};
const getLabel = (selectedOption: Partial<DropdownOption>[]) => {
@ -626,155 +451,6 @@ function WorkspaceInviteUsersForm(props: any) {
}
};
const renderOption = ({
index,
isHighlighted,
option,
optionClickHandler,
}: {
index?: number;
option: DropdownOption | DropdownOption[];
optionClickHandler?: (dropdownOption: DropdownOption) => void;
isHighlighted?: boolean;
}) => {
let isSelected = false;
if (props.isMultiSelect && Array.isArray(selected) && selected.length) {
isSelected = !!selected.find((selectedOption: any) =>
!Array.isArray(option) ? selectedOption.value === option.value : false,
);
} else {
isSelected =
!Array.isArray(option) && selected
? selected.value === option.value
: false;
}
return !Array.isArray(option) && !option.isSectionHeader ? (
!option.link ? (
<TooltipComponent
content={
!!option.disabledTooltipText
? option.disabledTooltipText
: "Action not supported"
}
disabled={!option.disabled}
key={`tootltip-${index}`}
styles={{
width: "100%",
}}
>
<OptionWrapper
aria-selected={isSelected}
className={`t--dropdown-option ${isSelected ? "selected" : ""} ${
isHighlighted ? "highlighted" : ""
}`}
data-cy={`t--dropdown-option-${option?.label}`}
disabled={option.disabled}
key={index}
onClick={
// users should be able to unselect a selected option by clicking the option again.
isSelected && props.allowDeselection
? () => props.removeSelectedOptionClickHandler(option)
: () => optionClickHandler?.(option)
}
role="option"
selected={
props.isMultiSelect ? props.highlightIndex === index : isSelected
}
selectedHighlightBg={props.selectedHighlightBg}
subTextPosition={option.subTextPosition ?? SubTextPosition.LEFT}
>
{option.leftElement && (
<LeftIconWrapper>{option.leftElement}</LeftIconWrapper>
)}
{option.icon ? (
<SelectedIcon
fillColor={option?.iconColor}
hoverFillColor={option?.iconColor}
name={option.icon}
size={option.iconSize || IconSize.XL}
/>
) : null}
{props.showLabelOnly ? (
props.truncateOption ? (
<>
<TooltipWrappedText
label={option.label || ""}
type={TextType.P1}
/>
{option.hasCustomBadge && props.customBadge}
</>
) : (
<>
<Text type={TextType.P1}>{option.label}</Text>
{option.hasCustomBadge && props.customBadge}
</>
)
) : option.label && option.value ? (
<LabelWrapper className="label-container">
<Text type={TextType.H5}>{option.value}</Text>
<Text type={TextType.P1}>{option.label}</Text>
</LabelWrapper>
) : props.truncateOption ? (
<TooltipWrappedText
label={option.value || ""}
type={TextType.P1}
/>
) : (
<Text type={TextType.P1}>{option.value}</Text>
)}
{option.subText ? (
<StyledSubText
subTextPosition={option.subTextPosition}
type={TextType.P3}
>
{option.subText}
</StyledSubText>
) : null}
</OptionWrapper>
</TooltipComponent>
) : (
<Link key={index} to={option.link || "/"}>
<OptionWrapper
className={`t--dropdown-link`}
data-cy={`t--dropdown-option-${option?.value}`}
disabled={option.disabled}
role="option"
selected={false}
selectedHighlightBg={props.selectedHighlightBg}
subTextPosition={option.subTextPosition ?? SubTextPosition.LEFT}
>
{option.leftElement && (
<LeftIconWrapper>{option.leftElement}</LeftIconWrapper>
)}
{option.icon ? (
<SelectedIcon
fillColor={option?.iconColor}
hoverFillColor={option?.iconColor}
name={option.icon}
size={option.iconSize || IconSize.XL}
/>
) : null}
<Text type={TextType.P1}>{option.value}</Text>
{option.subText ? (
<StyledSubText
subTextPosition={option.subTextPosition}
type={TextType.P3}
>
{option.subText}
</StyledSubText>
) : null}
</OptionWrapper>
</Link>
)
) : (
<SegmentHeader
key={index}
style={{ paddingRight: theme.spaces[5] }}
title={!Array.isArray(option) && option.label ? option.label : ""}
/>
);
};
return (
<WorkspaceInviteWrapper>
<InviteModalStyles />
@ -797,10 +473,11 @@ function WorkspaceInviteUsersForm(props: any) {
.join(",");
return inviteUsersToWorkspace(
{
...values,
...(props.workspaceId ? { workspaceId: props.workspaceId } : {}),
users,
permissionGroupId: selectedOption.id,
workspaceId: props.workspaceId,
permissionGroupId: isMultiSelectDropdown
? selectedOption.map((group: any) => group.id).join(",")
: selectedOption[0].id,
},
dispatch,
);
@ -821,36 +498,24 @@ function WorkspaceInviteUsersForm(props: any) {
placeholder={placeholder || "Enter email address"}
type="text"
/>
{isMultiSelectDropdown ? (
<SelectField
allowDeselection
disabled={!!selected}
isMultiSelect
labelRenderer={(selected: Partial<DropdownOption>[]) =>
getLabel(selected)
}
name="roles"
onSelect={onSelect}
options={multiSelectDropdownOptions}
outline={false}
placeholder="Select a role"
removeSelectedOption={onRemoveOptions}
selected={selectedItems}
showLabelOnly
size="small"
/>
) : (
<SelectField
data-cy="t--invite-role-input"
name="role"
onSelect={(value, option) => setSelectedOption(option)}
options={styledRoles}
outline={false}
placeholder="Select a role"
renderOption={renderOption}
size="small"
/>
)}
<SelectField
allowDeselection={isMultiSelectDropdown}
data-cy="t--invite-role-input"
disabled={props.disableDropdown}
isMultiSelect={isMultiSelectDropdown}
labelRenderer={(selected: Partial<DropdownOption>[]) =>
getLabel(selected)
}
name={"role"}
onSelect={(value, option) => onSelect(value, option)}
options={styledRoles}
outline={false}
placeholder="Select a role"
removeSelectedOption={onRemoveOptions}
selected={selectedOption}
showLabelOnly={isMultiSelectDropdown}
size="small"
/>
</div>
<Button
className="t--invite-user-btn"
@ -983,7 +648,6 @@ export default connect(
applicationId?: string;
workspaceId?: string;
isApplicationInvite?: boolean;
links?: any[];
}
>({
validate,

View File

@ -47,7 +47,6 @@ type FormDialogComponentProps = {
selected?: any;
tabs?: any[];
options?: any[];
links?: any[];
placeholder?: string;
};
@ -137,7 +136,6 @@ export function FormDialogComponent(props: FormDialogComponentProps) {
<Form
{...props.customProps}
applicationId={props.applicationId}
links={props.links}
message={props.message}
onCancel={() => setIsOpen(false)}
placeholder={props.placeholder}

View File

@ -25,14 +25,15 @@ type DropdownWrapperProps = {
};
function DropdownWrapper(props: DropdownWrapperProps) {
const [selectedOption, setSelectedOption] = useState({
value: props.placeholder,
});
const [selected, setSelected] = useState<any>([]);
const [selectedOption, setSelectedOption] = useState<any>([
{
value: props.placeholder,
},
]);
const onSelectHandler = (value?: string, option?: DropdownOption) => {
if (props?.isMultiSelect) {
const updatedItems: DropdownOption[] = [...selected, option];
const updatedItems: DropdownOption[] = [...selectedOption, option];
props.input && props.input.onChange && props.input.onChange(updatedItems);
props.onOptionSelect && props.onOptionSelect(value, updatedItems);
} else {
@ -42,7 +43,7 @@ function DropdownWrapper(props: DropdownWrapperProps) {
};
const onRemoveOptions = (value: any) => {
const updatedItems = selected.filter(
const updatedItems = selectedOption.filter(
(option: any) => option.value !== value,
);
props.input && props.input.onChange && props.input.onChange(updatedItems);
@ -50,13 +51,13 @@ function DropdownWrapper(props: DropdownWrapperProps) {
};
useEffect(() => {
if (props?.isMultiSelect) {
setSelected(props.selected);
if (props.selected) {
setSelectedOption(props.selected);
} else {
if (props.input && props.input.value) {
setSelectedOption({ value: props.input.value });
setSelectedOption([{ value: props.input.value }]);
} else if (props.placeholder) {
setSelectedOption({ value: props.placeholder });
setSelectedOption([{ value: props.placeholder }]);
}
}
}, [props.input, props.placeholder, props.selected]);
@ -73,11 +74,7 @@ function DropdownWrapper(props: DropdownWrapperProps) {
placeholder={props.placeholder}
removeSelectedOption={onRemoveOptions}
renderOption={props?.renderOption}
selected={
props.isMultiSelect
? (props.selected as DropdownOption[])
: selectedOption
}
selected={props.isMultiSelect ? selectedOption : selectedOption[0]}
showLabelOnly={props.showLabelOnly}
/>
);

View File

@ -27,12 +27,6 @@ import AppErrorBoundary from "./AppErrorBoundry";
import GlobalStyles from "globalStyles";
appInitializer();
if (process.env.NODE_ENV === "development") {
import("./mocks/browser").then(({ worker }) => {
worker.start();
});
}
function App() {
return (
<Sentry.ErrorBoundary fallback={"An error has occured"}>

View File

@ -34,7 +34,6 @@ import BackToHomeButton from "./BackToHomeButton";
import TourCompletionMessage from "pages/Editor/GuidedTour/TourCompletionMessage";
import { useHref } from "pages/Editor/utils";
import { builderURL } from "RouteBuilder";
import { inviteModalLinks } from "@appsmith/constants/forms";
import {
createMessage,
INVITE_USERS_MESSAGE,
@ -135,7 +134,6 @@ export function AppViewerHeader(props: AppViewerHeaderProps) {
bgColor: "transparent",
}}
isOpen={showAppInviteUsersDialog}
links={inviteModalLinks}
message={createMessage(INVITE_USERS_MESSAGE)}
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
title={currentApplicationDetails.name}

View File

@ -24,7 +24,6 @@ import { useHref } from "pages/Editor/utils";
import { APP_MODE } from "entities/App";
import { builderURL, viewerURL } from "RouteBuilder";
import { trimQueryString } from "utils/helpers";
import { inviteModalLinks } from "@appsmith/constants/forms";
import {
createMessage,
INVITE_USERS_MESSAGE,
@ -118,7 +117,6 @@ export function PageMenu(props: AppViewerHeaderProps) {
bgColor: "transparent",
}}
isOpen={showAppInviteUsersDialog}
links={inviteModalLinks}
message={createMessage(INVITE_USERS_MESSAGE)}
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
title={application.name}

View File

@ -41,10 +41,7 @@ import FormDialogComponent from "components/editorComponents/form/FormDialogComp
import Dialog from "components/ads/DialogComponent";
import { User } from "constants/userConstants";
import { getCurrentUser, selectFeatureFlags } from "selectors/usersSelectors";
import {
CREATE_WORKSPACE_FORM_NAME,
inviteModalLinks,
} from "@appsmith/constants/forms";
import { CREATE_WORKSPACE_FORM_NAME } from "@appsmith/constants/forms";
import {
DropdownOnSelectActions,
getOnSelectAction,
@ -708,7 +705,6 @@ function ApplicationsSection(props: any) {
title={`Invite Users to ${workspace.name}`}
>
<Form
links={inviteModalLinks}
message={createMessage(INVITE_USERS_MESSAGE)}
workspaceId={workspace.id}
/>
@ -734,7 +730,6 @@ function ApplicationsSection(props: any) {
<FormDialogComponent
Form={WorkspaceInviteUsersForm}
canOutsideClickClose
links={inviteModalLinks}
message={createMessage(INVITE_USERS_MESSAGE)}
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
title={`Invite Users to ${workspace.name}`}

View File

@ -84,7 +84,6 @@ import EndTour from "./GuidedTour/EndTour";
import { GUIDED_TOUR_STEPS } from "./GuidedTour/constants";
import { viewerURL } from "RouteBuilder";
import { useHref } from "./utils";
import { inviteModalLinks } from "@appsmith/constants/forms";
const HeaderWrapper = styled.div`
width: 100%;
@ -462,7 +461,6 @@ export function EditorHeader(props: EditorHeaderProps) {
bgColor: Colors.GEYSER_LIGHT,
}}
isOpen={showAppInviteUsersDialog}
links={inviteModalLinks}
message={createMessage(INVITE_USERS_MESSAGE)}
placeholder={createMessage(INVITE_USERS_PLACEHOLDER)}
title={

View File

@ -63,7 +63,6 @@ function AppInviteUsersForm(props: any) {
fetchCurrentWorkspace,
isChangingViewAccess,
isFetchingApplication,
links,
} = props;
const currentWorkspaceId = useSelector(getCurrentWorkspaceId);
@ -125,7 +124,6 @@ function AppInviteUsersForm(props: any) {
{canInviteToWorkspace && (
<WorkspaceInviteUsersForm
isApplicationInvite
links={links}
workspaceId={props.workspaceId}
/>
)}

View File

@ -1,68 +0,0 @@
import { createSelector } from "reselect";
import { AppState } from "@appsmith/reducers";
import { WorkspaceRole } from "constants/workspaceConstants";
export const getRolesFromState = (state: AppState) => {
return state.ui.workspaces.roles;
};
export const getWorkspaceLoadingStates = (state: AppState) => {
return {
isFetchingWorkspace: state.ui.workspaces.loadingStates.isFetchingWorkspace,
isFetchingAllUsers: state.ui.workspaces.loadingStates.isFetchAllUsers,
isFetchingAllRoles: state.ui.workspaces.loadingStates.isFetchAllRoles,
deletingUserInfo: state.ui.workspaces.workspaceUsers.filter(
(el) => el.isDeleting,
)[0],
roleChangingUserInfo: state.ui.workspaces.workspaceUsers.filter(
(el) => el.isChangingRole,
)[0],
};
};
export const getCurrentWorkspaceId = (state: AppState) =>
state.ui.workspaces.currentWorkspace.id;
export const getWorkspaces = (state: AppState) => {
return state.ui.applications.userWorkspaces;
};
export const getCurrentWorkspace = (state: AppState) => {
return state.ui.applications.userWorkspaces.map((el) => el.workspace);
};
export const getCurrentAppWorkspace = (state: AppState) => {
return state.ui.workspaces.currentWorkspace;
};
export const getAllUsers = (state: AppState) =>
state.ui.workspaces.workspaceUsers;
export const getAllRoles = (state: AppState) =>
state.ui.workspaces.workspaceRoles;
export const getRoles = createSelector(
getRolesFromState,
(roles?: WorkspaceRole[]): WorkspaceRole[] | undefined => {
return roles?.map((role) => ({
id: role.id,
name: role.displayName || role.name,
isDefault: role.isDefault,
}));
},
);
export const getRolesForField = createSelector(getAllRoles, (roles?: any) => {
return Object.entries(roles).map((role) => {
return {
id: role[0],
name: role[0],
description: role[1],
};
});
});
export const getDefaultRole = createSelector(
getRoles,
(roles?: WorkspaceRole[]) => {
return roles?.find((role) => role.isDefault);
},
);
export const getCurrentError = (state: AppState) => {
return state.ui.errors.currentError;
};