From ce36e76982d16ae9e2dcdab658345744825de8d6 Mon Sep 17 00:00:00 2001 From: Martin Krulis Date: Thu, 30 Dec 2021 00:53:39 +0100 Subject: [PATCH] Providing standard navigation for pipeline pages. --- .../layout/Navigation/Navigation.js | 20 +++- .../layout/Navigation/PipelineNavigation.js | 40 ++++++++ src/components/layout/Navigation/index.js | 1 + .../PipelineNameContainer.js | 64 ++++++++++++ src/containers/PipelineNameContainer/index.js | 2 + src/locales/cs.json | 2 +- src/locales/en.json | 2 +- src/locales/whitelist_cs.json | 1 + src/pages/EditPipeline/EditPipeline.js | 13 ++- src/pages/Pipeline/Pipeline.js | 98 ++++++++----------- 10 files changed, 181 insertions(+), 62 deletions(-) create mode 100644 src/components/layout/Navigation/PipelineNavigation.js create mode 100644 src/containers/PipelineNameContainer/PipelineNameContainer.js create mode 100644 src/containers/PipelineNameContainer/index.js diff --git a/src/components/layout/Navigation/Navigation.js b/src/components/layout/Navigation/Navigation.js index c868cc338..8e13f8b19 100644 --- a/src/components/layout/Navigation/Navigation.js +++ b/src/components/layout/Navigation/Navigation.js @@ -10,7 +10,8 @@ import ExercisesNameContainer from '../../../containers/ExercisesNameContainer'; import GroupsNameContainer from '../../../containers/GroupsNameContainer'; import ShadowAssignmentNameContainer from '../../../containers/ShadowAssignmentNameContainer'; import UsersNameContainer from '../../../containers/UsersNameContainer'; -import { AssignmentIcon, ExerciseIcon, GroupIcon, ShadowAssignmentIcon } from '../../icons'; +import PipelineNameContainer from '../../../containers/PipelineNameContainer'; +import { AssignmentIcon, ExerciseIcon, GroupIcon, PipelineIcon, ShadowAssignmentIcon } from '../../icons'; import styles from './Navigation.less'; @@ -49,6 +50,7 @@ const Navigation = ({ exerciseId = null, assignmentId = null, shadowId = null, + pipelineId = null, userId = null, emphasizeUser = false, links, @@ -135,6 +137,21 @@ const Navigation = ({ )} + {pipelineId && ( + + + + + }> + + + + + )} + {userId && !emphasizeUser && ( ( + , + link: PIPELINE_URI_FACTORY(pipelineId), + icon: , + }, + canEdit && { + caption: , + link: PIPELINE_EDIT_URI_FACTORY(pipelineId), + icon: , + }, + ]} + /> +); + +PipelineNavigation.propTypes = { + pipelineId: PropTypes.string.isRequired, + canViewDetail: PropTypes.bool, + canEdit: PropTypes.bool, + isLoggedInUser: PropTypes.bool, + links: PropTypes.object.isRequired, +}; + +export default withLinks(PipelineNavigation); diff --git a/src/components/layout/Navigation/index.js b/src/components/layout/Navigation/index.js index 1a6cd727d..b53c52803 100644 --- a/src/components/layout/Navigation/index.js +++ b/src/components/layout/Navigation/index.js @@ -2,6 +2,7 @@ export { default as AssignmentNavigation } from './AssignmentNavigation'; export { default as AssignmentSolutionNavigation } from './AssignmentSolutionNavigation'; export { default as ExerciseNavigation } from './ExerciseNavigation'; export { default as GroupNavigation } from './GroupNavigation'; +export { default as PipelineNavigation } from './PipelineNavigation'; export { default as ReferenceSolutionNavigation } from './ReferenceSolutionNavigation'; export { default as ShadowAssignmentNavigation } from './ShadowAssignmentNavigation'; export { default as UserNavigation } from './UserNavigation'; diff --git a/src/containers/PipelineNameContainer/PipelineNameContainer.js b/src/containers/PipelineNameContainer/PipelineNameContainer.js new file mode 100644 index 000000000..9213ba36d --- /dev/null +++ b/src/containers/PipelineNameContainer/PipelineNameContainer.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; + +import { fetchPipelineIfNeeded } from '../../redux/modules/pipelines'; +import { getPipeline } from '../../redux/selectors/pipelines'; +import ResourceRenderer from '../../components/helpers/ResourceRenderer'; +import { LoadingIcon } from '../../components/icons'; +import withLinks from '../../helpers/withLinks'; + +class PipelineNameContainer extends Component { + componentDidMount = () => this.props.loadPipelineIfNeeded(); + + componentDidUpdate(prevProps) { + if (this.props.pipelineId !== prevProps.pipelineId) { + this.props.loadPipelineIfNeeded(); + } + } + + render() { + const { + pipelineId, + pipeline, + noLink = false, + links: { PIPELINE_URI_FACTORY }, + } = this.props; + return ( + + + + + }> + {pipeline => + noLink ? <>{pipeline.name} : {pipeline.name} + } + + ); + } +} + +PipelineNameContainer.propTypes = { + pipelineId: PropTypes.string.isRequired, + pipeline: ImmutablePropTypes.map, + noLink: PropTypes.bool, + loadPipelineIfNeeded: PropTypes.func.isRequired, + links: PropTypes.object.isRequired, +}; + +export default withLinks( + connect( + (state, { pipelineId }) => ({ + pipeline: getPipeline(pipelineId)(state), + }), + (dispatch, { pipelineId }) => ({ + loadPipelineIfNeeded: () => dispatch(fetchPipelineIfNeeded(pipelineId)), + }) + )(PipelineNameContainer) +); diff --git a/src/containers/PipelineNameContainer/index.js b/src/containers/PipelineNameContainer/index.js new file mode 100644 index 000000000..3fb4b8187 --- /dev/null +++ b/src/containers/PipelineNameContainer/index.js @@ -0,0 +1,2 @@ +import PipelineNameContainer from './PipelineNameContainer'; +export default PipelineNameContainer; diff --git a/src/locales/cs.json b/src/locales/cs.json index cd5de5d7c..e645c895a 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -1073,6 +1073,7 @@ "app.navigation.group": "Skupina", "app.navigation.groupAssignments": "Úlohy ve skupině", "app.navigation.groupInfo": "Info skupiny", + "app.navigation.pipeline": "Pipeline", "app.navigation.referenceSolution": "Referenční řešení", "app.navigation.shadowAssignment": "Stínová úloha", "app.navigation.solution": "Řešení", @@ -1102,7 +1103,6 @@ "app.passwordStrength.somewhatOk": "Šlo by to i lépe.", "app.passwordStrength.unknown": "...", "app.passwordStrength.worst": "Nevyhovující", - "app.pipeline.editSettings": "Upravit pipeline", "app.pipeline.exercises": "Úlohy:", "app.pipeline.failedDetail": "Načítání detailů pipeline selhalo. Ujistěte se prosím, že jste připojeni k Internetu a zkuste to později.", "app.pipeline.loadingDetail": "Načítám podrobnosti pipeline", diff --git a/src/locales/en.json b/src/locales/en.json index d44adb6f0..c70d8a85d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1073,6 +1073,7 @@ "app.navigation.group": "Group", "app.navigation.groupAssignments": "Group Assignments", "app.navigation.groupInfo": "Group Info", + "app.navigation.pipeline": "Pipeline", "app.navigation.referenceSolution": "Reference Solution", "app.navigation.shadowAssignment": "Shadow Assignment", "app.navigation.solution": "Solution", @@ -1102,7 +1103,6 @@ "app.passwordStrength.somewhatOk": "You can do better.", "app.passwordStrength.unknown": "...", "app.passwordStrength.worst": "Unsatisfactory", - "app.pipeline.editSettings": "Edit pipeline", "app.pipeline.exercises": "Exercises:", "app.pipeline.failedDetail": "Loading the details of the pipeline failed. Please make sure you are connected to the Internet and try again later.", "app.pipeline.loadingDetail": "Loading pipeline detail", diff --git a/src/locales/whitelist_cs.json b/src/locales/whitelist_cs.json index e85b1b356..0f6512500 100644 --- a/src/locales/whitelist_cs.json +++ b/src/locales/whitelist_cs.json @@ -112,6 +112,7 @@ "app.homepage.githubLink", "app.homepage.title", "app.instancesTable.admin", + "app.navigation.pipeline", "app.passwordStrength.ok", "app.passwordStrength.unknown", "app.pipelines.boxesTable.port", diff --git a/src/pages/EditPipeline/EditPipeline.js b/src/pages/EditPipeline/EditPipeline.js index f83e0260b..0e560794c 100644 --- a/src/pages/EditPipeline/EditPipeline.js +++ b/src/pages/EditPipeline/EditPipeline.js @@ -8,6 +8,7 @@ import { reset } from 'redux-form'; import { defaultMemoize } from 'reselect'; import Page from '../../components/layout/Page'; +import { PipelineNavigation } from '../../components/layout/Navigation'; import Box from '../../components/widgets/Box'; import Callout from '../../components/widgets/Callout'; import EditPipelineForm from '../../components/forms/EditPipelineForm'; @@ -26,7 +27,7 @@ import { runtimeEnvironmentsSelector } from '../../redux/selectors/runtimeEnviro import { isLoggedAsSuperAdmin } from '../../redux/selectors/users'; import withLinks from '../../helpers/withLinks'; -import { arrayToObject } from '../../helpers/common'; +import { arrayToObject, hasPermissions } from '../../helpers/common'; // convert pipeline data into initial structure for pipeline edit metadata form const perpareInitialPipelineData = ({ name, description, version, parameters, author }) => ({ @@ -114,7 +115,13 @@ class EditPipeline extends Component { icon={} title={}> {pipeline => ( -
+ <> + + @@ -192,7 +199,7 @@ class EditPipeline extends Component { -
+ )} ); diff --git a/src/pages/Pipeline/Pipeline.js b/src/pages/Pipeline/Pipeline.js index 792a16e0d..3c2b55a40 100644 --- a/src/pages/Pipeline/Pipeline.js +++ b/src/pages/Pipeline/Pipeline.js @@ -2,27 +2,24 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; -import { Row, Col, ButtonGroup } from 'react-bootstrap'; +import { Row, Col } from 'react-bootstrap'; import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; import Page from '../../components/layout/Page'; +import { PipelineNavigation } from '../../components/layout/Navigation'; import Box from '../../components/widgets/Box'; -import Button from '../../components/widgets/TheButton'; -import { EditIcon, PipelineIcon } from '../../components/icons'; +import { PipelineIcon } from '../../components/icons'; // import ForkPipelineForm from '../../components/forms/ForkPipelineForm'; import { fetchPipelineIfNeeded, forkPipeline } from '../../redux/modules/pipelines'; import { getPipeline, pipelineEnvironmentsSelector } from '../../redux/selectors/pipelines'; -import { loggedInUserIdSelector } from '../../redux/selectors/auth'; -import { canEditPipeline } from '../../redux/selectors/users'; import { getVariablesUtilization } from '../../helpers/pipelines'; -import withLinks from '../../helpers/withLinks'; import PipelineDetail from '../../components/Pipelines/PipelineDetail'; import PipelineGraph from '../../components/Pipelines/PipelineGraph'; import ResourceRenderer from '../../components/helpers/ResourceRenderer'; import { fetchRuntimeEnvironments } from '../../redux/modules/runtimeEnvironments'; +import { hasPermissions } from '../../helpers/common'; class Pipeline extends Component { state = { @@ -48,9 +45,7 @@ class Pipeline extends Component { render() { const { - links: { PIPELINE_EDIT_URI_FACTORY }, pipeline, - isAuthorOfPipeline, // forkPipeline, runtimeEnvironments, } = this.props; @@ -62,27 +57,26 @@ class Pipeline extends Component { titleWindow={pipeline => pipeline.name} title={}> {pipeline => ( -
+ <> + + + {/* TODO Fork form needs redesigning (better selection of exercises).
- {isAuthorOfPipeline(pipeline.id) && ( - - - - )} - {/* TODO Fork form needs redesigning (better selection of exercises). forkPipeline(forkId, formData)} - /> */} + />
-

