diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Configuration/DesignConfigurations.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Configuration/DesignConfigurations.jsx index ca936c8f51c..55873dc5544 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Configuration/DesignConfigurations.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Configuration/DesignConfigurations.jsx @@ -66,7 +66,6 @@ import StoreVisibility from './components/StoreVisibility'; import Tags from './components/Tags'; import Social from './components/Social'; import APICategories from './components/APICategories'; -import SharedOrganizations from './components/SharedOrganizations'; const PREFIX = 'DesignConfigurations'; @@ -818,16 +817,6 @@ export default function DesignConfigurations() { /> )} - { api.apiType !== API.CONSTS.APIProduct - && settings && settings.orgAccessControlEnabled && ( - - - - )} { settings && !settings.portalConfigurationOnlyModeEnabled && ( ({ - [`& .${classes.tooltip}`]: { - top: theme.spacing(1), - marginLeft: theme.spacing(1), - }, - - [`& .${classes.listItemText}`]: { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - } -})); - -const icon = ; -const checkedIcon = ; - -/** - * Render the organizations drop down. - * @param {JSON} props props passed from it's parents. - * @returns {JSX} Render the organizations drop down. - */ -function SharedOrganizations(props) { - const [organizations, setOrganizations] = useState({}); - const { api, configDispatcher } = props; - const [selectionMode, setSelectionMode] = useState("all"); - - useEffect(() => { - API.getOrganizations().then((response) => setOrganizations(response.body)); - if (api.visibleOrganizations.includes("all")) { - setSelectionMode("all"); - } else if (api.visibleOrganizations.length === 0) { - setSelectionMode("none"); - } else { - setSelectionMode("select"); - } - }, []); - - if (organizations && !organizations.list) { - return null; - } else if (organizations && organizations.list) { - const optionsList = organizations.list; - const handleRadioChange = (event) => { - const { value } = event.target; - setSelectionMode(value); - if (value === "all") { - configDispatcher({ action: "visibleOrganizations", value: ["all"] }); - } else if (value === "none") { - configDispatcher({ action: "visibleOrganizations", value: [] }); - } - }; - const handleDropdownChange = (event, newValue) => { - configDispatcher({ - action: "visibleOrganizations", - value: newValue.map((org) => org.organizationId), - }); - }; - - return ( - - - - - - -

- -

- - )} - aria-label='Shared Organizations' - placement='right-end' - interactive - className={classes.tooltip} - > - -
-
- - } label='All' /> - } label='None' /> - } label='Select' /> - - {selectionMode === "select" && ( - option.displayName} - isOptionEqualToValue={(option, value) => option.organizationId === value.organizationId} - value={organizations.list.filter((org) => - api.visibleOrganizations.includes(org.organizationId) - )} - onChange={handleDropdownChange} - renderOption={(optionProps, option, { selected }) => ( -
  • - - {option.displayName} -
  • - )} - renderInput={(params) => ( - - )} - /> - )} -
    - ); - } -} - -SharedOrganizations.defaultProps = { - organizations: [], - api: PropTypes.shape({}).isRequired, - configDispatcher: PropTypes.func.isRequired, -}; - -export default SharedOrganizations; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Subscriptions/OrganizationSubscriptionPoliciesManage.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/OrganizationSubscriptionPoliciesManage.jsx similarity index 92% rename from portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Subscriptions/OrganizationSubscriptionPoliciesManage.jsx rename to portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/OrganizationSubscriptionPoliciesManage.jsx index f60871ff077..027a17153e6 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Subscriptions/OrganizationSubscriptionPoliciesManage.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/OrganizationSubscriptionPoliciesManage.jsx @@ -16,7 +16,7 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useMemo } from 'react'; import { styled } from '@mui/material/styles'; import Checkbox from '@mui/material/Checkbox'; import Typography from '@mui/material/Typography'; @@ -79,14 +79,25 @@ const Root = styled('div')(( * @returns {JSX.Element} The rendered component */ function OrganizationSubscriptionPoliciesManage(props) { - const { api, organizations, visibleOrganizations, organizationPolicies, setOrganizationPolicies } = props; + const { api, organizations, visibleOrganizations, + organizationPolicies, setOrganizationPolicies, selectionMode } = props; const [filteredOrganizations, setFilteredOrganizations] = useState([]); const [subscriptionPolicies, setSubscriptionPolicies] = useState([]); + + const isAsyncAPI = useMemo(() => + ['WS', 'WEBSUB', 'SSE', 'ASYNC'].includes(api.type), [api.type] + ); useEffect(() => { const limit = Configurations.app.subscriptionPolicyLimit; const isAiApi = api?.subtypeConfiguration?.subtype?.toLowerCase().includes('aiapi') ?? false; - const policyPromise = API.policies('subscription', limit || undefined, isAiApi); + let policyPromise; + + if (isAsyncAPI) { + policyPromise = API.asyncAPIPolicies(); + } else { + policyPromise = API.policies('subscription', limit || undefined, isAiApi); + } policyPromise .then((res) => { @@ -96,12 +107,18 @@ function OrganizationSubscriptionPoliciesManage(props) { console.error(error); }); - if (visibleOrganizations.includes('all')) { - setFilteredOrganizations(organizations); + let updatedOrganizations = []; + if (selectionMode === 'all') { + updatedOrganizations = [ + { organizationId: 'all', displayName: 'All Organizations' }, + ]; } else { - setFilteredOrganizations(organizations.filter(org => visibleOrganizations.includes(org.organizationId))); + updatedOrganizations = organizations.filter(org => visibleOrganizations.includes(org.organizationId)); } - }, [organizations, visibleOrganizations]); + + setFilteredOrganizations(updatedOrganizations); + + }, [organizations, visibleOrganizations, selectionMode]); const handlePolicyChange = (organizationId, selectedPolicies) => { const selectedPolicyNames = selectedPolicies.map(policy => policy.name); @@ -160,7 +177,7 @@ function OrganizationSubscriptionPoliciesManage(props) {
    - Business Plans for Shared Organizations + Business Plans
    diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/ShareAPI.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/ShareAPI.jsx new file mode 100644 index 00000000000..8f598b4963e --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/ShareAPI.jsx @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, {useEffect, useState} from 'react'; +import { styled } from '@mui/material/styles'; +import { Link } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import Grid from '@mui/material/Grid'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import { FormattedMessage, injectIntl, useIntl } from 'react-intl'; +import { CircularProgress } from '@mui/material'; +import Alert from 'AppComponents/Shared/Alert'; +import API from 'AppData/api'; +import { withAPI, useAPI } from 'AppComponents/Apis/Details/components/ApiContext'; +import CONSTS from 'AppData/Constants'; +import { isRestricted } from 'AppData/AuthManager'; +import OrganizationSubscriptionPoliciesManage from './OrganizationSubscriptionPoliciesManage'; +import SharedOrganizations from './SharedOrganizations'; + + +const PREFIX = 'ShareAPI'; + +const classes = { + FormControl: `${PREFIX}-FormControl`, + FormControlOdd: `${PREFIX}-FormControlOdd`, + FormLabel: `${PREFIX}-FormLabel`, + buttonWrapper: `${PREFIX}-buttonWrapper`, + root: `${PREFIX}-root`, + group: `${PREFIX}-group`, + helpButton: `${PREFIX}-helpButton`, + helpIcon: `${PREFIX}-helpIcon`, + htmlTooltip: `${PREFIX}-htmlTooltip`, + buttonSection: `${PREFIX}-buttonSection`, + emptyBox: `${PREFIX}-emptyBox` +} + + +const Root = styled('div')(( + { + theme + } +) => ({ + [`& .${classes.buttonSection}`]: { + marginTop: theme.spacing(2), + }, + + [`& .${classes.emptyBox}`]: { + marginTop: theme.spacing(2), + } +})); + +/** + * ShareAPI component + * + * @class ShareAPI + * @extends {Component} + * @param {Object} props - The properties passed to the component. + * @param {Function} props.updateAPI - Function to update the API. + */ +function ShareAPI(props) { + + const [api] = useAPI(); + const intl = useIntl(); + const { updateAPI } = props; + const restApi = new API(); + const [tenants, setTenants] = useState(null); + const [updateInProgress, setUpdateInProgress] = useState(false); + const [organizationPolicies, setOrganizationPolicies] = useState([]); + const [organizations, setOrganizations] = useState({}); + const [visibleOrganizations, setVisibleOrganizations] = useState(api.visibleOrganizations); + const [selectionMode, setSelectionMode] = useState("none"); + + /** + * Save API sharing information (visible organizations and organization policies) + */ + function saveAPI() { + setUpdateInProgress(true); + + let updatedVisibleOrganizations = []; + let updatedOrganizationPolicies = [...organizationPolicies]; + + if (selectionMode === "all") { + updatedVisibleOrganizations = ["all"]; + updatedOrganizationPolicies = updatedOrganizationPolicies.filter(policy => policy.organizationID === "all"); + } else if (selectionMode === "none") { + updatedVisibleOrganizations = ["none"]; + updatedOrganizationPolicies = []; + } else if (selectionMode === "select") { + updatedVisibleOrganizations = visibleOrganizations; + updatedOrganizationPolicies = updatedOrganizationPolicies.filter(policy => + visibleOrganizations.includes(policy.organizationID) + ); + } + + const newApi = { + visibleOrganizations : updatedVisibleOrganizations, + ...(api.apiType !== API.CONSTS.APIProduct && { organizationPolicies : updatedOrganizationPolicies }), + }; + updateAPI(newApi) + .then(() => { + Alert.info(intl.formatMessage({ + id: 'Apis.Details.ShareAPI.update.success', + defaultMessage: 'API sharing configurations updated successfully', + })); + }) + .catch((error) => { + if (error.response) { + Alert.error(error.response.body.description); + } else { + Alert.error(intl.formatMessage({ + id: 'Apis.Details.ShareAPI.update.error', + defaultMessage: 'Error occurred while updating API sharing configurations', + })); + } + }).finally(() => { + setUpdateInProgress(false); + }); + } + + useEffect(() => { + restApi.getTenantsByState(CONSTS.TENANT_STATE_ACTIVE) + .then((result) => { + setTenants(result.body.count); + }); + restApi.organizations() + .then((result) => { + setOrganizations(result.body); + }) + setOrganizationPolicies(api.organizationPolicies ? [...api.organizationPolicies] : []); + setVisibleOrganizations([...api.visibleOrganizations]); + + if (visibleOrganizations.includes("none")) { + setSelectionMode("none"); + } else if (visibleOrganizations.includes("all")) { + setSelectionMode("all"); + } else { + setSelectionMode("select"); + } + }, []); + + const handleShareAPISave = () => { + saveAPI(); + }; + + if (typeof tenants !== 'number') { + return ( + + + + + + ); + } + return ( + ( + + + + + {(api.gatewayVendor === 'wso2') && + ( + <> + {organizations?.list?.length > 0 && selectionMode !== "none" && + + } + + )} + {(api.gatewayVendor === 'wso2') && ( + + + + + + + + + )} + + ) + ); +} + + +ShareAPI.propTypes = { + api: PropTypes.shape({ + id: PropTypes.string, + }).isRequired, + intl: PropTypes.shape({ + formatMessage: PropTypes.func, + }).isRequired, +}; + +export default injectIntl(withAPI(ShareAPI)); diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/SharedOrganizations.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/SharedOrganizations.jsx new file mode 100644 index 00000000000..0cd9d43d463 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/ShareAPI/SharedOrganizations.jsx @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { styled } from '@mui/material/styles'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import Autocomplete from '@mui/material/Autocomplete'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; +import HelpOutline from '@mui/icons-material/HelpOutline'; +import { + RadioGroup, + FormControlLabel, + FormLabel, + Radio, + TextField, + Checkbox, + Tooltip, + Box, + Paper, +} from "@mui/material"; + +const PREFIX = 'SharedOrganizations'; + +const classes = { + tooltip: `${PREFIX}-tooltip`, + listItemText: `${PREFIX}-listItemText`, + sharedOrganizationsPaper: `${PREFIX}-sharedOrganizationsPaper` +}; + +const StyledBox = styled(Box)(({ theme }) => ({ + [`& .${classes.tooltip}`]: { + top: theme.spacing(1), + marginLeft: theme.spacing(0.5), + }, + + [`& .${classes.listItemText}`]: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + + [`& .${classes.sharedOrganizationsPaper}`]: { + padding: theme.spacing(2), + } +})); + +const icon = ; +const checkedIcon = ; + +/** + * Render the organizations drop down. + * @param {JSON} props props passed from it's parents. + * @returns {JSX} Render the organizations drop down. + */ +function SharedOrganizations(props) { + const { organizations, visibleOrganizations, setVisibleOrganizations, selectionMode, setSelectionMode } = props; + + if (organizations && !organizations.list) { + return null; + } else if (organizations && organizations.list) { + const optionsList = organizations.list; + const handleRadioChange = (event) => { + const { value } = event.target; + setSelectionMode(value); + }; + const handleDropdownChange = (event, newValue) => { + setVisibleOrganizations(newValue.map((org) => org.organizationId)); + }; + + return ( + + + + + + + + + + } + label='Do not share with any organization' /> + +

    + +

    + + )} + aria-label='Shared Organizations' + placement='right-end' + interactive + className={classes.tooltip} + > + +
    +
    + + } label='Share with all organizations' /> + +

    + +

    + + )} + aria-label='Shared Organizations' + placement='right-end' + interactive + className={classes.tooltip} + > + +
    +
    + } + label='Share with only selected organizations' /> +
    + {selectionMode === "select" && ( + option.displayName} + isOptionEqualToValue={(option, value) => option.organizationId === value.organizationId} + value={organizations.list.filter((org) => + visibleOrganizations.includes(org.organizationId) + )} + onChange={handleDropdownChange} + renderOption={(optionProps, option, { selected }) => ( +
  • + + {option.displayName} +
  • + )} + renderInput={(params) => ( + + )} + /> + )} +
    +
    + ); + } +} + +SharedOrganizations.defaultProps = { + organizations: [], + configDispatcher: PropTypes.func.isRequired, +}; + +export default SharedOrganizations; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Subscriptions/Subscriptions.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Subscriptions/Subscriptions.jsx index e245f52e66a..d54deb4026f 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Subscriptions/Subscriptions.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Subscriptions/Subscriptions.jsx @@ -37,7 +37,6 @@ import { useAppContext } from 'AppComponents/Shared/AppContext'; import { isRestricted } from 'AppData/AuthManager'; import SubscriptionsTable from './SubscriptionsTable'; import SubscriptionPoliciesManage from './SubscriptionPoliciesManage'; -import OrganizationSubscriptionPoliciesManage from './OrganizationSubscriptionPoliciesManage'; import SubscriptionAvailability from './SubscriptionAvailability'; const PREFIX = 'Subscriptions'; @@ -85,9 +84,6 @@ function Subscriptions(props) { const { settings } = useAppContext(); const isSubValidationDisabled = api.policies && api.policies.length === 1 && api.policies[0].includes(CONSTS.DEFAULT_SUBSCRIPTIONLESS_PLAN); - const [organizationPolicies, setOrganizationPolicies] = useState([]); - const [organizations, setOrganizations] = useState({}); - const [visibleOrganizations, setVisibleOrganizations] = useState(api.visibleOrganizations); /** * Save subscription information (policies, subscriptionAvailability, subscriptionAvailableTenants) @@ -97,7 +93,6 @@ function Subscriptions(props) { const { subscriptionAvailability } = availability; const newApi = { policies, - ...(settings?.orgAccessControlEnabled && api.apiType !== API.CONSTS.APIProduct && { organizationPolicies }), subscriptionAvailability, subscriptionAvailableTenants: tenantList, }; @@ -131,14 +126,6 @@ function Subscriptions(props) { .then((result) => { setSubscriptions(result.body.count); }); - if (settings && settings.orgAccessControlEnabled && api.apiType !== API.CONSTS.APIProduct) { - restApi.organizations() - .then((result) => { - setOrganizations(result.body.list); - }) - setOrganizationPolicies(api.organizationPolicies ? [...api.organizationPolicies] : []); - setVisibleOrganizations([...api.visibleOrganizations]); - } setPolices([...api.policies]); setOriginalPolicies([...api.policies]); }, []); @@ -175,23 +162,12 @@ function Subscriptions(props) { ( {(api.gatewayVendor === 'wso2') && ( - <> - - {organizations?.length > 0 && - - } - + )} {isSubValidationDisabled && ( diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/components/APIDetailsTopMenu.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/components/APIDetailsTopMenu.jsx index 71e31d8dc58..27be170e2e7 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/components/APIDetailsTopMenu.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/components/APIDetailsTopMenu.jsx @@ -42,6 +42,7 @@ import API from 'AppData/api'; import MUIAlert from 'AppComponents/Shared/MuiAlert'; import DeleteApiButton from './DeleteApiButton'; import CreateNewVersionButton from './CreateNewVersionButton'; +import ShareButton from './ShareButton'; const PREFIX = 'APIDetailsTopMenu'; const classes = { @@ -466,6 +467,12 @@ const APIDetailsTopMenu = (props) => { )} {/* Page error banner */} {/* end of Page error banner */} + {api.apiType !== API.CONSTS.APIProduct && isVisibleInStore + ? <> + + : null + } {api.isRevision || (settings && settings.portalConfigurationOnlyModeEnabled) ? null : <> diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/components/ShareButton.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/components/ShareButton.jsx new file mode 100644 index 00000000000..614a0c7da42 --- /dev/null +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/components/ShareButton.jsx @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { styled } from '@mui/material/styles'; +import IosShareIcon from '@mui/icons-material/IosShare'; +import PropTypes from 'prop-types'; +import { Link, withRouter } from 'react-router-dom'; +import Typography from '@mui/material/Typography'; +import { FormattedMessage } from 'react-intl'; + +import { resourceMethod, resourcePath, ScopeValidation } from 'AppData/ScopeValidation'; +import VerticalDivider from 'AppComponents/Shared/VerticalDivider'; + +const PREFIX = 'ShareButton'; + +const classes = { + root: `${PREFIX}-root`, + backLink: `${PREFIX}-backLink`, + backIcon: `${PREFIX}-backIcon`, + backText: `${PREFIX}-backText`, + shareAPIWrapper: `${PREFIX}-shareAPIWrapper`, + shareAPI: `${PREFIX}-shareAPI`, + linkText: `${PREFIX}-linkText`, +}; + +const Root = styled('div')(({ theme }) => ({ + [`& .${classes.root}`]: { + background: theme.palette.background.paper, + borderBottom: 'solid 1px ' + theme.palette.grey.A200, + display: 'flex', + alignItems: 'center', + }, + [`& .${classes.backLink}`]: { + alignItems: 'center', + textDecoration: 'none', + display: 'flex', + }, + [`& .${classes.backIcon}`]: { + color: theme.palette.primary.main, + fontSize: 56, + cursor: 'pointer', + }, + [`& .${classes.backText}`]: { + color: theme.palette.primary.main, + cursor: 'pointer', + fontFamily: theme.typography.fontFamily, + }, + [`& .${classes.shareAPIWrapper}`]: { + display: 'flex', + justifyContent: 'flex-end', + }, + [`& .${classes.shareAPI}`]: { + display: 'flex', + flexDirection: 'column', + textAlign: 'center', + justifyContent: 'center', + cursor: 'pointer', + color: theme.custom.shareButtonColor || 'inherit', + }, + [`& .${classes.linkText}`]: { + fontSize: theme.typography.fontSize, + }, +})); + +/** + * + * Function to create a 'shareAPI' button + * + * @param {any} props props + * @returns {*} React ShareAPI function component + * @constructor + */ +function ShareButton(props) { + const { api } = props; + return ( + + {/* allowing share API based on scopes */} + +
    + + +
    + +
    + + + + +
    +
    +
    + ); +} + +ShareButton.propTypes = { + api: PropTypes.shape({ + id: PropTypes.string, + }).isRequired, + history: PropTypes.shape({ push: PropTypes.func }).isRequired, +}; + +export default withRouter(ShareButton); diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/index.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/index.jsx index 83089f24f45..abdb89b027d 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/index.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/index.jsx @@ -78,6 +78,7 @@ import Policies from './Policies/Policies'; import ExternalStores from './ExternalStores/ExternalStores'; import { APIProvider } from './components/ApiContext'; import CreateNewVersion from './NewVersion/NewVersion'; +import ShareAPI from './ShareAPI/ShareAPI'; import TryOutConsole from './TryOut/TryOutConsole'; import Compliance from './APICompliance/Compliance'; @@ -1065,6 +1066,8 @@ class Details extends Component { path={Details.subPaths.PROPERTIES_PRODUCT} component={() => } /> + } /> } />