diff --git a/portals/admin/src/main/webapp/site/public/locales/en.json b/portals/admin/src/main/webapp/site/public/locales/en.json index 8fbe43ca97f..42f749c7449 100644 --- a/portals/admin/src/main/webapp/site/public/locales/en.json +++ b/portals/admin/src/main/webapp/site/public/locales/en.json @@ -156,9 +156,8 @@ "Dashboard.rateLimiting.card.subscriptionPolicies.description": "Control access per Subscription", "Dashboard.rateLimiting.card.subscriptionPolicies.name": "Subscription Policies", "Dashboard.rateLimiting.card.title": "Rate Limiting", - "Dashboard.tasksWorkflow.card.task.update.failed": "Task status updated failed", - "Dashboard.tasksWorkflow.card.task.update.success": "Task status updated successfully", "Dashboard.tasksWorkflow.compactTasks.apiProductStateChange.name": "API Product State Change", + "Dashboard.tasksWorkflow.compactTasks.apiRevisionDeployment.name": "API Revision Deployment", "Dashboard.tasksWorkflow.compactTasks.apiStateChange.name": "API State Change", "Dashboard.tasksWorkflow.compactTasks.applicationCreation.name": "Application Creation", "Dashboard.tasksWorkflow.compactTasks.applicationDeletion.name": "Application Deletion", @@ -170,19 +169,6 @@ "Dashboard.tasksWorkflow.compactTasks.subscriptionDeletion.name": "Subscription Deletion", "Dashboard.tasksWorkflow.compactTasks.subscriptionUpdate.name": "Subscription Update", "Dashboard.tasksWorkflow.compactTasks.userCreation.name": "User Creation", - "Dashboard.tasksWorkflow.fewerTasks.card.apiProduct.stateChangeAction.prefix": "State Change Action:", - "Dashboard.tasksWorkflow.fewerTasks.card.application.createdBy.prefix": "Application Created by ", - "Dashboard.tasksWorkflow.fewerTasks.card.application.deletedBy.prefix": "Application Deleted by ", - "Dashboard.tasksWorkflow.fewerTasks.card.registration.creation.keyType.Production": "Production", - "Dashboard.tasksWorkflow.fewerTasks.card.registration.creation.keyType.SandBox": "SandBox", - "Dashboard.tasksWorkflow.fewerTasks.card.registration.key.generated.by": "Key generated by", - "Dashboard.tasksWorkflow.fewerTasks.card.stateChangeAction.prefix": "State Change Action:", - "Dashboard.tasksWorkflow.fewerTasks.card.subscription.deletedBy": "Subscription Deleted by", - "Dashboard.tasksWorkflow.fewerTasks.card.subscription.subscribedBy": "Subscribed by", - "Dashboard.tasksWorkflow.fewerTasks.card.task.accept": "Accept", - "Dashboard.tasksWorkflow.fewerTasks.card.task.reject": "Reject", - "Dashboard.tasksWorkflow.fewerTasks.card.title": "Pending tasks", - "Dashboard.tasksWorkflow.fewerTasks.card.user.createdOn.prefix": "User Created on ", "Dashboard.tasksWorkflow.noTasks.card.description": "Manage workflow tasks, increase productivity and enhance\n competitiveness by enabling developers to easily deploy\n business processes and models.", "Dashboard.tasksWorkflow.noTasks.card.title": "All the pending tasks completed", "GatewayEnvironments.AddEditGWEnvironment.form.description.help": "Description of the Gateway Environment", @@ -614,6 +600,25 @@ "Workflow.APIProductStateChange.List.empty.title.apistatechange": "API Product State Change", "Workflow.APIProductStateChange.apicall.has.errors": "Unable to get workflow pending requests for API Product State Change", "Workflow.APIProductStateChange.updateStatus.has.errors": "Unable to complete API Product state change approve/reject process.", + "Workflow.APIRevisionDeployment.List.empty.content.revisiondeployments": "There are no pending workflow requests for revision deployment.", + "Workflow.APIRevisionDeployment.List.empty.title.revisiondeployments": "Revision Deployment", + "Workflow.APIRevisionDeployment.apiCall.has.errors": "Unable to get workflow pending requests for Revision Deployment", + "Workflow.APIRevisionDeployment.help.link.one": "Create a Revision Deployment approval workflow Request", + "Workflow.APIRevisionDeployment.permission.denied.content": "You do not have enough permission to view Revision Deployment - Approval Tasks. Please contact the site administrator.", + "Workflow.APIRevisionDeployment.permission.denied.title": "Permission Denied", + "Workflow.APIRevisionDeployment.search.default": "Search by Revision Id, API name, Environment or Created by", + "Workflow.APIRevisionDeployment.table.button.approve": "Approve", + "Workflow.APIRevisionDeployment.table.button.reject": "Reject", + "Workflow.APIRevisionDeployment.table.header.Action": "Action", + "Workflow.APIRevisionDeployment.table.header.apiName": "API", + "Workflow.APIRevisionDeployment.table.header.apiVersion": "API", + "Workflow.APIRevisionDeployment.table.header.description": "Description", + "Workflow.APIRevisionDeployment.table.header.environment": "Environment", + "Workflow.APIRevisionDeployment.table.header.revisionId": "Revision Id", + "Workflow.APIRevisionDeployment.table.header.userName": "Created by", + "Workflow.APIRevisionDeployment.title.revisiondeployment": "Revision Deployment - Approval Tasks", + "Workflow.APIRevisionDeployment.update.success": "Workflow status is updated successfully", + "Workflow.APIRevisionDeployment.updateStatus.has.errors": "Unable to complete revision deployment approve/reject process. ", "Workflow.APIStateChange.List.empty.content.apistatechange": "There are no pending workflow requests for API state change.", "Workflow.APIStateChange.List.empty.title.apistatechange": "API State Change", "Workflow.APIStateChange.apicall.has.errors": "Unable to get workflow pending requests for API State Change", diff --git a/portals/admin/src/main/webapp/site/public/locales/fr.json b/portals/admin/src/main/webapp/site/public/locales/fr.json index dbb9e9608d3..bfbfcbed787 100644 --- a/portals/admin/src/main/webapp/site/public/locales/fr.json +++ b/portals/admin/src/main/webapp/site/public/locales/fr.json @@ -156,9 +156,8 @@ "Dashboard.rateLimiting.card.subscriptionPolicies.description": "", "Dashboard.rateLimiting.card.subscriptionPolicies.name": "", "Dashboard.rateLimiting.card.title": "", - "Dashboard.tasksWorkflow.card.task.update.failed": "", - "Dashboard.tasksWorkflow.card.task.update.success": "", "Dashboard.tasksWorkflow.compactTasks.apiProductStateChange.name": "", + "Dashboard.tasksWorkflow.compactTasks.apiRevisionDeployment.name": "", "Dashboard.tasksWorkflow.compactTasks.apiStateChange.name": "", "Dashboard.tasksWorkflow.compactTasks.applicationCreation.name": "", "Dashboard.tasksWorkflow.compactTasks.applicationDeletion.name": "", @@ -170,19 +169,6 @@ "Dashboard.tasksWorkflow.compactTasks.subscriptionDeletion.name": "", "Dashboard.tasksWorkflow.compactTasks.subscriptionUpdate.name": "", "Dashboard.tasksWorkflow.compactTasks.userCreation.name": "", - "Dashboard.tasksWorkflow.fewerTasks.card.apiProduct.stateChangeAction.prefix": "", - "Dashboard.tasksWorkflow.fewerTasks.card.application.createdBy.prefix": "", - "Dashboard.tasksWorkflow.fewerTasks.card.application.deletedBy.prefix": "", - "Dashboard.tasksWorkflow.fewerTasks.card.registration.creation.keyType.Production": "", - "Dashboard.tasksWorkflow.fewerTasks.card.registration.creation.keyType.SandBox": "", - "Dashboard.tasksWorkflow.fewerTasks.card.registration.key.generated.by": "", - "Dashboard.tasksWorkflow.fewerTasks.card.stateChangeAction.prefix": "", - "Dashboard.tasksWorkflow.fewerTasks.card.subscription.deletedBy": "", - "Dashboard.tasksWorkflow.fewerTasks.card.subscription.subscribedBy": "", - "Dashboard.tasksWorkflow.fewerTasks.card.task.accept": "", - "Dashboard.tasksWorkflow.fewerTasks.card.task.reject": "", - "Dashboard.tasksWorkflow.fewerTasks.card.title": "", - "Dashboard.tasksWorkflow.fewerTasks.card.user.createdOn.prefix": "", "Dashboard.tasksWorkflow.noTasks.card.description": "", "Dashboard.tasksWorkflow.noTasks.card.title": "", "GatewayEnvironments.AddEditGWEnvironment.form.description.help": "", @@ -347,6 +333,7 @@ "Manage.Alerts.unsubscribe.success.msg": "", "RolePermissions.Common.AddRoleWizard.add.mapping.button": "", "RolePermissions.Common.AddRoleWizard.add.mapping.title": "", + "RolePermissions.Common.AddRoleWizard.add.role.warn.empty": "", "RolePermissions.Common.AddRoleWizard.add.scope.error": "", "RolePermissions.Common.DeletePermission.delete.scope.error": "", "RolePermissions.ListRoles.error.retrieving.perm": "", @@ -613,6 +600,25 @@ "Workflow.APIProductStateChange.List.empty.title.apistatechange": "", "Workflow.APIProductStateChange.apicall.has.errors": "", "Workflow.APIProductStateChange.updateStatus.has.errors": "", + "Workflow.APIRevisionDeployment.List.empty.content.revisiondeployments": "", + "Workflow.APIRevisionDeployment.List.empty.title.revisiondeployments": "", + "Workflow.APIRevisionDeployment.apiCall.has.errors": "", + "Workflow.APIRevisionDeployment.help.link.one": "", + "Workflow.APIRevisionDeployment.permission.denied.content": "", + "Workflow.APIRevisionDeployment.permission.denied.title": "", + "Workflow.APIRevisionDeployment.search.default": "", + "Workflow.APIRevisionDeployment.table.button.approve": "", + "Workflow.APIRevisionDeployment.table.button.reject": "", + "Workflow.APIRevisionDeployment.table.header.Action": "", + "Workflow.APIRevisionDeployment.table.header.apiName": "", + "Workflow.APIRevisionDeployment.table.header.apiVersion": "", + "Workflow.APIRevisionDeployment.table.header.description": "", + "Workflow.APIRevisionDeployment.table.header.environment": "", + "Workflow.APIRevisionDeployment.table.header.revisionId": "", + "Workflow.APIRevisionDeployment.table.header.userName": "", + "Workflow.APIRevisionDeployment.title.revisiondeployment": "", + "Workflow.APIRevisionDeployment.update.success": "", + "Workflow.APIRevisionDeployment.updateStatus.has.errors": "", "Workflow.APIStateChange.List.empty.content.apistatechange": "", "Workflow.APIStateChange.List.empty.title.apistatechange": "", "Workflow.APIStateChange.apicall.has.errors": "", diff --git a/portals/admin/src/main/webapp/source/src/app/components/AdminPages/Dashboard/TasksWorkflowCard.jsx b/portals/admin/src/main/webapp/source/src/app/components/AdminPages/Dashboard/TasksWorkflowCard.jsx index ead290894c6..6867d7b3e5b 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/AdminPages/Dashboard/TasksWorkflowCard.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/AdminPages/Dashboard/TasksWorkflowCard.jsx @@ -22,7 +22,6 @@ import { Link as RouterLink } from 'react-router-dom'; import { Card } from '@material-ui/core'; import Avatar from '@material-ui/core/Avatar'; import Box from '@material-ui/core/Box'; -import Button from '@material-ui/core/Button'; import CardContent from '@material-ui/core/CardContent'; import Divider from '@material-ui/core/Divider'; import Link from '@material-ui/core/Link'; @@ -34,9 +33,7 @@ import PeopleIcon from '@material-ui/icons/People'; import PermMediaOutlinedIcon from '@material-ui/icons/PhotoSizeSelectActual'; import PublicIcon from '@material-ui/icons/Public'; import SettingsEthernetIcon from '@material-ui/icons/SettingsEthernet'; -import Alert from 'AppComponents/Shared/Alert'; import Api from 'AppData/api'; -import moment from 'moment'; const useStyles = makeStyles((theme) => ({ root: { @@ -101,10 +98,13 @@ export default function TasksWorkflowCard() { const promiseSubUpdate = restApi.workflowsGet('AM_SUBSCRIPTION_UPDATE'); const promiseRegProd = restApi.workflowsGet('AM_APPLICATION_REGISTRATION_PRODUCTION'); const promiseRegSb = restApi.workflowsGet('AM_APPLICATION_REGISTRATION_SANDBOX'); - Promise.all([promiseUserSign, promiseStateChange, promiseAppCreation, promiseAppDeletion, promiseSubCreation, - promiseSubDeletion, promiseSubUpdate, promiseRegProd, promiseRegSb, promiseApiProductStateChange]) + const promiseRevDeployment = restApi.workflowsGet('AM_REVISION_DEPLOYMENT'); + Promise.all([promiseUserSign, promiseStateChange, promiseAppCreation, promiseAppDeletion, + promiseSubCreation, promiseSubDeletion, promiseSubUpdate, promiseRegProd, promiseRegSb, + promiseApiProductStateChange, promiseRevDeployment]) .then(([resultUserSign, resultStateChange, resultAppCreation, resultAppDeletion, resultSubCreation, - resultSubDeletion, resultSubUpdate, resultRegProd, resultRegSb, resultApiProductStateChange]) => { + resultSubDeletion, resultSubUpdate, resultRegProd, resultRegSb, + resultApiProductStateChange, resultRevDeployment]) => { const userCreation = resultUserSign.body.list; const stateChange = resultStateChange.body.list; const productStateChange = resultApiProductStateChange.body.list; @@ -113,6 +113,7 @@ export default function TasksWorkflowCard() { const subscriptionCreation = resultSubCreation.body.list; const subscriptionDeletion = resultSubDeletion.body.list; const subscriptionUpdate = resultSubUpdate.body.list; + const revisionDeployment = resultRevDeployment.body.list; const registration = resultRegProd.body.list.concat(resultRegSb.body.list); setAllTasksSet({ userCreation, @@ -124,6 +125,7 @@ export default function TasksWorkflowCard() { subscriptionUpdate, registration, productStateChange, + revisionDeployment, }); }); }; @@ -280,6 +282,15 @@ export default function TasksWorkflowCard() { }), count: allTasksSet.productStateChange.length, }, + { + icon: SettingsEthernetIcon, + path: '/tasks/api-revision-deploy', + name: intl.formatMessage({ + id: 'Dashboard.tasksWorkflow.compactTasks.apiRevisionDeployment.name', + defaultMessage: 'API Revision Deployment', + }), + count: allTasksSet.revisionDeployment.length, + }, ]; return ( @@ -318,435 +329,10 @@ export default function TasksWorkflowCard() { ); }; - // Approve/Reject button onClick handler - const updateStatus = (referenceId, value) => { - const body = { - status: value, - }; - restApi.updateWorkflow(referenceId, body) - .then(() => { - Alert.success( - , - ); - }) - .catch(() => { - Alert.error( - , - ); - }) - .finally(() => { - fetchAllWorkFlows(); - }); - }; - - // Renders the approve/reject buttons with styles - const getApproveRejectButtons = (referenceId) => { - return ( - - - - - ); - }; - - // Fewer task component's application creation task element - const getApplicationCreationFewerTaskComponent = () => { - // Application Creation tasks related component generation - return allTasksSet.applicationCreation.map((task) => { - return ( - - - - {task.properties.applicationName} - - - - - - -   - {task.properties.userName} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - const getApplicationDeletionFewerTaskComponent = () => { - // Application Creation tasks related component generation - return allTasksSet.applicationDeletion.map((task) => { - return ( - - - - {task.properties.applicationName} - - - - - - -   - {task.properties.userName} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - // Fewer task component's user creation task element - const getUserCreationFewerTaskComponent = () => { - // User Creation tasks related component generation - return allTasksSet.userCreation.map((task) => { - return ( - - - - {task.properties.tenantAwareUserName} - - - - - - -   - {task.properties.tenantDomain} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - // Fewer task component's subscription creation task element - const getSubscriptionCreationFewerTaskComponent = () => { - // Subscription Creation tasks related component generation - return allTasksSet.subscriptionCreation.map((task) => { - return ( - - - - {task.properties.apiName + '-' + task.properties.apiVersion} - - - - {task.properties.applicationName + ','} -   - - - - - -   - {task.properties.subscriber} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - // Fewer task component's subscription creation task element - const getSubscriptionDeletionFewerTaskComponent = () => { - // Subscription Update tasks related component generation - return allTasksSet.subscriptionDeletion.map((task) => { - return ( - - - - {task.properties.apiName + '-' + task.properties.apiVersion} - - - - {task.properties.applicationName + ','} -   - - - - - -   - {task.properties.subscriber} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - // Fewer task component's subscription creation task element - const getSubscriptionUpdateFewerTaskComponent = () => { - // Subscription Update tasks related component generation - return allTasksSet.subscriptionUpdate.map((task) => { - return ( - - - - {task.properties.apiName + '-' + task.properties.apiVersion} - - - - {task.properties.applicationName + ','} -   - - - - - -   - {task.properties.subscriber} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - // Fewer task component's registration creation task element - const getRegistrationCreationFewerTaskComponent = () => { - // Registration Creation tasks related component generation - return allTasksSet.registration.map((task) => { - let keyType; - if (task.properties.keyType === 'PRODUCTION') { - keyType = ( - - ); - } else if (task.properties.keyType === 'SANDBOX') { - keyType = ( - - ); - } else { - keyType = task.properties.keyType; - } - return ( - - - - {task.properties.applicationName} - - - - {keyType} -   - - - - - -   - {task.properties.userName} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - // Fewer task component's api state change task element - const getStateChangeFewerTaskComponent = () => { - // State Change tasks related component generation - return allTasksSet.stateChange.map((task) => { - return ( - - - - {task.properties.apiName + '-' + task.properties.apiVersion} - - - - -   - - - {task.properties.action} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - const getAPIProductStateChangeFewerTaskComponent = () => { - // State Change tasks related component generation - return allTasksSet.productStateChange.map((task) => { - return ( - - - - {task.properties.apiName} - - - - -   - - - {task.properties.action} -   - - - {moment(task.createdTime).fromNow()} - - - - {getApproveRejectButtons(task.referenceId)} - - ); - }); - }; - - // Component to be displayed when there are 4 or less remaining tasks - // Renders some details of the task and approve/reject buttons - const fewerTasksCard = () => { - return ( - - - - - - - - - - - {getAllTaskCount()} - - - - - - {getApplicationCreationFewerTaskComponent()} - {getApplicationDeletionFewerTaskComponent()} - {getUserCreationFewerTaskComponent()} - {getSubscriptionCreationFewerTaskComponent()} - {getSubscriptionDeletionFewerTaskComponent()} - {getSubscriptionUpdateFewerTaskComponent()} - {getRegistrationCreationFewerTaskComponent()} - {getStateChangeFewerTaskComponent()} - {getAPIProductStateChangeFewerTaskComponent()} - - - ); - }; - // Render the card depending on the number of all remaining tasks const cnt = getAllTaskCount(); - if (cnt > 4) { + if (cnt > 0) { return compactTasksCard(); - } else if (cnt > 0) { - return fewerTasksCard(); } else { return noTasksCard; } diff --git a/portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx b/portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx index 5d4d00fceea..97c5ef6e5c7 100644 --- a/portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx +++ b/portals/admin/src/main/webapp/source/src/app/components/Base/RouteMenuMapping.jsx @@ -50,6 +50,7 @@ import SubscriptionDeletion from 'AppComponents/Workflow/SubscriptionDeletion'; import SubscriptionUpdate from 'AppComponents/Workflow/SubscriptionUpdate'; import RegistrationCreation from 'AppComponents/Workflow/RegistrationCreation'; import APIStateChange from 'AppComponents/Workflow/APIStateChange'; +import APIRevisionDeployment from 'AppComponents/Workflow/APIRevisionDeployment'; import UserCreation from 'AppComponents/Workflow/UserCreation'; import AssignmentTurnedInIcon from '@material-ui/icons/AssignmentTurnedIn'; import SecurityIcon from '@material-ui/icons/Security'; @@ -290,6 +291,12 @@ const RouteMenuMapping = (intl) => [ component: () => , icon: , }, + { + id: 'API Revision Deployment', + path: '/tasks/api-revision-deploy', + component: APIRevisionDeployment, + icon: , + }, ], }, { diff --git a/portals/admin/src/main/webapp/source/src/app/components/Workflow/APIRevisionDeployment.jsx b/portals/admin/src/main/webapp/source/src/app/components/Workflow/APIRevisionDeployment.jsx new file mode 100644 index 00000000000..7785450d89c --- /dev/null +++ b/portals/admin/src/main/webapp/source/src/app/components/Workflow/APIRevisionDeployment.jsx @@ -0,0 +1,527 @@ +/* eslint-disable react/jsx-props-no-spreading */ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) 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 { FormattedMessage, useIntl } from 'react-intl'; +import AppBar from '@material-ui/core/AppBar'; +import Toolbar from '@material-ui/core/Toolbar'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import TextField from '@material-ui/core/TextField'; +import Tooltip from '@material-ui/core/Tooltip'; +import IconButton from '@material-ui/core/IconButton'; +import { makeStyles } from '@material-ui/core/styles'; +import SearchIcon from '@material-ui/icons/Search'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; +import MUIDataTable from 'mui-datatables'; +import ContentBase from 'AppComponents/AdminPages/Addons/ContentBase'; +import InlineProgress from 'AppComponents/AdminPages/Addons/InlineProgress'; +import Alert from 'AppComponents/Shared/Alert'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import HelpBase from 'AppComponents/AdminPages/Addons/HelpBase'; +import DescriptionIcon from '@material-ui/icons/Description'; +import Link from '@material-ui/core/Link'; +import Configurations from 'Config'; +import API from 'AppData/api'; +import Button from '@material-ui/core/Button'; +import * as dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import localizedFormat from 'dayjs/plugin/localizedFormat'; +import CheckIcon from '@material-ui/icons/Check'; +import ClearIcon from '@material-ui/icons/Clear'; +import Box from '@material-ui/core/Box'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import WarningBase from 'AppComponents/AdminPages/Addons/WarningBase'; +import { Alert as MUIAlert } from '@material-ui/lab'; + +const useStyles = makeStyles((theme) => ({ + searchInput: { + fontSize: theme.typography.fontSize, + }, + block: { + display: 'block', + }, + contentWrapper: { + margin: theme.spacing(2), + }, + approveButton: { + textDecoration: 'none', + backgroundColor: theme.palette.success.light, + }, + rejectButton: { + textDecoration: 'none', + backgroundColor: theme.palette.error.light, + }, +})); + +/** + * Render a list + * @param {JSON} props props passed from parent + * @returns {JSX} Header AppBar components. + */ +function ListLabels() { + const intl = useIntl(); + const [data, setData] = useState(null); + const restApi = new API(); + const classes = useStyles(); + const [searchText, setSearchText] = useState(''); + const [isUpdating, setIsUpdating] = useState(null); + const [buttonValue, setButtonValue] = useState(); + const [hasListPermission, setHasListPermission] = useState(true); + const [errorMessage, setError] = useState(null); + + /** + * API call to get Detected Data + * @returns {Promise}. + */ + function apiCall() { + return restApi + .workflowsGet('AM_REVISION_DEPLOYMENT') + .then((result) => { + const workflowlist = result.body.list.map((obj) => { + return { + description: obj.description, + apiName: obj.properties.apiName, + apiVersion: obj.properties.apiVersion, + environment: obj.properties.environment, + revisionId: obj.properties.revisionId, + userName: obj.properties.userName, + referenceId: obj.referenceId, + createdTime: obj.createdTime, + properties: obj.properties, + }; + }); + return workflowlist; + }) + .catch((error) => { + const { status } = error; + if (status === 401) { + setHasListPermission(false); + } else { + Alert.error(intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.apiCall.has.errors', + defaultMessage: 'Unable to get workflow pending requests for Revision Deployment', + })); + throw (error); + } + }); + } + + const fetchData = () => { + // Fetch data from backend + setData(null); + const promiseAPICall = apiCall(); + promiseAPICall.then((LocalData) => { + setData(LocalData); + }) + .catch((e) => { + console.error('Unable to fetch data. ', e.message); + setError(e.message); + }); + }; + + useEffect(() => { + fetchData(); + }, []); + + const updateStatus = (referenceId, value) => { + setButtonValue(value); + const body = { status: value, attributes: {}, description: '' }; + setIsUpdating(true); + if (value === 'APPROVED') { + body.description = 'Approve workflow request.'; + } + if (value === 'REJECTED') { + body.description = 'Reject workflow request.'; + } + + const promisedupdateWorkflow = restApi.updateWorkflow(referenceId, body); + return promisedupdateWorkflow + .then(() => { + setIsUpdating(false); + Alert.success(intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.update.success', + defaultMessage: 'Workflow status is updated successfully', + })); + }) + .catch((error) => { + const { response, status } = error; + const { body: { description } } = response; + if (status === 401) { + Alert.error(description); + } else if (response.body) { + Alert.error(intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.updateStatus.has.errors', + defaultMessage: 'Unable to complete revision deployment approve/reject process. ', + })); + throw (response.body.description); + } + setIsUpdating(false); + return null; + }) + .then(() => { + fetchData(); + }); + }; + + const pageProps = { + help: ( + + + + + + + + + )} + /> + + + + ), + + pageStyle: 'half', + title: intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.title.revisiondeployment', + defaultMessage: 'Revision Deployment - Approval Tasks', + }), + }; + + const columProps = [ + { + name: 'description', + label: intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.table.header.description', + defaultMessage: 'Description', + }), + options: { + sort: false, + display: false, + }, + }, + { + name: 'apiName', + label: intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.table.header.apiName', + defaultMessage: 'API', + }), + options: { + sort: false, + filter: true, + }, + }, + { + name: 'apiVersion', + label: intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.table.header.apiVersion', + defaultMessage: 'API', + }), + options: { + sort: false, + filter: true, + }, + }, + { + name: 'revisionId', + label: intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.table.header.revisionId', + defaultMessage: 'Revision Id', + }), + options: { + sort: false, + filter: true, + }, + }, + { + name: 'environment', + label: intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.table.header.environment', + defaultMessage: 'Environment', + }), + options: { + sort: false, + filter: true, + }, + }, + { + name: 'userName', + label: intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.table.header.userName', + defaultMessage: 'Created by', + }), + options: { + sort: false, + customBodyRender: (value, tableMeta) => { + const dataRow = data[tableMeta.rowIndex]; + const { properties } = dataRow; + const { createdTime } = dataRow; + dayjs.extend(relativeTime); + const time = dayjs(createdTime).fromNow(); + dayjs.extend(localizedFormat); + const format = dayjs(createdTime).format('LLL'); + return ( +
+ {properties.userName} +
+ + + {time} + + +
+ ); + }, + }, + }, + { + name: 'action', + label: intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.table.header.Action', + defaultMessage: 'Action', + }), + options: { + sort: false, + customBodyRender: (value, tableMeta) => { + const dataRow = data[tableMeta.rowIndex]; + const { referenceId } = dataRow; + return ( +
+ + +    + + +
+ ); + }, + }, + }, + ]; + + const addButtonProps = {}; + const addButtonOverride = null; + const noDataMessage = ( + + ); + + const searchActive = true; + const searchPlaceholder = intl.formatMessage({ + id: 'Workflow.APIRevisionDeployment.search.default', + defaultMessage: 'Search by Revision Id, API name, Environment or Created by', + }); + + const filterData = (event) => { + setSearchText(event.target.value); + }; + + const columns = [ + ...columProps, + ]; + + const options = { + filterType: 'checkbox', + selectableRows: 'none', + filter: false, + search: false, + print: false, + download: false, + viewColumns: false, + customToolbar: null, + responsive: 'stacked', + searchText, + }; + if (data && data.length === 0) { + return ( + + + + + + + + + + + + {addButtonOverride || ( + // eslint-disable-next-line react/no-unknown-property + + )} + + + + ); + } + if (!hasListPermission) { + return ( + + )} + content={( + + )} + /> + ); + } + if (!errorMessage && !data) { + return ( + + + + + ); + } + if (errorMessage) { + return ( + + {errorMessage} + + + ); + } + return ( + <> + + {(searchActive || addButtonProps) && ( + + + + + {searchActive && ()} + + + {searchActive && ( + + )} + + + {addButtonOverride || ( + + )} + + )} + > + + + + + + + + + )} + {data && data.length > 0 && ( + + )} + {data && data.length === 0 && ( +
+ + {noDataMessage} + +
+ )} +
+ + ); +} + +export default ListLabels; diff --git a/portals/devportal/src/main/webapp/site/public/locales/en.json b/portals/devportal/src/main/webapp/site/public/locales/en.json index badcee029dc..b3f8ae2829f 100644 --- a/portals/devportal/src/main/webapp/site/public/locales/en.json +++ b/portals/devportal/src/main/webapp/site/public/locales/en.json @@ -268,6 +268,9 @@ "Apis.Listing.ApiThumb.by": "By", "Apis.Listing.ApiThumb.by.colon": " : ", "Apis.Listing.ApiThumb.context": "Context", + "Apis.Listing.ApiThumb.owners": "Owners", + "Apis.Listing.ApiThumb.owners.business": "Business", + "Apis.Listing.ApiThumb.owners.technical": "Technical", "Apis.Listing.ApiThumb.version": "Version", "Apis.Listing.CategoryListingCategories.categoriesNotFound": "Categories cannot be found", "Apis.Listing.CategoryListingCategories.title": "API Categories", diff --git a/portals/publisher/src/main/webapp/site/public/locales/en.json b/portals/publisher/src/main/webapp/site/public/locales/en.json index ff89ac03024..0c6833b14dc 100644 --- a/portals/publisher/src/main/webapp/site/public/locales/en.json +++ b/portals/publisher/src/main/webapp/site/public/locales/en.json @@ -637,6 +637,12 @@ "value": "Protocol" } ], + "Apis.Details.\n Environments.Environments.\n pending.chip": [ + { + "type": 0, + "value": "Pending" + } + ], "Apis.Details.\n Environments.deploy.vhost": [ { "type": 0, @@ -1611,6 +1617,12 @@ "value": "Edit Complexity Values" } ], + "Apis.Details.Configuration.ApiKeyHeader.helper.text": [ + { + "type": 0, + "value": "ApiKey header name cannot contain spaces or special characters" + } + ], "Apis.Details.Configuration.AuthHeader.helper.text": [ { "type": 0, @@ -1635,6 +1647,12 @@ "value": "Backend Throughput" } ], + "Apis.Details.Configuration.Configuration.ApiKeyHeader.tooltip": [ + { + "type": 0, + "value": "The header name that is used to send the api key information. \"ApiKey\" is the default header." + } + ], "Apis.Details.Configuration.Configuration.AuthHeader.tooltip": [ { "type": 0, @@ -1665,6 +1683,12 @@ "value": "Edit API Endpoints" } ], + "Apis.Details.Configuration.Configuration.apiKey.header.label": [ + { + "type": 0, + "value": "ApiKey Header" + } + ], "Apis.Details.Configuration.Configuration.auth.header.label": [ { "type": 0, @@ -4193,12 +4217,6 @@ "value": "Deployed Revision" } ], - "Apis.Details.Environments.Environments\n .select.table": [ - { - "type": 0, - "value": "Select Revision" - } - ], "Apis.Details.Environments.Environments\n .select.vhost": [ { "type": 0, @@ -4211,12 +4229,6 @@ "value": "Deploy" } ], - "Apis.Details.Environments.Environments.\n deploy.button": [ - { - "type": 0, - "value": "Deploy" - } - ], "Apis.Details.Environments.Environments.APIGateways": [ { "type": 0, @@ -4259,6 +4271,12 @@ "value": "Name" } ], + "Apis.Details.Environments.Environments.cancel.btn": [ + { + "type": 0, + "value": "Cancel" + } + ], "Apis.Details.Environments.Environments.create.cancel": [ { "type": 0, @@ -4271,6 +4289,12 @@ "value": "Create" } ], + "Apis.Details.Environments.Environments.deploy.button": [ + { + "type": 0, + "value": "Deploy" + } + ], "Apis.Details.Environments.Environments.deploy.cancel": [ { "type": 0, @@ -4319,10 +4343,10 @@ "value": "Display in Developer Portal" } ], - "Apis.Details.Environments.Environments.gateway\n .deployed.revision": [ + "Apis.Details.Environments.Environments.gateway\n .deployment.current.revision": [ { "type": 0, - "value": "Deployed Revision" + "value": "Current Revision" } ], "Apis.Details.Environments.Environments.gateway.accessUrl": [ @@ -4337,6 +4361,18 @@ "value": "Action" } ], + "Apis.Details.Environments.Environments.gateway.deployment.next.revision": [ + { + "type": 0, + "value": "Next Revision" + } + ], + "Apis.Details.Environments.Environments.pending.chip": [ + { + "type": 0, + "value": "Pending" + } + ], "Apis.Details.Environments.Environments.revision\n .description.deploy": [ { "type": 0, @@ -4485,12 +4521,24 @@ "value": "count" } ], + "Apis.Details.Environments.Environments.select.table": [ + { + "type": 0, + "value": "Select Revision" + } + ], "Apis.Details.Environments.Environments.solace.platform.environments.heading": [ { "type": 0, "value": "Solace Platform Environments" } ], + "Apis.Details.Environments.Environments.status.not.deployed": [ + { + "type": 0, + "value": "No Revision Deployed" + } + ], "Apis.Details.Environments.Environments.undeploy.btn": [ { "type": 0, @@ -4627,6 +4675,12 @@ "value": "Endpoint provided" } ], + "Apis.Details.LifeCycle.CheckboxLabels.mandatory.properties.provided": [ + { + "type": 0, + "value": "Mandatory Properties provided" + } + ], "Apis.Details.LifeCycle.LifeCycle.change.not.allowed": [ { "type": 0, @@ -6113,13 +6167,25 @@ "value": "Show in devportal" } ], + "Apis.Details.Properties.\n Properties.editable.show.in.devporal": [ + { + "type": 0, + "value": "Show in devportal" + } + ], + "Apis.Details.Properties.Properties.\n show.add.property.custom.property.name": [ + { + "type": 1, + "value": "message" + } + ], "Apis.Details.Properties.Properties.\n show.add.property.invalid.error": [ { "type": 0, "value": "Invalid property name" } ], - "Apis.Details.Properties.Properties.\n show.add.property.property.name": [ + "Apis.Details.Properties.Properties.\n show.add.property.property.name": [ { "type": 0, "value": "Name" @@ -7919,6 +7985,24 @@ "value": "Context" } ], + "Apis.Listing.ApiThumb.owners": [ + { + "type": 0, + "value": "Owners" + } + ], + "Apis.Listing.ApiThumb.owners.business": [ + { + "type": 0, + "value": "Business" + } + ], + "Apis.Listing.ApiThumb.owners.technical": [ + { + "type": 0, + "value": "Technical" + } + ], "Apis.Listing.ApiThumb.version": [ { "type": 0, @@ -8117,6 +8201,12 @@ "value": "Deploying sample API ..." } ], + "Apis.Listing.SampleAPI.popup.deploy.pending": [ + { + "type": 0, + "value": "API revision deployment request sent" + } + ], "Apis.Listing.SampleAPI.popup.publish.complete": [ { "type": 0, diff --git a/portals/publisher/src/main/webapp/site/public/locales/raw.en.json b/portals/publisher/src/main/webapp/site/public/locales/raw.en.json index 84090bf8c12..aaf9f58c97f 100644 --- a/portals/publisher/src/main/webapp/site/public/locales/raw.en.json +++ b/portals/publisher/src/main/webapp/site/public/locales/raw.en.json @@ -278,6 +278,9 @@ "Apis.Create.streaming.Components.SelectPolicies.business.plans": { "defaultMessage": "Protocol" }, + "Apis.Details.\n Environments.Environments.\n pending.chip": { + "defaultMessage": "Pending" + }, "Apis.Details.\n Environments.deploy.vhost": { "defaultMessage": "VHost" }, @@ -702,6 +705,9 @@ "Apis.Details.Configurartion.components.QueryAnalysis.edit": { "defaultMessage": "Edit Complexity Values" }, + "Apis.Details.Configuration.ApiKeyHeader.helper.text": { + "defaultMessage": "ApiKey header name cannot contain spaces or special characters" + }, "Apis.Details.Configuration.AuthHeader.helper.text": { "defaultMessage": "Authorization header name cannot contain spaces or special characters" }, @@ -714,6 +720,9 @@ "Apis.Details.Configuration.Components.MaxBackendTps.maximum.backend.throughput": { "defaultMessage": "Backend Throughput" }, + "Apis.Details.Configuration.Configuration.ApiKeyHeader.tooltip": { + "defaultMessage": "The header name that is used to send the api key information. \"ApiKey\" is the default header." + }, "Apis.Details.Configuration.Configuration.AuthHeader.tooltip": { "defaultMessage": "The header name that is used to send the authorization information. \"Authorization\" is the default header." }, @@ -729,6 +738,9 @@ "Apis.Details.Configuration.Configuration.Endpoints.edit.api.endpoints": { "defaultMessage": "Edit API Endpoints" }, + "Apis.Details.Configuration.Configuration.apiKey.header.label": { + "defaultMessage": "ApiKey Header" + }, "Apis.Details.Configuration.Configuration.auth.header.label": { "defaultMessage": "Authorization Header" }, @@ -1989,18 +2001,12 @@ "Apis.Details.Environments.\n Environments.gateway.deployed.revision": { "defaultMessage": "Deployed Revision" }, - "Apis.Details.Environments.Environments\n .select.table": { - "defaultMessage": "Select Revision" - }, "Apis.Details.Environments.Environments\n .select.vhost": { "defaultMessage": "Select Access URL" }, "Apis.Details.Environments.Environments\n .deploy.button": { "defaultMessage": "Deploy" }, - "Apis.Details.Environments.Environments.\n deploy.button": { - "defaultMessage": "Deploy" - }, "Apis.Details.Environments.Environments.APIGateways": { "defaultMessage": "API Gateways" }, @@ -2022,12 +2028,18 @@ "Apis.Details.Environments.Environments.api.gateways.name": { "defaultMessage": "Name" }, + "Apis.Details.Environments.Environments.cancel.btn": { + "defaultMessage": "Cancel" + }, "Apis.Details.Environments.Environments.create.cancel": { "defaultMessage": "Cancel" }, "Apis.Details.Environments.Environments.create.create": { "defaultMessage": "Create" }, + "Apis.Details.Environments.Environments.deploy.button": { + "defaultMessage": "Deploy" + }, "Apis.Details.Environments.Environments.deploy.cancel": { "defaultMessage": "Cancel" }, @@ -2052,8 +2064,8 @@ "Apis.Details.Environments.Environments.display.in.devportal": { "defaultMessage": "Display in Developer Portal" }, - "Apis.Details.Environments.Environments.gateway\n .deployed.revision": { - "defaultMessage": "Deployed Revision" + "Apis.Details.Environments.Environments.gateway\n .deployment.current.revision": { + "defaultMessage": "Current Revision" }, "Apis.Details.Environments.Environments.gateway.accessUrl": { "defaultMessage": "Gateway Access URL" @@ -2061,6 +2073,12 @@ "Apis.Details.Environments.Environments.gateway.action": { "defaultMessage": "Action" }, + "Apis.Details.Environments.Environments.gateway.deployment.next.revision": { + "defaultMessage": "Next Revision" + }, + "Apis.Details.Environments.Environments.pending.chip": { + "defaultMessage": "Pending" + }, "Apis.Details.Environments.Environments.revision\n .description.deploy": { "defaultMessage": "Add a description to the revision" }, @@ -2121,9 +2139,15 @@ "Apis.Details.Environments.Environments.select.rev.warning": { "defaultMessage": "Please delete a revision as the number of revisions have reached a maximum of {count}" }, + "Apis.Details.Environments.Environments.select.table": { + "defaultMessage": "Select Revision" + }, "Apis.Details.Environments.Environments.solace.platform.environments.heading": { "defaultMessage": "Solace Platform Environments" }, + "Apis.Details.Environments.Environments.status.not.deployed": { + "defaultMessage": "No Revision Deployed" + }, "Apis.Details.Environments.Environments.undeploy.btn": { "defaultMessage": "Undeploy" }, @@ -2184,6 +2208,9 @@ "Apis.Details.LifeCycle.CheckboxLabels.endpoints.provided": { "defaultMessage": "Endpoint provided" }, + "Apis.Details.LifeCycle.CheckboxLabels.mandatory.properties.provided": { + "defaultMessage": "Mandatory Properties provided" + }, "Apis.Details.LifeCycle.LifeCycle.change.not.allowed": { "defaultMessage": "* You are not authorized to change the life cycle state of the API due to insufficient permissions" }, @@ -2907,10 +2934,16 @@ "Apis.Details.Properties.\n Properties.editable.show.in.devporal": { "defaultMessage": "Show in devportal" }, + "Apis.Details.Properties.\n Properties.editable.show.in.devporal": { + "defaultMessage": "Show in devportal" + }, + "Apis.Details.Properties.Properties.\n show.add.property.custom.property.name": { + "defaultMessage": "{message}" + }, "Apis.Details.Properties.Properties.\n show.add.property.invalid.error": { "defaultMessage": "Invalid property name" }, - "Apis.Details.Properties.Properties.\n show.add.property.property.name": { + "Apis.Details.Properties.Properties.\n show.add.property.property.name": { "defaultMessage": "Name" }, "Apis.Details.Properties.Properties.\n property.name.exists": { @@ -3771,6 +3804,15 @@ "Apis.Listing.ApiThumb.context": { "defaultMessage": "Context" }, + "Apis.Listing.ApiThumb.owners": { + "defaultMessage": "Owners" + }, + "Apis.Listing.ApiThumb.owners.business": { + "defaultMessage": "Business" + }, + "Apis.Listing.ApiThumb.owners.technical": { + "defaultMessage": "Technical" + }, "Apis.Listing.ApiThumb.version": { "defaultMessage": "Version" }, @@ -3870,6 +3912,9 @@ "Apis.Listing.SampleAPI.popup.deploy.inprogress": { "defaultMessage": "Deploying sample API ..." }, + "Apis.Listing.SampleAPI.popup.deploy.pending": { + "defaultMessage": "API revision deployment request sent" + }, "Apis.Listing.SampleAPI.popup.publish.complete": { "defaultMessage": "API published successfully!" }, diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Environments.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Environments.jsx index 58bcc543024..0d69552acbe 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Environments.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Environments/Environments.jsx @@ -764,31 +764,59 @@ export default function Environments() { } /** - * Handles deploy a revision - * @memberof Revisions - */ - function deployRevision(revisionId, envName, vhost, displayOnDevportal) { - const body = [{ - name: envName, - displayOnDevportal, - vhost, - }]; + * Handles canceling a revision deployment workflow + * @memberof Revisions + */ + function cancelRevisionDeploymentWorkflow(revisionId, envName) { if (api.apiType !== API.CONSTS.APIProduct) { - restApi.deployRevision(api.id, revisionId, body) + restApi.cancelRevisionDeploymentWorkflow(api.id, revisionId, envName) .then(() => { - Alert.info('Deploy revision Successfully'); + Alert.info('Revision deployment request cancelled successfully'); }) .catch((error) => { if (error.response) { Alert.error(error.response.body.description); } else { - Alert.error('Something went wrong while deploy the revision'); + Alert.error('Something went wrong while cancelling the revision deployment request'); } console.error(error); }).finally(() => { getRevision(); getDeployedEnv(); }); + } + } + + /** + * Handles deploy a revision + * @memberof Revisions + */ + function deployRevision(revisionId, envName, vhost, displayOnDevportal) { + const body = [{ + name: envName, + displayOnDevportal, + vhost, + }]; + if (api.apiType !== API.CONSTS.APIProduct) { + restApi.deployRevision(api.id, revisionId, body).then((response) => { + if (response && response.obj && response.obj.length > 0) { + if (response.obj[0]?.status === null || response.obj[0]?.status === 'APPROVED') { + Alert.info('Deploy revision successfully'); + } else if (response.obj[0]?.status === 'CREATED') { + Alert.info('Deploy revision request sent successfully'); + } + } + }).catch((error) => { + if (error.response) { + Alert.error(error.response.body.description); + } else { + Alert.error('Something went wrong while deploying the revision'); + } + console.error(error); + }).finally(() => { + getRevision(); + getDeployedEnv(); + }); } else { restProductApi.deployProductRevision(api.id, revisionId, body) .then(() => { @@ -1483,6 +1511,251 @@ export default function Environments() { return endpoints; } + /** + * Get the deployment status component based on the environment and revision status. + * @param {*} row Row + * @param {*} allEnvRevisionMapping All environment revision mapping + * @returns {JSX.Element} The JSX element representing the deployment component + */ + function getDeployedRevisionStatusComponent(row, allEnvRevisionMapping) { + + const deployingGateway = allEnvRevisionMapping.find(gateway => { + return gateway.name === row.name + }); + const gatewayRevisions = deployingGateway?.revisions; + + if (!gatewayRevisions.length) { + // Content to display when there is no revision + return ( +
+ + )} + SelectProps={{ + MenuProps: { + anchorOrigin: { + vertical: 'bottom', + horizontal: 'left', + }, + getContentAnchorEl: null, + }, + }} + name={row.name} + onChange={handleSelect} + margin='dense' + variant='outlined' + style={{width: '50%'}} + disabled={api.isRevision || !allRevisions || allRevisions.length === 0} + > + {allRevisions && allRevisions.length !== 0 && allRevisions.map((number) => ( + {number.displayName} + ))} + + +
+ ); + } + const pendingDeployment = gatewayRevisions.find(revision => { + return revision.deploymentInfo.some(info => info.status === "CREATED" && info.name === row.name); + }); + + const approvedDeployment = gatewayRevisions.find(revision => { + return revision.deploymentInfo.some(info => (info.status === null || info.status === "APPROVED") && + info.name === row.name); + }); + + const filteredRevisions = allRevisions.filter(item => { + if (pendingDeployment && pendingDeployment.displayName === item.displayName) { + return false; + } + if (approvedDeployment && approvedDeployment.displayName === item.displayName) { + return false; + } + return true; + }); + + if (pendingDeployment) { + // Content to display when revision status is created + return ( +
+ + +
+ {pendingDeployment.displayName} +
+ } + style={{backgroundColor: '#FFBF00', width: '100px'}} + /> + + + ); + } + return ( +
+ + )} + SelectProps={{ + MenuProps: { + anchorOrigin: { + vertical: 'bottom', + horizontal: 'left', + }, + getContentAnchorEl: null, + }, + }} + name={row.name} + onChange={handleSelect} + margin='dense' + variant='outlined' + style={{width: '50%'}} + disabled={api.isRevision || !filteredRevisions || filteredRevisions.length === 0} + > + {filteredRevisions && filteredRevisions.length !== 0 && filteredRevisions.map((number) => ( + {number.displayName} + ))} + + +
+ ); + } + + /** + * Get the deployment component based on the environment and revision status. + * @param {*} row Row + * @param {*} allEnvRevisionMapping All environment revision mapping + * @returns {JSX.Element} The JSX element representing the deployment component + */ + function getDeployedRevisionComponent(row, allEnvRevisionMapping) { + const deployingGateway = allEnvRevisionMapping.find(gateway => { + return gateway.name === row.name + }); + const gatewayRevisions = deployingGateway?.revisions; + + if (!gatewayRevisions.length) { + // Content to display when there is no revision + return ( + + ); + + } + + const approvedDeployment = gatewayRevisions.find(revision => { + return revision.deploymentInfo.some(info => (info.status === null || info.status === "APPROVED") && + info.name === row.name); + }); + + + if (approvedDeployment) { + return ( +
+ + +
+ ); + + } + return ( + + ); + } + /** * Get helper text for selected vhost. * @param {*} env Environment @@ -1527,6 +1800,7 @@ export default function Environments() { // allEnvDeployments represents all deployments of the API with mapping // environment -> {revision deployed to env, vhost deployed to env with revision} const allEnvDeployments = Utils.getAllEnvironmentDeployments(settings.environment, allEnvRevision); + const allEnvRevisionMapping = Utils.getAllEnvironmentRevisions(settings.environment, allEnvRevision); return ( <> @@ -1730,6 +2004,13 @@ export default function Environments() { {internalGateways && internalGateways.map((row) => ( { + return o1.deploymentInfo.some(o2 => + o2.name === row.name && + o2.status === 'CREATED'); + }) ? '0.6' : '1' + }} className={clsx(SelectedEnvironment && SelectedEnvironment.includes(row.name) ? (classes.changeCard) @@ -1738,19 +2019,42 @@ export default function Environments() { > } - checkedIcon={} - inputProps={{ 'aria-label': 'secondary checkbox' }} - data-testid={row.displayName + 'gateway-select-btn'} - /> - )} + action={ + <> + {allEnvRevision.some(o1 => { + return o1.deploymentInfo.some(o2 => + o2.name === row.name && + o2.status === 'CREATED'); + }) ? + ( + } + checkedIcon={< + CheckCircleIcon color='primary'/>} + disabled + /> + ) : + ( + } + checkedIcon={< + CheckCircleIcon color='primary'/>} + inputProps={{ + 'aria-label': 'secondary checkbox' + }} + data-testid={ + row.displayName + 'gateway-select-btn'} + /> + ) + } + } title={( {row.displayName} @@ -1816,9 +2120,9 @@ export default function Environments() { > {row.vhosts.map( (vhost) => ( - - {api.isWebSocket() + {api.isWebSocket() ? vhost.wsHost : vhost.host} ), @@ -1828,40 +2132,64 @@ export default function Environments() { {allEnvRevision - && allEnvRevision.filter( - (o1) => { - if (o1.deploymentInfo.filter( - (o2) => o2.name === row.name, - ).length > 0) { + .filter(o1 => { + if (o1.deploymentInfo.some( + o2 => o2.name === row.name && + (o2.status === null || + o2.status === 'APPROVED'))) { return o1; } return null; - }, - ).length !== 0 ? ( - allEnvRevision && allEnvRevision.filter( - (o1) => { - if (o1.deploymentInfo.filter( - (o2) => o2.name === row.name, - ).length > 0) { - return o1; + }) + .map(o3 => ( +
+ +
+ ))} +
+ + {allEnvRevision + .filter(o1 => { + if (o1.deploymentInfo.some( + o2 => o2.name === row.name && + o2.status === 'CREATED')) { + return o1; + } + return null; + }) + .map(o3 => ( +
+ + +
+ {o3.displayName} +
} - return null; - }, - ).map((o3) => ( -
- -
- ))) : ( - // eslint-disable-next-line react/jsx-indent -
- )} + style={{ + backgroundColor: '#FFBF00', + width: '100px' + }} + /> +
+ ))}
- + @@ -2198,8 +2526,8 @@ export default function Environments() { ) @@ -2211,6 +2539,12 @@ export default function Environments() { /> )} + + + )} + + {getDeployedRevisionComponent(row, allEnvRevisionMapping)} + - {allEnvDeployments[row.name].revision != null - ? ( -
- - -
- ) : ( -
- - )} - SelectProps={{ - MenuProps: { - anchorOrigin: { - vertical: 'bottom', - horizontal: 'left', - }, - getContentAnchorEl: null, - }, - }} - name={row.name} - onChange={handleSelect} - margin='dense' - variant='outlined' - style={{ width: '50%' }} - disabled={api.isRevision - || !allRevisions || allRevisions.length === 0} - > - {allRevisions && allRevisions.length !== 0 - && allRevisions.map( - (number) => ( - - {number.displayName} - - ), - )} - - -
- )} + {getDeployedRevisionStatusComponent(row, allEnvRevisionMapping)}
{ api.getRevisionsWithEnv(api.isRevision ? api.revisionedApiId : api.id).then((result) => { - setDeploymentsAvailable(result.body.count > 0); + if (api.apiType === API.CONSTS.APIProduct){ + setDeploymentsAvailable(result.body.count > 0); + } else { + let hasApprovedDeployment = false; + result.body.list.forEach((revision) => { + if (revision.deploymentInfo) { + for (const deployment of revision.deploymentInfo) { + if (deployment.status === 'APPROVED') { + hasApprovedDeployment = true; + break; + } + } + } + }); + setDeploymentsAvailable(hasApprovedDeployment); + + } + }); }, []); diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/TryOut/TryOutConsole.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/TryOut/TryOutConsole.jsx index 357534edfb6..1a8fd151f0b 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/TryOut/TryOutConsole.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/TryOut/TryOutConsole.jsx @@ -128,11 +128,12 @@ const TryOutConsole = () => { api.getDeployedRevisions(api.id).then((deploymentsResponse) => { tasksStatusDispatcher({ name: 'getDeployments', status: { inProgress: false, completed: true } }); const currentDeployments = deploymentsResponse.body; - const currentDeploymentsWithDisplayName = currentDeployments.map((deploy) => { - const gwEnvironment = publisherSettings.environment.find((e) => e.name === deploy.name); - const displayName = (gwEnvironment ? gwEnvironment.displayName : deploy.name); - return { ...deploy, displayName }; - }); + const currentDeploymentsWithDisplayName = currentDeployments + .filter(deploy => deploy.status !== 'CREATED').map((deploy) => { + const gwEnvironment = publisherSettings.environment.find((e) => e.name === deploy.name); + const displayName = (gwEnvironment ? gwEnvironment.displayName : deploy.name); + return { ...deploy, displayName }; + }); setDeployments(currentDeploymentsWithDisplayName); if (currentDeploymentsWithDisplayName && currentDeploymentsWithDisplayName.length > 0) { const [initialDeploymentSelection] = currentDeploymentsWithDisplayName; diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Listing/SampleAPI/SampleAPI.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Listing/SampleAPI/SampleAPI.jsx index 724597b7742..65cf856af6d 100644 --- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Listing/SampleAPI/SampleAPI.jsx +++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Listing/SampleAPI/SampleAPI.jsx @@ -51,11 +51,11 @@ const useStyles = makeStyles({ }); const initialTaskStates = { - create: { inProgress: true, completed: false, errors: false }, - update: { inProgress: false, completed: false, errors: false }, - revision: { inProgress: false, completed: false, errors: false }, - deploy: { inProgress: false, completed: false, errors: false }, - publish: { inProgress: false, completed: false, errors: false }, + create: { inProgress: true, completed: false, pending: false, errors: false }, + update: { inProgress: false, completed: false, pending: false, errors: false }, + revision: { inProgress: false, completed: false, pending: false, errors: false }, + deploy: { inProgress: false, completed: false, pending: false, errors: false }, + publish: { inProgress: false, completed: false, pending: false, errors: false }, }; const tasksReducer = (state, action) => { @@ -94,6 +94,10 @@ const SampleAPI = (props) => { throw errors; } tasksStatusDispatcher({ name, status: { inProgress: false, completed: true } }); + if (taskResult.body?.[0]?.status === 'CREATED') { + tasksStatusDispatcher({ name, status: { pending: true} }); + } + return taskResult; }; /** @@ -284,9 +288,16 @@ const SampleAPI = (props) => { Revision API + )} completedMessage={( ({ + ...environment, + revisions: allEnvRevisions + .filter(revision => + revision.deploymentInfo.some(info => info.name === environment.name) + ) + })); + } } Utils.CONST = { diff --git a/portals/publisher/src/main/webapp/source/src/app/data/api.js b/portals/publisher/src/main/webapp/source/src/app/data/api.js index 9dc6a9eb9ff..47e65bd293c 100644 --- a/portals/publisher/src/main/webapp/source/src/app/data/api.js +++ b/portals/publisher/src/main/webapp/source/src/app/data/api.js @@ -2164,6 +2164,26 @@ class API extends Resource { }); } + /** + * Cleanup pending workflow revision deployment task for API given its id (UUID) and revision id (UUID) + * @param apiId {string} UUID of the api + * @param revisionID {string} UUID of the revision + * @param callback {function} Callback function which needs to be executed in the success call + */ + cancelRevisionDeploymentWorkflow(apiId, revisionID, envName, callback = null) { + const promise_deletePendingTask = this.client.then(client => { + return client.apis['API Revisions'].deleteAPIRevisionDeploymentPendingTask( + { + apiId: apiId, + revisionId: revisionID, + envName: envName + }, + this._requestMetaData(), + ); + }); + return promise_deletePendingTask; + } + /** * Change displayInDevportal. *