+ */} + {(...runtimes) => ( @@ -103,7 +97,7 @@ class Pipeline extends Component { -

+ )} ); @@ -118,41 +112,33 @@ Pipeline.propTypes = { pipelineId: PropTypes.string.isRequired, }).isRequired, }).isRequired, - isAuthorOfPipeline: PropTypes.func.isRequired, - links: PropTypes.object.isRequired, forkPipeline: PropTypes.func.isRequired, runtimeEnvironments: PropTypes.array, }; -export default withLinks( - connect( - ( - state, - { - match: { - params: { pipelineId }, - }, - } - ) => { - const userId = loggedInUserIdSelector(state); - - return { - pipeline: getPipeline(pipelineId)(state), - userId: loggedInUserIdSelector(state), - isAuthorOfPipeline: pipelineId => canEditPipeline(userId, pipelineId)(state), - runtimeEnvironments: pipelineEnvironmentsSelector(pipelineId)(state), - }; - }, - ( - dispatch, - { - match: { - params: { pipelineId }, - }, - } - ) => ({ - loadAsync: () => Pipeline.loadAsync({ pipelineId }, dispatch), - forkPipeline: (forkId, data) => dispatch(forkPipeline(pipelineId, forkId, data)), - }) - )(Pipeline) -); +export default connect( + ( + state, + { + match: { + params: { pipelineId }, + }, + } + ) => { + return { + pipeline: getPipeline(pipelineId)(state), + runtimeEnvironments: pipelineEnvironmentsSelector(pipelineId)(state), + }; + }, + ( + dispatch, + { + match: { + params: { pipelineId }, + }, + } + ) => ({ + loadAsync: () => Pipeline.loadAsync({ pipelineId }, dispatch), + forkPipeline: (forkId, data) => dispatch(forkPipeline(pipelineId, forkId, data)), + }) +)(Pipeline);