From e8a0a771a6769c1ea3478d2b26706a22410f3d06 Mon Sep 17 00:00:00 2001 From: brig Date: Sun, 10 Sep 2023 10:19:36 +0200 Subject: [PATCH 01/19] init --- .../organisms/UserProcessActivity/index.tsx | 93 +++++++++++++++++-- .../organisms/UserProcessActivity/styles.css | 28 ++++++ .../pages/UserActivityPage/index.tsx | 2 +- 3 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 console2/src/components/organisms/UserProcessActivity/styles.css diff --git a/console2/src/components/organisms/UserProcessActivity/index.tsx b/console2/src/components/organisms/UserProcessActivity/index.tsx index 058e8e1730..d79dabb30c 100644 --- a/console2/src/components/organisms/UserProcessActivity/index.tsx +++ b/console2/src/components/organisms/UserProcessActivity/index.tsx @@ -27,7 +27,7 @@ import { ProjectProcesses, UserActivity } from '../../../api/service/console/user'; -import { Header } from 'semantic-ui-react'; +import {Button, Card, CardGroup, Header, Icon, Image} from 'semantic-ui-react'; import { MAX_CARD_ITEMS, OrgProjects } from '../../molecules/UserProcessByOrgCards'; import { ProcessList } from '../../molecules/index'; import { StatusCount } from '../../molecules/UserProcessStats'; @@ -45,6 +45,8 @@ import { useCallback, useEffect, useState } from 'react'; import { useApi } from '../../../hooks/useApi'; import { LoadingDispatch } from '../../../App'; import RequestErrorActivity from '../RequestErrorActivity'; +import {Link} from "react-router-dom"; +import './styles.css'; export interface ExternalProps { forceRefresh: any; @@ -73,6 +75,12 @@ const UserProcesses = ({ forceRefresh }: ExternalProps) => { const [processStats, setProcessStats] = useState(DEFAULT_STATS); const [processByOrg, setProcessByOrg] = useState(); const [processes, setProcesses] = useState(); + const [processCardsShow, toggleProcessCardsShow] = useState(true); + + const processCardsShowHandler = useCallback(() => { + toggleProcessCardsShow((prevState) => !prevState); + }, []); + const fetchData = useCallback(() => { return apiGetActivity(MAX_CARD_ITEMS + 1, MAX_OWN_PROCESSES); @@ -98,18 +106,85 @@ const UserProcesses = ({ forceRefresh }: ExternalProps) => { <> {error && } -
- Your processes: +
+ Actions +
- -
- Running projects: -
- +
+ + {processCardsShow && + + + {/**/} + + Deploy Prod + + + + Project: test-project + + + + Test description + + + + +
+ +
+
+
+ + + {/**/} + + Deploy Test + + + + Project: test-project + + + + Test description + + + + +
+ +
+
+
+ + + {/**/} + + Any other process + + + + Project: test-project + + + + Test description + + + + +
+ +
+
+
+
} +
- Your last {MAX_OWN_PROCESSES} processes: + Your last {MAX_OWN_PROCESSES} processes
.card { + border: 1px solid #D4D4ED; + box-shadow: none; + border-radius: 0; +} + +.no-margin-top { + margin-top: 0 !important; +} diff --git a/console2/src/components/pages/UserActivityPage/index.tsx b/console2/src/components/pages/UserActivityPage/index.tsx index 91ea73ba6f..48bf74a59a 100644 --- a/console2/src/components/pages/UserActivityPage/index.tsx +++ b/console2/src/components/pages/UserActivityPage/index.tsx @@ -38,7 +38,7 @@ const UserActivityPage = () => { return ( <> - Activity today + Activity From 3f19d08ef4ff5c23c6f9d18ea78b20ddb7f81228 Mon Sep 17 00:00:00 2001 From: brig Date: Sun, 10 Sep 2023 22:10:09 +0200 Subject: [PATCH 02/19] impl --- client/pom.xml | 3 + .../src/api/service/console/user/index.ts | 39 ++-- .../molecules/UserProcessByOrgCards/index.tsx | 104 --------- .../molecules/UserProcessStats/index.tsx | 62 ----- console2/src/components/molecules/index.tsx | 2 - .../organisms/UserProcessActivity/index.tsx | 211 +++++------------- .../concord/server/db/liquibase.xml | 1 + .../walmartlabs/concord/server/db/v2.1.0.xml | 96 ++++++++ .../concord/server/console/ConsoleModule.java | 2 +- .../server/console/ProcessCardEntry.java | 70 ++++++ .../server/console/UserActivityResource.java | 181 --------------- .../console/UserActivityResourceV2.java | 154 +++++++++++++ 12 files changed, 403 insertions(+), 522 deletions(-) delete mode 100644 console2/src/components/molecules/UserProcessByOrgCards/index.tsx delete mode 100644 console2/src/components/molecules/UserProcessStats/index.tsx create mode 100644 server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml create mode 100644 server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java delete mode 100644 server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResource.java create mode 100644 server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java diff --git a/client/pom.xml b/client/pom.xml index 6a1b972110..411f877aa7 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -169,6 +169,9 @@ java.method.returnTypeTypeParametersChanged + + java.method.numberOfParametersChanged + diff --git a/console2/src/api/service/console/user/index.ts b/console2/src/api/service/console/user/index.ts index 8df52b4979..da92dbb1c4 100644 --- a/console2/src/api/service/console/user/index.ts +++ b/console2/src/api/service/console/user/index.ts @@ -2,7 +2,7 @@ * ***** * Concord * ----- - * Copyright (C) 2017 - 2018 Walmart Inc. + * Copyright (C) 2023 - 2018 Walmart Inc. * ----- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,30 +18,33 @@ * ===== */ -import { ConcordKey, fetchJson, queryParams } from '../../../common'; -import { ProcessEntry, ProcessStatus } from '../../../process'; - -export interface ProjectProcesses { - projectName: ConcordKey; - running: number; -} +import {ConcordId, ConcordKey, fetchJson, queryParams} from '../../../common'; +import { ProcessEntry } from '../../../process'; export interface UserActivity { - processStats: { - status: ProcessStatus; - count: number; - }; - orgProcesses?: { - orgName: ConcordKey; - processes: ProjectProcesses[]; - }; processes: ProcessEntry[]; } +export interface ProcessCardEntry { + id: ConcordId; + orgName: ConcordKey; + projectName: ConcordKey; + repoName: ConcordKey; + entryPoint: string; + name: string; + description?: string; + icon?: string; +} + export const getActivity = ( - maxProjectsPerOrg: number, maxOwnProcesses: number ): Promise => fetchJson( - `/api/service/console/user/activity?${queryParams({ maxProjectsPerOrg, maxOwnProcesses })}` + `/api/v2/service/console/user/activity?${queryParams({ maxOwnProcesses })}` ); + +export const listProcessCards = ( +): Promise => + fetchJson( + `/api/v2/service/console/user/process-card` + ); \ No newline at end of file diff --git a/console2/src/components/molecules/UserProcessByOrgCards/index.tsx b/console2/src/components/molecules/UserProcessByOrgCards/index.tsx deleted file mode 100644 index c277228333..0000000000 --- a/console2/src/components/molecules/UserProcessByOrgCards/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/*- - * ***** - * Concord - * ----- - * Copyright (C) 2017 - 2018 Walmart Inc. - * ----- - * Licensed 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 * as React from 'react'; -import { ConcordKey, queryParams } from '../../../api/common'; -import { Link } from 'react-router-dom'; -import { ProjectProcesses } from '../../../api/service/console/user'; -import { RedirectButton } from '../../organisms'; -import { ProcessStatus } from '../../../api/process'; - -export const MAX_CARD_ITEMS = 5; - -export interface OrgProjects { - orgName: ConcordKey; - projects: ProjectProcesses[]; -} - -interface Props { - items?: OrgProjects[]; -} - -const renderProject = (orgName: ConcordKey, project: ProjectProcesses) => { - return ( -
-
{project.running}
-
- - {project.projectName} - -
-
- ); -}; - -const renderCard = (orgName: ConcordKey, projects: ProjectProcesses[]) => { - return ( -
-
-
- - {orgName} - -
-
-
-
- {projects.slice(0, MAX_CARD_ITEMS).map((p) => renderProject(orgName, p))} -
-
- {projects.length > MAX_CARD_ITEMS && ( - - )} -
- ); -}; - -class UserProcessByOrgCards extends React.PureComponent { - render() { - const { items } = this.props; - if (items === undefined) { - return 'Loading'; - } - - if (items.length === 0) { - return 'No processes found.'; - } - - return ( -
{items.map((i) => renderCard(i.orgName, i.projects))}
- ); - } -} - -export default UserProcessByOrgCards; diff --git a/console2/src/components/molecules/UserProcessStats/index.tsx b/console2/src/components/molecules/UserProcessStats/index.tsx deleted file mode 100644 index 3015ad37b6..0000000000 --- a/console2/src/components/molecules/UserProcessStats/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/*- - * ***** - * Concord - * ----- - * Copyright (C) 2017 - 2018 Walmart Inc. - * ----- - * Licensed 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 * as React from 'react'; -import { Link } from 'react-router-dom'; -import { getStatusSemanticColor, ProcessStatus } from '../../../api/process'; -import { queryParams } from '../../../api/common'; -import { useContext } from 'react'; -import { UserSessionContext } from '../../../session'; - -export interface StatusCount { - status: ProcessStatus; - count: number; -} - -interface Props { - items: StatusCount[]; -} - -const renderItem = (initiator: string, status: ProcessStatus, count: number) => { - return ( -
0 ? getStatusSemanticColor(status) : 'grey')} - key={status}> -
{count > 0 ? count : 0}
-
- {count > 0 ? ( - {status} - ) : ( - status - )} -
-
- ); -}; - -export default ({ items }: Props) => { - const { userInfo } = useContext(UserSessionContext); - - return ( -
- {items.map((v) => renderItem(userInfo!.username, v.status, v.count))} -
- ); -}; diff --git a/console2/src/components/molecules/index.tsx b/console2/src/components/molecules/index.tsx index e57d93e639..2b786d38de 100644 --- a/console2/src/components/molecules/index.tsx +++ b/console2/src/components/molecules/index.tsx @@ -63,8 +63,6 @@ export { default as SingleOperationPopup } from './SingleOperationPopup'; export { default as TeamAccessDropdown } from './TeamAccessDropdown'; export { default as TeamAccessList } from './TeamAccessList'; export { default as TeamRoleDropdown } from './TeamRoleDropdown'; -export { default as UserProcessByOrgCards } from './UserProcessByOrgCards'; -export { default as UserProcessStats } from './UserProcessStats'; export { default as WithCopyToClipboard } from './WithCopyToClipboard'; // https://github.com/facebook/create-react-app/issues/6054 diff --git a/console2/src/components/organisms/UserProcessActivity/index.tsx b/console2/src/components/organisms/UserProcessActivity/index.tsx index d79dabb30c..7ab1b41cdd 100644 --- a/console2/src/components/organisms/UserProcessActivity/index.tsx +++ b/console2/src/components/organisms/UserProcessActivity/index.tsx @@ -20,19 +20,13 @@ import * as React from 'react'; -import { ConcordKey } from '../../../api/common'; -import { UserProcessStats, UserProcessByOrgCards } from '../../molecules'; import { getActivity as apiGetActivity, - ProjectProcesses, - UserActivity + listProcessCards as apiListProcessCards, ProcessCardEntry } from '../../../api/service/console/user'; -import {Button, Card, CardGroup, Header, Icon, Image} from 'semantic-ui-react'; -import { MAX_CARD_ITEMS, OrgProjects } from '../../molecules/UserProcessByOrgCards'; +import { Button, Card, CardGroup, Header, Icon, Image } from 'semantic-ui-react'; import { ProcessList } from '../../molecules/index'; -import { StatusCount } from '../../molecules/UserProcessStats'; -import { ProcessEntry, ProcessStatus } from '../../../api/process'; -import { comparators } from '../../../utils'; +import { ProcessEntry } from '../../../api/process'; import { CREATED_AT_COLUMN, INITIATOR_COLUMN, @@ -54,39 +48,64 @@ export interface ExternalProps { const MAX_OWN_PROCESSES = 10; -const DEFAULT_STATS: StatusCount[] = [ - { status: ProcessStatus.ENQUEUED, count: -1 }, - { status: ProcessStatus.RUNNING, count: -1 }, - { status: ProcessStatus.SUSPENDED, count: -1 }, - { status: ProcessStatus.FINISHED, count: -1 }, - { status: ProcessStatus.FAILED, count: -1 } -]; - -const statusOrder = [ - ProcessStatus.RUNNING, - ProcessStatus.SUSPENDED, - ProcessStatus.FINISHED, - ProcessStatus.FAILED -]; +const renderCard = (card: ProcessCardEntry) => { + return ( + + + {card.icon && } + {card.name} + + + Project: {card.projectName} + + + {card.description} + + + +
+ +
+
+
+ ); +}; + +const renderCards = (cards: ProcessCardEntry[]) => { + if (cards.length === 0) { + return; + } + + return ( + + {cards.map((card) => renderCard(card))} + + ); +}; + +interface ActivityEntry { + processes?: ProcessEntry[]; + cards?: ProcessCardEntry[]; +} const UserProcesses = ({ forceRefresh }: ExternalProps) => { const dispatch = React.useContext(LoadingDispatch); - const [processStats, setProcessStats] = useState(DEFAULT_STATS); - const [processByOrg, setProcessByOrg] = useState(); const [processes, setProcesses] = useState(); + const [processCards, setProcessCards] = useState(); const [processCardsShow, toggleProcessCardsShow] = useState(true); const processCardsShowHandler = useCallback(() => { toggleProcessCardsShow((prevState) => !prevState); }, []); - - const fetchData = useCallback(() => { - return apiGetActivity(MAX_CARD_ITEMS + 1, MAX_OWN_PROCESSES); + const fetchData = useCallback(async () => { + const activity = await apiGetActivity(MAX_OWN_PROCESSES); + const cards = await apiListProcessCards(); + return { processes: activity.processes, cards} }, []); - const { data, error } = useApi(fetchData, { + const { data, error } = useApi(fetchData, { fetchOnMount: true, forceRequest: forceRefresh, dispatch: dispatch @@ -97,91 +116,22 @@ const UserProcesses = ({ forceRefresh }: ExternalProps) => { return; } - setProcessStats(makeProcessStatsList(data?.processStats)); - setProcessByOrg(makeProcessByOrgList(data?.orgProcesses)); - setProcesses(data?.processes); + setProcesses(data.processes); + setProcessCards(data.cards) }, [data]); return ( <> {error && } -
- Actions - -
+ {processCards && processCards.length > 0 && +
+ Actions + +
+ } -
- - {processCardsShow && - - - {/**/} - - Deploy Prod - - - - Project: test-project - - - - Test description - - - - -
- -
-
-
- - - {/**/} - - Deploy Test - - - - Project: test-project - - - - Test description - - - - -
- -
-
-
- - - {/**/} - - Any other process - - - - Project: test-project - - - - Test description - - - - -
- -
-
-
-
} -
+ {processCardsShow && processCards && renderCards(processCards)}
Your last {MAX_OWN_PROCESSES} processes @@ -201,51 +151,4 @@ const UserProcesses = ({ forceRefresh }: ExternalProps) => { ); }; -const sort = (orgProcess: ProjectProcesses[]): ProjectProcesses[] => - orgProcess.sort(comparators.byProperty((i) => i.projectName)); - -const makeProcessByOrgList = (orgProcesses?: { - orgName: ConcordKey; - processes: ProjectProcesses[]; -}): OrgProjects[] => { - if (orgProcesses) { - return Object.keys(orgProcesses) - .map((k) => ({ orgName: k, projects: sort(orgProcesses[k]) })) - .sort((a, b) => b.projects.length - a.projects.length); - } else { - return []; - } -}; - -const makeProcessStatsList = (processStats?: { - status: ProcessStatus; - count: number; -}): StatusCount[] => { - if (processStats === undefined) { - return DEFAULT_STATS; - } - - return Object.keys(processStats) - .map((k) => ({ status: k as ProcessStatus, count: processStats[k] })) - .sort((a, b) => { - const i1 = statusOrder.indexOf(a.status); - const i2 = statusOrder.indexOf(b.status); - return i1 - i2; - }); -}; - export default UserProcesses; -// -// const mapStateToProps = ({ userActivity }: { userActivity: State }): StateProps => ({ -// loading: userActivity.loading, -// error: userActivity.error, -// userActivity: userActivity.getUserActivity.response -// ? userActivity.getUserActivity.response.activity -// ? userActivity.getUserActivity.response.activity -// : undefined -// : undefined -// }); -// -// const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ -// load: () => dispatch(actions.getUserActivity(MAX_CARD_ITEMS + 1, MAX_OWN_PROCESSES)) -// }); diff --git a/server/db/src/main/resources/com/walmartlabs/concord/server/db/liquibase.xml b/server/db/src/main/resources/com/walmartlabs/concord/server/db/liquibase.xml index 43d783c306..bd7024b5af 100644 --- a/server/db/src/main/resources/com/walmartlabs/concord/server/db/liquibase.xml +++ b/server/db/src/main/resources/com/walmartlabs/concord/server/db/liquibase.xml @@ -110,5 +110,6 @@ + diff --git a/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml b/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml new file mode 100644 index 0000000000..1e09932bd6 --- /dev/null +++ b/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ConsoleModule.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ConsoleModule.java index c261e7c74d..fd1965c157 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ConsoleModule.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ConsoleModule.java @@ -36,7 +36,7 @@ public void configure(Binder binder) { binder.bind(CustomFormServiceV1.class).in(SINGLETON); binder.bind(CustomFormServiceV2.class).in(SINGLETON); - bindJaxRsResource(binder, UserActivityResource.class); + bindJaxRsResource(binder, UserActivityResourceV2.class); bindJaxRsResource(binder, CustomFormService.class); bindJaxRsResource(binder, ConsoleService.class); diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java new file mode 100644 index 0000000000..e0a2d2faa7 --- /dev/null +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java @@ -0,0 +1,70 @@ +package com.walmartlabs.concord.server.console; + +/*- + * ***** + * Concord + * ----- + * Copyright (C) 2017 - 2023 Walmart Inc. + * ----- + * Licensed 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 com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.walmartlabs.concord.common.validation.ConcordKey; +import com.walmartlabs.concord.server.org.ImmutableEntityOwner; +import org.immutables.value.Value; + +import javax.annotation.Nullable; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.UUID; + +@Value.Immutable +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonSerialize(as = ImmutableProcessCardEntry.class) +@JsonDeserialize(as = ImmutableProcessCardEntry.class) +public interface ProcessCardEntry extends Serializable { + + long serialVersionUID = 1L; + + UUID id(); + + @ConcordKey + String orgName(); + + @ConcordKey + String projectName(); + + @ConcordKey + String repoName(); + + @Size(max = 256) + String entryPoint(); + + @Size(max = 128) + String name(); + + @Size(max = 512) + @Nullable + String description(); + + @Nullable + String icon(); + + static ImmutableProcessCardEntry.Builder builder() { + return ImmutableProcessCardEntry.builder(); + } +} diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResource.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResource.java deleted file mode 100644 index 9451a328d8..0000000000 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResource.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.walmartlabs.concord.server.console; - -/*- - * ***** - * Concord - * ----- - * Copyright (C) 2017 - 2018 Walmart Inc. - * ----- - * Licensed 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 com.walmartlabs.concord.db.AbstractDao; -import com.walmartlabs.concord.db.MainDB; -import com.walmartlabs.concord.server.process.ProcessEntry; -import com.walmartlabs.concord.server.process.queue.ProcessFilter; -import com.walmartlabs.concord.server.process.queue.ProcessQueueDao; -import com.walmartlabs.concord.server.sdk.ProcessStatus; -import com.walmartlabs.concord.server.sdk.metrics.WithTimer; -import com.walmartlabs.concord.server.security.UserPrincipal; -import com.walmartlabs.concord.server.user.UserDao; -import org.jooq.*; -import org.sonatype.siesta.Resource; - -import javax.inject.Inject; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.util.*; -import java.util.stream.Collectors; - -import static com.walmartlabs.concord.server.console.UserActivityResponse.ProjectProcesses; -import static com.walmartlabs.concord.server.jooq.tables.Organizations.ORGANIZATIONS; -import static com.walmartlabs.concord.server.jooq.tables.ProcessQueue.PROCESS_QUEUE; -import static com.walmartlabs.concord.server.jooq.tables.Projects.PROJECTS; -import static com.walmartlabs.concord.server.sdk.ProcessStatus.*; -import static org.jooq.impl.DSL.*; - -@Path("/api/service/console/user") -public class UserActivityResource implements Resource { - - private final Set ORG_VISIBLE_STATUSES = new HashSet<>(Collections.singletonList(ProcessStatus.RUNNING)); - - private final UserDao userDao; - private final ProcessQueueDao processDao; - private final ProcessStatsDao processStatsDao; - - @Inject - public UserActivityResource(UserDao userDao, ProcessQueueDao processDao, ProcessStatsDao processStatsDao) { - this.userDao = userDao; - this.processDao = processDao; - this.processStatsDao = processStatsDao; - } - - @GET - @Path("/activity") - @Produces(MediaType.APPLICATION_JSON) - @WithTimer - public UserActivityResponse activity(@QueryParam("maxProjectsPerOrg") @DefaultValue("5") int maxProjectsPerOrg, - @QueryParam("maxOwnProcesses") @DefaultValue("5") int maxOwnProcesses) { - - UserPrincipal user = UserPrincipal.assertCurrent(); - Set orgIds = userDao.getOrgIds(user.getId()); - OffsetDateTime t = startOfDay(); - - Map> orgProcesses = processStatsDao.processByOrgs(maxProjectsPerOrg, orgIds, ORG_VISIBLE_STATUSES, t); - - Map stats = processStatsDao.getCountByStatuses(orgIds, t, user.getId()); - - ProcessFilter filter = ProcessFilter.builder() - .initiator(user.getUsername()) - .orgIds(orgIds) - .includeWithoutProject(true) - .limit(maxOwnProcesses) - .build(); - List lastProcesses = processDao.list(filter); - - return new UserActivityResponse(stats, orgProcesses, lastProcesses); - } - - private static OffsetDateTime startOfDay() { - LocalDateTime startOfDay = LocalDateTime.now().with(LocalTime.MIN); - return startOfDay.atZone(ZoneId.systemDefault()).toOffsetDateTime(); - } - - private static class ProcessStatsDao extends AbstractDao { - - @Inject - protected ProcessStatsDao(@MainDB Configuration cfg) { - super(cfg); - } - - public Map getCountByStatuses(Set orgIds, OffsetDateTime fromUpdatedAt, UUID initiatorId) { - DSLContext tx = dsl(); - - SelectConditionStep> projectIds = select(PROJECTS.PROJECT_ID) - .from(PROJECTS) - .where(PROJECTS.ORG_ID.in(orgIds)); - - SelectConditionStep> q = tx.select( - when(PROCESS_QUEUE.CURRENT_STATUS.eq(RUNNING.name()), 1).otherwise(0).as(RUNNING.name()), - when(PROCESS_QUEUE.CURRENT_STATUS.eq(SUSPENDED.name()), 1).otherwise(0).as(SUSPENDED.name()), - when(PROCESS_QUEUE.CURRENT_STATUS.eq(FINISHED.name()), 1).otherwise(0).as(FINISHED.name()), - when(PROCESS_QUEUE.CURRENT_STATUS.eq(FAILED.name()), 1).otherwise(0).as(FAILED.name()), - when(PROCESS_QUEUE.CURRENT_STATUS.eq(ENQUEUED.name()), 1).otherwise(0).as(ENQUEUED.name())) - .from(PROCESS_QUEUE) - .where(PROCESS_QUEUE.INITIATOR_ID.eq(initiatorId) - .and(PROCESS_QUEUE.LAST_UPDATED_AT.greaterOrEqual(fromUpdatedAt)) - .and(or(PROCESS_QUEUE.PROJECT_ID.in(projectIds), PROCESS_QUEUE.PROJECT_ID.isNull()))); - - Record5 r = tx.select( - sum(q.field(RUNNING.name(), Integer.class)), - sum(q.field(SUSPENDED.name(), Integer.class)), - sum(q.field(FINISHED.name(), Integer.class)), - sum(q.field(FAILED.name(), Integer.class)), - sum(q.field(ENQUEUED.name(), Integer.class))) - .from(q) - .fetchOne(); - - Map result = new HashMap<>(); - result.put(RUNNING.name(), r.value1() != null ? r.value1().intValue() : 0); - result.put(SUSPENDED.name(), r.value2() != null ? r.value2().intValue() : 0); - result.put(FINISHED.name(), r.value3() != null ? r.value3().intValue() : 0); - result.put(FAILED.name(), r.value4() != null ? r.value4().intValue() : 0); - result.put(ENQUEUED.name(), r.value5() != null ? r.value5().intValue() : 0); - return result; - } - - public Map> processByOrgs(int maxProjectRows, - Set orgIds, - Set processStatuses, - OffsetDateTime fromUpdatedAt) { - - DSLContext tx = dsl(); - - Set statuses = processStatuses.stream().map(Enum::name).collect(Collectors.toSet()); - WindowRowsStep rnField = rowNumber().over().partitionBy(ORGANIZATIONS.ORG_NAME).orderBy(ORGANIZATIONS.ORG_NAME); - - SelectHavingStep> a = - tx.select(ORGANIZATIONS.ORG_NAME, PROJECTS.PROJECT_NAME, count(), rnField) - .from(PROCESS_QUEUE) - .innerJoin(PROJECTS) - .on(PROCESS_QUEUE.PROJECT_ID.eq(PROJECTS.PROJECT_ID) - .and(PROCESS_QUEUE.CURRENT_STATUS.in(statuses)) - .and(PROCESS_QUEUE.LAST_UPDATED_AT.greaterOrEqual(fromUpdatedAt)) - .and(PROJECTS.ORG_ID.in(orgIds)) - ) - .innerJoin(ORGANIZATIONS) - .on(ORGANIZATIONS.ORG_ID.eq(PROJECTS.ORG_ID)) - .groupBy(ORGANIZATIONS.ORG_NAME, PROJECTS.PROJECT_NAME); - - Result> r = tx.select(a.field(0, String.class), a.field(1, String.class), a.field(2, Integer.class)) - .from(a) - .where(a.field(rnField).lessOrEqual(maxProjectRows)) - .fetch(); - - Map> result = new HashMap<>(); - r.forEach(i -> { - String orgName = i.value1(); - String projectName = i.value2(); - int count = i.value3(); - result.computeIfAbsent(orgName, (k) -> new ArrayList<>()).add(new ProjectProcesses(projectName, count)); - }); - return result; - } - } -} diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java new file mode 100644 index 0000000000..ada32d8234 --- /dev/null +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java @@ -0,0 +1,154 @@ +package com.walmartlabs.concord.server.console; + +/*- + * ***** + * Concord + * ----- + * Copyright (C) 2017 - 2018 Walmart Inc. + * ----- + * Licensed 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 com.walmartlabs.concord.db.AbstractDao; +import com.walmartlabs.concord.db.MainDB; +import com.walmartlabs.concord.server.process.ProcessEntry; +import com.walmartlabs.concord.server.process.queue.ProcessFilter; +import com.walmartlabs.concord.server.process.queue.ProcessQueueDao; +import com.walmartlabs.concord.server.sdk.metrics.WithTimer; +import com.walmartlabs.concord.server.security.UserPrincipal; +import org.jooq.*; +import org.sonatype.siesta.Resource; + +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.util.Base64; +import java.util.List; +import java.util.UUID; + +import static com.walmartlabs.concord.server.jooq.Tables.*; +import static com.walmartlabs.concord.server.jooq.tables.Organizations.ORGANIZATIONS; +import static com.walmartlabs.concord.server.jooq.tables.Projects.PROJECTS; + +@Path("/api/v2/service/console/user") +public class UserActivityResourceV2 implements Resource { + + private final ProcessQueueDao processDao; + private final UserActivityDao dao; + + @Inject + public UserActivityResourceV2(ProcessQueueDao processDao, + UserActivityDao dao) { + this.processDao = processDao; + this.dao = dao; + } + + @GET + @Path("/activity") + @Produces(MediaType.APPLICATION_JSON) + @WithTimer + public UserActivityResponse activity(@QueryParam("maxOwnProcesses") @DefaultValue("5") int maxOwnProcesses) { + + UserPrincipal user = UserPrincipal.assertCurrent(); + + ProcessFilter filter = ProcessFilter.builder() + .initiator(user.getUsername()) + .includeWithoutProject(true) + .limit(maxOwnProcesses) + .build(); + List lastProcesses = processDao.list(filter); + + return new UserActivityResponse(null, null, lastProcesses); + } + + @GET + @Path("/process-card") + @Produces(MediaType.APPLICATION_JSON) + @WithTimer + public List processCardsList() { + + UserPrincipal user = UserPrincipal.assertCurrent(); + + return dao.listCards(user.getId()); + } + + public static class UserActivityDao extends AbstractDao { + + @Inject + protected UserActivityDao(@MainDB Configuration cfg) { + super(cfg); + } + + public List listCards(UUID userId) { + return txResult(tx -> listCards(tx, userId)); + } + + public List listCards(DSLContext tx, UUID userId) { + // TODO: V_USER_TEAMS + SelectConditionStep> userTeams = tx.select(USER_TEAMS.TEAM_ID) + .from(USER_TEAMS) + .where(USER_TEAMS.USER_ID.eq(userId)); + + SelectConditionStep> byUserFilter = tx.select(USER_UI_PROCESS_CARDS.UI_PROCESS_CARD_ID) + .from(USER_UI_PROCESS_CARDS) + .where(USER_UI_PROCESS_CARDS.USER_ID.eq(userId)); + + SelectConditionStep> byTeamFilter = tx.select(TEAM_UI_PROCESS_CARDS.UI_PROCESS_CARD_ID) + .from(TEAM_UI_PROCESS_CARDS) + .where(TEAM_UI_PROCESS_CARDS.TEAM_ID.in(userTeams)); + + SelectConditionStep> query = tx.select( + UI_PROCESS_CARDS.UI_PROCESS_CARD_ID, + PROJECTS.ORG_ID, + ORGANIZATIONS.ORG_NAME, + UI_PROCESS_CARDS.PROJECT_ID, + PROJECTS.PROJECT_NAME, + UI_PROCESS_CARDS.REPO_ID, + REPOSITORIES.REPO_NAME, + UI_PROCESS_CARDS.NAME, + UI_PROCESS_CARDS.ENTRY_POINT, + UI_PROCESS_CARDS.DESCRIPTION, + UI_PROCESS_CARDS.ICON) + .from(UI_PROCESS_CARDS) + .join(REPOSITORIES, JoinType.JOIN).on(REPOSITORIES.REPO_ID.eq(UI_PROCESS_CARDS.REPO_ID)) + .join(PROJECTS, JoinType.JOIN).on(PROJECTS.PROJECT_ID.eq(UI_PROCESS_CARDS.PROJECT_ID)) + .join(ORGANIZATIONS, JoinType.JOIN).on(ORGANIZATIONS.ORG_ID.eq(PROJECTS.ORG_ID)) + .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(byUserFilter) + .or(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(byTeamFilter))); + + return query.fetch(this::toEntry); + } + + private ProcessCardEntry toEntry(Record11 r) { + return ProcessCardEntry.builder() + .id(r.get(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID)) + .orgName(r.get(ORGANIZATIONS.ORG_NAME)) + .projectName(r.get(PROJECTS.PROJECT_NAME)) + .repoName(r.get(REPOSITORIES.REPO_NAME)) + .entryPoint(r.get(UI_PROCESS_CARDS.ENTRY_POINT)) + .name(r.get(UI_PROCESS_CARDS.NAME)) + .description(r.get(UI_PROCESS_CARDS.DESCRIPTION)) + .icon(encodeBase64(r.get(UI_PROCESS_CARDS.ICON))) + .build(); + } + + static String encodeBase64(byte[] value) { + if (value == null) { + return null; + } + + return Base64.getEncoder().encodeToString(value); + } + } +} From f1a584f67bff1af2eb0b233ee2469a0663d9c3fd Mon Sep 17 00:00:00 2001 From: brig Date: Mon, 11 Sep 2023 00:16:49 +0200 Subject: [PATCH 03/19] fix start process url --- console2/src/components/organisms/UserProcessActivity/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console2/src/components/organisms/UserProcessActivity/index.tsx b/console2/src/components/organisms/UserProcessActivity/index.tsx index 7ab1b41cdd..7dcda7ab8c 100644 --- a/console2/src/components/organisms/UserProcessActivity/index.tsx +++ b/console2/src/components/organisms/UserProcessActivity/index.tsx @@ -64,7 +64,7 @@ const renderCard = (card: ProcessCardEntry) => {
- +
From 76bc8184af1c97a287fc6425c45b72bcae5a0f58 Mon Sep 17 00:00:00 2001 From: brig Date: Mon, 11 Sep 2023 12:26:08 +0200 Subject: [PATCH 04/19] form name, txId to data.js --- .../server/console/CustomFormServiceV1.java | 82 +++++++++---------- .../server/console/CustomFormServiceV2.java | 13 ++- 2 files changed, 50 insertions(+), 45 deletions(-) diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/CustomFormServiceV1.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/CustomFormServiceV1.java index bfd5fc0721..4660cda9ee 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/CustomFormServiceV1.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/CustomFormServiceV1.java @@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.walmartlabs.concord.forms.ValidationError; import com.walmartlabs.concord.server.MultipartUtils; import com.walmartlabs.concord.server.cfg.CustomFormConfiguration; @@ -43,11 +45,13 @@ import io.takari.bpm.model.form.FormDefinition; import io.takari.bpm.model.form.FormField; import io.takari.bpm.model.form.FormField.Cardinality; +import org.immutables.value.Value; import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonatype.siesta.Validate; +import javax.annotation.Nullable; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.POST; @@ -334,7 +338,17 @@ private FormData prepareData(boolean success, boolean processFailed, Form form, } } - return new FormData(success, processFailed, submitUrl, fields, _definitions, _values, _errors); + return FormData.builder() + .txId(processInstanceId) + .formName(formName) + .success(success) + .processFailed(processFailed) + .submitUrl(submitUrl) + .fields(fields) + .definitions(_definitions) + .values(_values) + .errors(_errors) + .build(); } private void writeData(Path baseDir, Object data) throws IOException { @@ -396,56 +410,36 @@ private static String formPath(PartialProcessKey processKey, String formName) { return String.format(FORMS_PATH_TEMPLATE, processKey, formName); } - @JsonInclude(Include.NON_EMPTY) - public static class FormData implements Serializable { - - private static final long serialVersionUID = -1591440785695774602L; - - private final boolean success; - private final boolean processFailed; - private final String submitUrl; - private final List fields; - private final Map definitions; - private final Map values; - private final Map errors; - - public FormData(boolean success, boolean processFailed, String submitUrl, - List fields, Map definitions, Map values, - Map errors) { - - this.success = success; - this.processFailed = processFailed; - this.submitUrl = submitUrl; - this.fields = fields; - this.definitions = definitions; - this.values = values; - this.errors = errors; - } + @Value.Immutable + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @JsonSerialize(as = ImmutableFormData.class) + @JsonDeserialize(as = ImmutableFormData.class) + public interface FormData extends Serializable { - public boolean isSuccess() { - return success; - } + long serialVersionUID = -1591440785695774602L; - public boolean isProcessFailed() { - return processFailed; - } + String txId(); - public String getSubmitUrl() { - return submitUrl; - } + String formName(); - public List getFields() { return fields; } + boolean success(); - public Map getDefinitions() { - return definitions; - } + boolean processFailed(); - public Map getValues() { - return values; - } + String submitUrl(); + + List fields(); + + Map definitions(); + + @Nullable + Map values(); + + @Nullable + Map errors(); - public Map getErrors() { - return errors; + static ImmutableFormData.Builder builder() { + return ImmutableFormData.builder(); } } diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/CustomFormServiceV2.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/CustomFormServiceV2.java index 73fdbdc61a..4512ba9575 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/CustomFormServiceV2.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/CustomFormServiceV2.java @@ -315,7 +315,18 @@ private FormData prepareData(boolean success, boolean processFailed, Form form, } String submitUrl = String.format(FORM_WIZARD_CONTINUE_URL_TEMPLATE, processInstanceId, form.name()); - return new FormData(success, processFailed, submitUrl, fields, _definitions, _values, _errors); + + return FormData.builder() + .txId(processInstanceId.toString()) + .formName(form.name()) + .success(success) + .processFailed(processFailed) + .submitUrl(submitUrl) + .fields(fields) + .definitions(_definitions) + .values(_values) + .errors(_errors) + .build(); } private io.takari.bpm.model.form.FormField.Cardinality convert(FormField.Cardinality c) { From 51cd19df28c266365cc400c699e48f3058d518d2 Mon Sep 17 00:00:00 2001 From: brig Date: Tue, 12 Sep 2023 00:20:22 +0200 Subject: [PATCH 05/19] allow custom form --- .../src/api/service/console/user/index.ts | 1 + .../organisms/UserProcessActivity/index.tsx | 17 +- .../walmartlabs/concord/server/db/v2.1.0.xml | 10 + .../server/console/ProcessCardEntry.java | 2 + .../console/UserActivityResourceV2.java | 191 +++++++++++++++--- 5 files changed, 194 insertions(+), 27 deletions(-) diff --git a/console2/src/api/service/console/user/index.ts b/console2/src/api/service/console/user/index.ts index da92dbb1c4..84e572a4b0 100644 --- a/console2/src/api/service/console/user/index.ts +++ b/console2/src/api/service/console/user/index.ts @@ -34,6 +34,7 @@ export interface ProcessCardEntry { name: string; description?: string; icon?: string; + isCustomForm: boolean; } export const getActivity = ( diff --git a/console2/src/components/organisms/UserProcessActivity/index.tsx b/console2/src/components/organisms/UserProcessActivity/index.tsx index 7dcda7ab8c..8e7b25fcef 100644 --- a/console2/src/components/organisms/UserProcessActivity/index.tsx +++ b/console2/src/components/organisms/UserProcessActivity/index.tsx @@ -24,7 +24,7 @@ import { getActivity as apiGetActivity, listProcessCards as apiListProcessCards, ProcessCardEntry } from '../../../api/service/console/user'; -import { Button, Card, CardGroup, Header, Icon, Image } from 'semantic-ui-react'; +import { Button, Card, CardGroup, Embed, Header, Icon, Image, Modal } from 'semantic-ui-react'; import { ProcessList } from '../../molecules/index'; import { ProcessEntry } from '../../../api/process'; import { @@ -64,7 +64,20 @@ const renderCard = (card: ProcessCardEntry) => {
- + {card.isCustomForm && + Start process}> + + + + + } + + {!card.isCustomForm && + + }
diff --git a/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml b/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml index 1e09932bd6..73d3e79736 100644 --- a/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml +++ b/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml @@ -93,4 +93,14 @@ onDelete="CASCADE"/> + + + + + + + + + + diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java index e0a2d2faa7..77d97adc42 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java @@ -64,6 +64,8 @@ public interface ProcessCardEntry extends Serializable { @Nullable String icon(); + boolean isCustomForm(); + static ImmutableProcessCardEntry.Builder builder() { return ImmutableProcessCardEntry.builder(); } diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java index ada32d8234..c57953cb6e 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java @@ -20,11 +20,14 @@ * ===== */ +import com.walmartlabs.concord.common.IOUtils; import com.walmartlabs.concord.db.AbstractDao; import com.walmartlabs.concord.db.MainDB; +import com.walmartlabs.concord.server.ConcordObjectMapper; import com.walmartlabs.concord.server.process.ProcessEntry; import com.walmartlabs.concord.server.process.queue.ProcessFilter; import com.walmartlabs.concord.server.process.queue.ProcessQueueDao; +import com.walmartlabs.concord.server.sdk.ConcordApplicationException; import com.walmartlabs.concord.server.sdk.metrics.WithTimer; import com.walmartlabs.concord.server.security.UserPrincipal; import org.jooq.*; @@ -33,29 +36,43 @@ import javax.inject.Inject; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; -import java.util.Base64; -import java.util.List; -import java.util.UUID; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.*; +import java.util.function.Function; import static com.walmartlabs.concord.server.jooq.Tables.*; import static com.walmartlabs.concord.server.jooq.tables.Organizations.ORGANIZATIONS; import static com.walmartlabs.concord.server.jooq.tables.Projects.PROJECTS; +import static org.jooq.impl.DSL.*; -@Path("/api/v2/service/console/user") +@javax.ws.rs.Path("/api/v2/service/console/user") public class UserActivityResourceV2 implements Resource { + private static final String DATA_FILE_TEMPLATE = "data = %s;"; + private final ProcessQueueDao processDao; private final UserActivityDao dao; + private final ConcordObjectMapper objectMapper; @Inject public UserActivityResourceV2(ProcessQueueDao processDao, - UserActivityDao dao) { + UserActivityDao dao, + ConcordObjectMapper objectMapper) { this.processDao = processDao; this.dao = dao; + this.objectMapper = objectMapper; } @GET - @Path("/activity") + @javax.ws.rs.Path("/activity") @Produces(MediaType.APPLICATION_JSON) @WithTimer public UserActivityResponse activity(@QueryParam("maxOwnProcesses") @DefaultValue("5") int maxOwnProcesses) { @@ -73,7 +90,7 @@ public UserActivityResponse activity(@QueryParam("maxOwnProcesses") @DefaultValu } @GET - @Path("/process-card") + @javax.ws.rs.Path("/process-card") @Produces(MediaType.APPLICATION_JSON) @WithTimer public List processCardsList() { @@ -83,17 +100,131 @@ public List processCardsList() { return dao.listCards(user.getId()); } + @GET + @javax.ws.rs.Path("/process-card/{cardId}/form") + @Produces(MediaType.TEXT_HTML) + @WithTimer + public Response processForm(@PathParam("cardId") UUID cardId) { + + Optional o = dao.getForm(cardId, src -> { + try { + Path tmp = IOUtils.createTempFile("process-form", ".html"); + Files.copy(src, tmp, StandardCopyOption.REPLACE_EXISTING); + return Optional.of(tmp); + } catch (IOException e) { + throw new ConcordApplicationException("Error while downloading custom process start form: " + cardId, e); + } + }); + + if (!o.isPresent()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return toBinaryResponse(o.get()); + } + + @GET + @javax.ws.rs.Path("/process-card/{cardId}/data.js") + @Produces("text/javascript") + @WithTimer + public Response processFormData(@PathParam("cardId") UUID cardId) { + ProcessCardEntry card = dao.get(cardId); + if (card ==null) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + Map customData = dao.getFormData(cardId); + + Map resultData = new HashMap<>(customData != null ? customData : Collections.emptyMap()); + resultData.put("org", card.orgName()); + resultData.put("project", card.projectName()); + resultData.put("repo", card.repoName()); + resultData.put("entryPoint", card.entryPoint()); + + return Response.ok(formatData(resultData)) + .build(); + } + + private String formatData(Map data) { + return String.format(DATA_FILE_TEMPLATE, objectMapper.toString(data)); + } + + private static Response toBinaryResponse(Path file) { + return Response.ok((StreamingOutput) out -> { + try (InputStream in = Files.newInputStream(file)) { + IOUtils.copy(in, out); + } finally { + Files.delete(file); + } + }).build(); + } + public static class UserActivityDao extends AbstractDao { + private final ConcordObjectMapper objectMapper; + @Inject - protected UserActivityDao(@MainDB Configuration cfg) { + protected UserActivityDao(@MainDB Configuration cfg, ConcordObjectMapper objectMapper) { super(cfg); + this.objectMapper = objectMapper; + } + + public ProcessCardEntry get(UUID cardId) { + return txResult(tx -> get(tx, cardId)); + } + + public ProcessCardEntry get(DSLContext tx, UUID cardId) { + return buildSelect(tx) + .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.eq(cardId)) + .fetchOne(this::toEntry); } public List listCards(UUID userId) { return txResult(tx -> listCards(tx, userId)); } + public Map getFormData(UUID cardId) { + return txResult(tx -> getFormData(tx, cardId)); + } + + public Map getFormData(DSLContext tx, UUID cardId) { + return tx.select(UI_PROCESS_CARDS.DATA) + .from(UI_PROCESS_CARDS) + .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.eq(cardId)) + .fetchOne(r -> objectMapper.fromJSONB(r.get(UI_PROCESS_CARDS.DATA))); + } + + public Optional getForm(UUID cardId, Function> converter) { + return txResult(tx -> getForm(tx, cardId, converter)); + } + + public Optional getForm(DSLContext tx, UUID cardId, Function> converter) { + String sql = tx.select(UI_PROCESS_CARDS.FORM) + .from(UI_PROCESS_CARDS) + .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.eq((UUID) null) + .and(UI_PROCESS_CARDS.FORM.isNotNull())) + .getSQL(); + + return getInputStream(tx, sql, cardId, converter); + } + + private static Optional getInputStream(DSLContext tx, String sql, UUID cardId, Function> converter) { + return tx.connectionResult(conn -> { + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setObject(1, cardId); + + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return Optional.empty(); + } + try (InputStream in = rs.getBinaryStream(1)) { + return converter.apply(in); + } + } + } + }); + } + public List listCards(DSLContext tx, UUID userId) { // TODO: V_USER_TEAMS SelectConditionStep> userTeams = tx.select(USER_TEAMS.TEAM_ID) @@ -108,29 +239,38 @@ public List listCards(DSLContext tx, UUID userId) { .from(TEAM_UI_PROCESS_CARDS) .where(TEAM_UI_PROCESS_CARDS.TEAM_ID.in(userTeams)); - SelectConditionStep> query = tx.select( - UI_PROCESS_CARDS.UI_PROCESS_CARD_ID, - PROJECTS.ORG_ID, - ORGANIZATIONS.ORG_NAME, - UI_PROCESS_CARDS.PROJECT_ID, - PROJECTS.PROJECT_NAME, - UI_PROCESS_CARDS.REPO_ID, - REPOSITORIES.REPO_NAME, - UI_PROCESS_CARDS.NAME, - UI_PROCESS_CARDS.ENTRY_POINT, - UI_PROCESS_CARDS.DESCRIPTION, - UI_PROCESS_CARDS.ICON) - .from(UI_PROCESS_CARDS) - .join(REPOSITORIES, JoinType.JOIN).on(REPOSITORIES.REPO_ID.eq(UI_PROCESS_CARDS.REPO_ID)) - .join(PROJECTS, JoinType.JOIN).on(PROJECTS.PROJECT_ID.eq(UI_PROCESS_CARDS.PROJECT_ID)) - .join(ORGANIZATIONS, JoinType.JOIN).on(ORGANIZATIONS.ORG_ID.eq(PROJECTS.ORG_ID)) + SelectConditionStep> query = + buildSelect(tx) .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(byUserFilter) .or(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(byTeamFilter))); return query.fetch(this::toEntry); } - private ProcessCardEntry toEntry(Record11 r) { + private static SelectOnConditionStep> buildSelect(DSLContext tx) { + + Field isCustomForm = when(field(UI_PROCESS_CARDS.FORM).isNotNull(), true).otherwise(false); + + return tx.select( + UI_PROCESS_CARDS.UI_PROCESS_CARD_ID, + PROJECTS.ORG_ID, + ORGANIZATIONS.ORG_NAME, + UI_PROCESS_CARDS.PROJECT_ID, + PROJECTS.PROJECT_NAME, + UI_PROCESS_CARDS.REPO_ID, + REPOSITORIES.REPO_NAME, + UI_PROCESS_CARDS.NAME, + UI_PROCESS_CARDS.ENTRY_POINT, + UI_PROCESS_CARDS.DESCRIPTION, + UI_PROCESS_CARDS.ICON, + isCustomForm.as("isCustomForm")) + .from(UI_PROCESS_CARDS) + .join(REPOSITORIES, JoinType.JOIN).on(REPOSITORIES.REPO_ID.eq(UI_PROCESS_CARDS.REPO_ID)) + .join(PROJECTS, JoinType.JOIN).on(PROJECTS.PROJECT_ID.eq(UI_PROCESS_CARDS.PROJECT_ID)) + .join(ORGANIZATIONS, JoinType.JOIN).on(ORGANIZATIONS.ORG_ID.eq(PROJECTS.ORG_ID)); + } + + private ProcessCardEntry toEntry(Record12 r) { return ProcessCardEntry.builder() .id(r.get(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID)) .orgName(r.get(ORGANIZATIONS.ORG_NAME)) @@ -140,6 +280,7 @@ private ProcessCardEntry toEntry(Record11 Date: Tue, 12 Sep 2023 00:39:36 +0200 Subject: [PATCH 06/19] custom form example --- examples/process-start-form/data.js | 10 ++ examples/process-start-form/index.html | 159 +++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 examples/process-start-form/data.js create mode 100644 examples/process-start-form/index.html diff --git a/examples/process-start-form/data.js b/examples/process-start-form/data.js new file mode 100644 index 0000000000..a9687fa484 --- /dev/null +++ b/examples/process-start-form/data.js @@ -0,0 +1,10 @@ +data = { + "org": "Default", + "project": "test", + "repo": "test", + "entryPoint": "test", + + "values" : { + "release" : "1.0.0" + } +}; \ No newline at end of file diff --git a/examples/process-start-form/index.html b/examples/process-start-form/index.html new file mode 100644 index 0000000000..db53cbafb9 --- /dev/null +++ b/examples/process-start-form/index.html @@ -0,0 +1,159 @@ + + + + + Process start form example + + + + + + + + + +

Deployment info

+ +
+
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ + + + + + + + +
+ + + + + \ No newline at end of file From bf8c6beb051e4c1cd171e2041e1f440bc901dbcf Mon Sep 17 00:00:00 2001 From: brig Date: Sun, 15 Oct 2023 13:50:51 +0200 Subject: [PATCH 07/19] simple api endpoint --- .../walmartlabs/concord/server/db/v2.1.0.xml | 5 +- .../server/console/ProcessCardResource.java | 272 ++++++++++++++++++ 2 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardResource.java diff --git a/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml b/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml index 73d3e79736..f87f6505d1 100644 --- a/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml +++ b/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml @@ -13,13 +13,13 @@ - + - + @@ -103,4 +103,5 @@ + diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardResource.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardResource.java new file mode 100644 index 0000000000..5a861eb1b8 --- /dev/null +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardResource.java @@ -0,0 +1,272 @@ +package com.walmartlabs.concord.server.console; + +import com.walmartlabs.concord.common.IOUtils; +import com.walmartlabs.concord.common.validation.ConcordKey; +import com.walmartlabs.concord.db.AbstractDao; +import com.walmartlabs.concord.db.MainDB; +import com.walmartlabs.concord.sdk.Constants; +import com.walmartlabs.concord.server.ConcordObjectMapper; +import com.walmartlabs.concord.server.GenericOperationResult; +import com.walmartlabs.concord.server.MultipartUtils; +import com.walmartlabs.concord.server.OperationResult; +import com.walmartlabs.concord.server.jooq.tables.records.UiProcessCardsRecord; +import com.walmartlabs.concord.server.org.OrganizationDao; +import com.walmartlabs.concord.server.org.project.ProjectDao; +import com.walmartlabs.concord.server.org.project.RepositoryDao; +import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; +import org.jooq.Configuration; +import org.jooq.DSLContext; +import org.jooq.UpdateSetFirstStep; +import org.sonatype.siesta.Resource; +import org.sonatype.siesta.ValidationErrorsException; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static com.walmartlabs.concord.server.jooq.Tables.TEAM_UI_PROCESS_CARDS; +import static com.walmartlabs.concord.server.jooq.Tables.UI_PROCESS_CARDS; +import static org.jooq.impl.DSL.value; + +@Named +@Singleton +@javax.ws.rs.Path("/api/v1/org/") +public class ProcessCardResource implements Resource { + + private final OrganizationDao orgDao; + private final ProjectDao projectDao; + private final RepositoryDao repositoryDao; + private final Dao dao; + + @Inject + public ProcessCardResource(OrganizationDao orgDao, + ProjectDao projectDao, + RepositoryDao repositoryDao, + Dao dao) { + + this.orgDao = orgDao; + this.projectDao = projectDao; + this.repositoryDao = repositoryDao; + this.dao = dao; + } + + @POST + @Path("/{orgName}/process-card") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public GenericOperationResult createOrUpdate(@PathParam("orgName") @ConcordKey String orgName, + MultipartInput input) throws IOException { + UUID orgId = assertOrg(orgName); + UUID projectId = getProject(input, orgId); + UUID repoId = getRepo(input, projectId); + String name = MultipartUtils.getString(input, "name"); + String entryPoint = MultipartUtils.getString(input, "entryPoint"); + String description = MultipartUtils.getString(input, "description"); + + byte[] icon = getIcon(input); + byte[] form = getForm(input); + + Map data = MultipartUtils.getMap(input, "data"); + + UUID id = MultipartUtils.getUuid(input, "id"); + boolean exists = false; + if (id != null) { + exists = dao.exists(id); + } + + List teamIds = MultipartUtils.getUUIDList(input, "teamIds"); + + if (!exists) { + dao.tx(tx -> { + UUID resultId = dao.insert(tx, id, projectId, repoId, name, entryPoint, description, icon, form, data); + for (UUID teamId : teamIds) { + dao.upsertTeamAccess(tx, resultId, teamId); + } + }); + return new GenericOperationResult(OperationResult.CREATED); + } else { + dao.tx(tx -> { + dao.update(id, projectId, repoId, name, entryPoint, description, icon, form, data); + for (UUID teamId : teamIds) { + dao.upsertTeamAccess(tx, id, teamId); + } + }); + return new GenericOperationResult(OperationResult.UPDATED); + } + } + + private byte[] getIcon(MultipartInput input) throws IOException { + try (InputStream is = MultipartUtils.getStream(input, "icon")) { + if (is != null) { + return IOUtils.toByteArray(is); + } + } + return null; + } + + private byte[] getForm(MultipartInput input) throws IOException { + try (InputStream is = MultipartUtils.getStream(input, "form")) { + if (is != null) { + return IOUtils.toByteArray(is); + } + } + return null; + } + + private UUID assertOrg(String name) { + UUID result = orgDao.getId(name); + if (result != null) { + return result; + } + throw new ValidationErrorsException("Organization not found: " + name); + } + + private UUID getProject(MultipartInput input, UUID orgId) { + UUID id = MultipartUtils.getUuid(input, Constants.Multipart.PROJECT_ID); + String name = MultipartUtils.getString(input, Constants.Multipart.PROJECT_NAME); + if (id == null && name != null) { + if (orgId == null) { + throw new ValidationErrorsException("Organization ID or name is required"); + } + + id = projectDao.getId(orgId, name); + if (id == null) { + throw new ValidationErrorsException("Project not found: " + name); + } + } + return id; + } + + private UUID getRepo(MultipartInput input, UUID projectId) { + UUID id = MultipartUtils.getUuid(input, Constants.Multipart.REPO_ID); + String name = MultipartUtils.getString(input, Constants.Multipart.REPO_NAME); + if (id == null && name != null) { + if (projectId == null) { + throw new ValidationErrorsException("Project ID or name is required"); + } + + id = repositoryDao.getId(projectId, name); + if (id == null) { + throw new ValidationErrorsException("Repository not found: " + name); + } + } + return id; + } + + public static class Dao extends AbstractDao { + + private final ConcordObjectMapper objectMapper; + + @Inject + protected Dao(@MainDB Configuration cfg, ConcordObjectMapper objectMapper) { + super(cfg); + this.objectMapper = objectMapper; + } + + protected void tx(Tx t) { + super.tx(t); + } + + public UUID insert(UUID cardId, UUID projectId, UUID repoId, String name, String entryPoint, String description, byte[] icon, byte[] form, Map data) { + return txResult(tx -> insert(tx, cardId, projectId, repoId, name, entryPoint, description, icon, form, data)); + } + + private UUID insert(DSLContext tx, UUID cardId, UUID projectId, UUID repoId, String name, String entryPoint, String description, byte[] icon, byte[] form, Map data) { + return tx.insertInto(UI_PROCESS_CARDS) + .columns(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID, + UI_PROCESS_CARDS.PROJECT_ID, + UI_PROCESS_CARDS.REPO_ID, + UI_PROCESS_CARDS.NAME, + UI_PROCESS_CARDS.ENTRY_POINT, + UI_PROCESS_CARDS.DESCRIPTION, + UI_PROCESS_CARDS.ICON, + UI_PROCESS_CARDS.FORM, + UI_PROCESS_CARDS.DATA) + .values(value(cardId == null ? UUID.randomUUID() : cardId), + value(projectId), + value(repoId), + value(name), + value(entryPoint), + value(description), + value(icon), + value(form), + value(objectMapper.toJSONB(data))) + .returning(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID) + .fetchOne() + .getUiProcessCardId(); + } + + public void update(UUID cardId, UUID projectId, UUID repoId, String name, + String entryPoint, String description, byte[] icon, byte[] form, Map data) { + tx(tx -> update(tx, cardId, projectId, repoId, name, entryPoint, description, icon, form, data)); + } + + public void update(DSLContext tx, UUID cardId, UUID projectId, UUID repoId, String name, + String entryPoint, String description, byte[] icon, byte[] form, Map data) { + + UpdateSetFirstStep q = tx.update(UI_PROCESS_CARDS); + + if (projectId != null) { + q.set(UI_PROCESS_CARDS.PROJECT_ID, projectId); + } + + if (repoId != null) { + q.set(UI_PROCESS_CARDS.REPO_ID, repoId); + } + + if (name != null) { + q.set(UI_PROCESS_CARDS.NAME, name); + } + + if (entryPoint != null) { + q.set(UI_PROCESS_CARDS.ENTRY_POINT, entryPoint); + } + + if (description != null) { + q.set(UI_PROCESS_CARDS.DESCRIPTION, description); + } + + if (icon != null) { + q.set(UI_PROCESS_CARDS.ICON, icon); + } + + if (form != null) { + q.set(UI_PROCESS_CARDS.FORM, form); + } + + if (data != null) { + q.set(UI_PROCESS_CARDS.DATA, objectMapper.toJSONB(data)); + } + + q.set(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID, UI_PROCESS_CARDS.UI_PROCESS_CARD_ID) + .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.eq(cardId)) + .execute(); + } + + public boolean exists(UUID id) { + return txResult(tx -> tx.select(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID) + .from(UI_PROCESS_CARDS) + .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.eq(id)) + .fetchOne(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID)) != null; + } + + public void upsertTeamAccess(UUID id, UUID teamId) { + tx(tx -> upsertTeamAccess(tx, id, teamId)); + } + + public void upsertTeamAccess(DSLContext tx, UUID id, UUID teamId) { + tx.insertInto(TEAM_UI_PROCESS_CARDS) + .columns(TEAM_UI_PROCESS_CARDS.TEAM_ID, TEAM_UI_PROCESS_CARDS.UI_PROCESS_CARD_ID) + .values(teamId, id) + .onDuplicateKeyIgnore() + .execute(); + } + } +} From e72244850d585aecbfbcc8d501416f4aed7a468c Mon Sep 17 00:00:00 2001 From: brig Date: Mon, 13 Nov 2023 13:44:57 +0100 Subject: [PATCH 08/19] nullable project, repo, ord for user cards --- .../walmartlabs/concord/server/db/v2.1.0.xml | 1 - .../server/console/ProcessCardEntry.java | 4 ++++ .../server/console/ProcessCardResource.java | 20 ++++++++++++++++ .../console/UserActivityResourceV2.java | 24 ++++++++++++------- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml b/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml index f87f6505d1..3d13ec7bc6 100644 --- a/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml +++ b/server/db/src/main/resources/com/walmartlabs/concord/server/db/v2.1.0.xml @@ -103,5 +103,4 @@ - diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java index 77d97adc42..a4f1092772 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardEntry.java @@ -43,15 +43,19 @@ public interface ProcessCardEntry extends Serializable { UUID id(); @ConcordKey + @Nullable String orgName(); @ConcordKey + @Nullable String projectName(); @ConcordKey + @Nullable String repoName(); @Size(max = 256) + @Nullable String entryPoint(); @Size(max = 128) diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardResource.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardResource.java index 5a861eb1b8..8a2abb8b9c 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardResource.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/ProcessCardResource.java @@ -1,5 +1,25 @@ package com.walmartlabs.concord.server.console; +/*- + * ***** + * Concord + * ----- + * Copyright (C) 2017 - 2023 Walmart Inc. + * ----- + * Licensed 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 com.walmartlabs.concord.common.IOUtils; import com.walmartlabs.concord.common.validation.ConcordKey; import com.walmartlabs.concord.db.AbstractDao; diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java b/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java index c57953cb6e..1e15fe8b8b 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/console/UserActivityResourceV2.java @@ -227,9 +227,9 @@ private static Optional getInputStream(DSLContext tx, String sql, UUID ca public List listCards(DSLContext tx, UUID userId) { // TODO: V_USER_TEAMS - SelectConditionStep> userTeams = tx.select(USER_TEAMS.TEAM_ID) - .from(USER_TEAMS) - .where(USER_TEAMS.USER_ID.eq(userId)); + SelectConditionStep> userTeams = tx.select(V_USER_TEAMS.TEAM_ID) + .from(V_USER_TEAMS) + .where(V_USER_TEAMS.USER_ID.eq(userId)); SelectConditionStep> byUserFilter = tx.select(USER_UI_PROCESS_CARDS.UI_PROCESS_CARD_ID) .from(USER_UI_PROCESS_CARDS) @@ -239,12 +239,18 @@ public List listCards(DSLContext tx, UUID userId) { .from(TEAM_UI_PROCESS_CARDS) .where(TEAM_UI_PROCESS_CARDS.TEAM_ID.in(userTeams)); + SelectOrderByStep> userCards = byUserFilter.unionAll(byTeamFilter); + + SelectJoinStep> userCardsFilter = tx.select(userCards.field(TEAM_UI_PROCESS_CARDS.UI_PROCESS_CARD_ID)) + .from(userCards); + SelectConditionStep> query = buildSelect(tx) - .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(byUserFilter) - .or(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(byTeamFilter))); + .where(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID.in(userCardsFilter)); - return query.fetch(this::toEntry); + return query + .orderBy(UI_PROCESS_CARDS.UI_PROCESS_CARD_ID) + .fetch(this::toEntry); } private static SelectOnConditionStep> buildSelect(DSLContext tx) { @@ -265,9 +271,9 @@ private static SelectOnConditionStep r) { From 41667dcb73faa9c9924a6220adf1567f66aa5b41 Mon Sep 17 00:00:00 2001 From: brig Date: Mon, 13 Nov 2023 13:45:08 +0100 Subject: [PATCH 09/19] scrollable iframe --- .../organisms/UserProcessActivity/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/console2/src/components/organisms/UserProcessActivity/index.tsx b/console2/src/components/organisms/UserProcessActivity/index.tsx index 8e7b25fcef..02de5ea3bb 100644 --- a/console2/src/components/organisms/UserProcessActivity/index.tsx +++ b/console2/src/components/organisms/UserProcessActivity/index.tsx @@ -67,10 +67,14 @@ const renderCard = (card: ProcessCardEntry) => { {card.isCustomForm && Start process}> - +
+