From 3fd7291511f33bf425ab7b998fbecbba8b8735c4 Mon Sep 17 00:00:00 2001 From: Chandrasekhar Ramakrishnan Date: Thu, 9 Feb 2023 13:29:51 +0100 Subject: [PATCH] refactor: remove unused references to issues, MRs, collaboration (#2256) (#2365) --- client/src/api-client/index.js | 4 - client/src/api-client/issue.js | 113 ------- client/src/api-client/merge-request.js | 145 --------- client/src/api-client/test-client.js | 20 -- client/src/api-client/test-samples/index.js | 3 +- .../api-client/test-samples/issue-samples.js | 75 ----- client/src/collaboration/issue/Issue.js | 299 ------------------ client/src/collaboration/issue/Issue.state.js | 86 ----- client/src/collaboration/issue/Issue.test.js | 194 ------------ .../lists-old/CollaborationList.container.js | 144 --------- .../lists-old/CollaborationList.state.js | 111 ------- .../lists-old/IssueList.present.js | 96 ------ .../lists-old/MergeRequestList.present.js | 114 ------- .../lists/CollaborationList.container.js | 97 ------ client/src/collaboration/lists/index.js | 0 .../merge-request/MergeRequest.container.js | 155 --------- .../merge-request/MergeRequest.present.js | 235 -------------- .../src/collaboration/merge-request/index.js | 20 -- .../contribution/Contribution.constants.js | 34 -- .../contribution/Contribution.container.js | 183 ----------- .../src/contribution/Contribution.present.js | 168 ---------- client/src/contribution/index.js | 22 -- client/src/landing/NavBar.js | 11 - client/src/model/RenkuModels.js | 42 +-- client/src/project/Project-Route.test.js | 12 - client/src/project/Project.css | 16 - client/src/project/Project.js | 55 +--- client/src/project/Project.present.js | 122 ------- .../src/project/new/ProjectNew.container.js | 1 - 29 files changed, 4 insertions(+), 2573 deletions(-) delete mode 100644 client/src/api-client/issue.js delete mode 100644 client/src/api-client/merge-request.js delete mode 100644 client/src/api-client/test-samples/issue-samples.js delete mode 100644 client/src/collaboration/issue/Issue.js delete mode 100644 client/src/collaboration/issue/Issue.state.js delete mode 100644 client/src/collaboration/issue/Issue.test.js delete mode 100644 client/src/collaboration/lists-old/CollaborationList.container.js delete mode 100644 client/src/collaboration/lists-old/CollaborationList.state.js delete mode 100644 client/src/collaboration/lists-old/IssueList.present.js delete mode 100644 client/src/collaboration/lists-old/MergeRequestList.present.js delete mode 100644 client/src/collaboration/lists/CollaborationList.container.js delete mode 100644 client/src/collaboration/lists/index.js delete mode 100644 client/src/collaboration/merge-request/MergeRequest.container.js delete mode 100644 client/src/collaboration/merge-request/MergeRequest.present.js delete mode 100644 client/src/collaboration/merge-request/index.js delete mode 100644 client/src/contribution/Contribution.constants.js delete mode 100644 client/src/contribution/Contribution.container.js delete mode 100644 client/src/contribution/Contribution.present.js delete mode 100644 client/src/contribution/index.js diff --git a/client/src/api-client/index.js b/client/src/api-client/index.js index 770489aeea..f53974a3b5 100644 --- a/client/src/api-client/index.js +++ b/client/src/api-client/index.js @@ -20,9 +20,7 @@ import addDatasetMethods from "./dataset"; import addEnvironmentMethods from "./environment"; import addGraphMethods from "./graph"; import addInstanceMethods from "./instance"; -import addIssueMethods from "./issue"; import addJobMethods from "./job"; -import addMergeRequestMethods from "./merge-request"; import addMigrationMethods from "./migration"; import addNotebookServersMethods from "./notebook-servers"; import addPipelineMethods from "./pipeline"; @@ -71,9 +69,7 @@ class APIClient { addEnvironmentMethods(this); addGraphMethods(this); addInstanceMethods(this); - addIssueMethods(this); addJobMethods(this); - addMergeRequestMethods(this); addMigrationMethods(this); addNotebookServersMethods(this); addPipelineMethods(this); diff --git a/client/src/api-client/issue.js b/client/src/api-client/issue.js deleted file mode 100644 index a184206df3..0000000000 --- a/client/src/api-client/issue.js +++ /dev/null @@ -1,113 +0,0 @@ -/*! - * Copyright 2017 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -function addIssueMethods(client) { - - client.getProjectIssues = (projectId, queryParams) => { - let headers = client.getBasicHeaders(); - queryParams.scope = "all"; - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/issues`, { - method: "GET", - headers: headers, - queryParams: queryParams - }); - }; - - - client.postProjectIssue = (projectPathWithNamespace, issue) => { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${encodeURIComponent(projectPathWithNamespace)}/issues`, { - method: "POST", - headers: headers, - body: JSON.stringify(issue) - }); - - }; - - - client.getProjectIssue = (projectId, issueIid) => { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/issues/${issueIid}/`, { - method: "GET", - headers: headers, - }); - - }; - - - client.getContributions = async (projectId, issueIid) => { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/issues/${issueIid}/notes`, { - method: "GET", - headers: headers - }); - - }; - - - client.getContributionsAnonymous = async (projectSlug, issueIid) => { - let headers = client.getBasicHeaders(); - - return client.clientFetch(`${client.baseUrl}/direct/${projectSlug}/-/issues/${issueIid}/discussions`, { - method: "GET", - headers: headers - }); - }; - - - client.postContribution = (projectId, issueIid, contribution) => { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/issues/${issueIid}/notes`, { - method: "POST", - headers: headers, - body: JSON.stringify({ body: contribution }) - }); - - }; - - - client.closeIssue = (projectId, issueIid) => { - return updateIssue(client, projectId, issueIid, { state_event: "close" }); - }; - - client.reopenIssue = (projectId, issueIid) => { - return updateIssue(client, projectId, issueIid, { state_event: "reopen" }); - }; -} - - -function updateIssue(client, projectId, issueIid, body) { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/issues/${issueIid}`, { - method: "PUT", - headers: headers, - body: JSON.stringify(body) - }); -} - -export default addIssueMethods; diff --git a/client/src/api-client/merge-request.js b/client/src/api-client/merge-request.js deleted file mode 100644 index 378dcb2f57..0000000000 --- a/client/src/api-client/merge-request.js +++ /dev/null @@ -1,145 +0,0 @@ -/*! - * Copyright 2017 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -function addMergeRequestMethods(client) { - - - client.createMergeRequest = (projectId, title, source_branch, target_branch, options = { - allow_collaboration: true, - remove_source_branch: true - }) => { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/merge_requests`, { - method: "POST", - headers: headers, - body: JSON.stringify({ - ...options, - title, - source_branch, - target_branch, - }) - }); - }; - - - client.getMergeRequests = (projectId, queryParams = { scope: "all", state: "opened", per_page: "100" }) => { - let headers = client.getBasicHeaders(); - const url = projectId ? `${client.baseUrl}/projects/${projectId}/merge_requests` : - `${client.baseUrl}/merge_requests`; - return client.clientFetch(url, { - method: "GET", - headers, - queryParams: { ...queryParams } - }); - }; - - - client.getMergeRequestChanges = (projectId, mrIid) => { - let headers = client.getBasicHeaders(); - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/merge_requests/${mrIid}/changes`, { - method: "GET", - headers - }); - }; - - - client.getDiscussions = (projectId, mrIid) => { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/merge_requests/${mrIid}/discussions`, { - method: "GET", - headers: headers - }); - }; - - - client.getMergeRequestCommits = (projectId, mrIid) => { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/merge_requests/${mrIid}/commits`, { - method: "GET", - headers: headers - }); - }; - - - client.postDiscussion = (projectId, mrIid, contribution) => { - let headers = client.getBasicHeaders(); - headers.append("Content-Type", "application/json"); - - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/merge_requests/${mrIid}/discussions`, { - method: "POST", - headers: headers, - body: JSON.stringify({ body: contribution }) - }); - }; - - - // Get all files in a project that have modifications in an open merge request. - // Return an object giving for each file with modifications an array of merge requests (iids) - // in which the file is modified. - // TODO: This method should go to the gateway. - client.getModifiedFiles = (projectId) => { - return client.getMergeRequests(projectId) - .then(resp => { - - // For each MR get the changes introduced by the MR, creates an array - // of promises. - const mergeRequestsChanges = resp.data.map((mergeRequest) => { - return client.getMergeRequestChanges(projectId, mergeRequest.iid); - }); - - // On resolution of all promises, form an object which lists for each file - // the merge requests that modify this file. - return Promise.all(mergeRequestsChanges) - .then((mrChangeResponses) => { - const openMrs = {}; - mrChangeResponses.forEach((mrChangeResponse) => { - const mrChange = mrChangeResponse.data; - const changesArray = mrChange.changes; - const mrInfo = { mrIid: mrChange.iid, source_branch: mrChange.source_branch }; - changesArray - // eslint-disable-next-line - .filter((change) => change.old_path === change.new_path) - // eslint-disable-next-line - .forEach((change) => { - if (!openMrs[change.old_path]) openMrs[change.old_path] = []; - openMrs[change.old_path].push(mrInfo); - }); - }); - return openMrs; - }); - }); - }; - - - client.mergeMergeRequest = (projectId, mrIid) => { - let headers = client.getBasicHeaders(); - return client.clientFetch(`${client.baseUrl}/projects/${projectId}/merge_requests/${mrIid}/merge`, { - method: "PUT", - headers, - body: JSON.stringify({ should_remove_source_branch: true }) - }); - }; - -} -export default addMergeRequestMethods; diff --git a/client/src/api-client/test-client.js b/client/src/api-client/test-client.js index 09e7454812..d476462130 100644 --- a/client/src/api-client/test-client.js +++ b/client/src/api-client/test-client.js @@ -47,34 +47,14 @@ const methods = { getProjectFile: { response: samples.projectNotebookFile }, - getProjectIssues: { - response: { - data: samples.issues - } - }, - getProjectIssue: { - response: { - data: samples.issues[0] - } - }, getModifiedFiles: { response: { data: [] } }, - getContributions: { - response: { - data: [] - } - }, getRepositoryTree: { response: [] }, - getMergeRequests: { - response: { - data: [] - } - }, getBranches: { response: { data: [] diff --git a/client/src/api-client/test-samples/index.js b/client/src/api-client/test-samples/index.js index 2d6b4bc7f8..77279fe5a1 100644 --- a/client/src/api-client/test-samples/index.js +++ b/client/src/api-client/test-samples/index.js @@ -1,7 +1,6 @@ -import { issues } from "./issue-samples"; import { namespaces } from "./namespaces"; import { projects, projectReadme, projectNotebookFile } from "./project-samples"; import { statuspage } from "./statuspage"; import { user } from "./user"; -export { issues, namespaces, projects, projectReadme, projectNotebookFile, statuspage, user }; +export { namespaces, projects, projectReadme, projectNotebookFile, statuspage, user }; diff --git a/client/src/api-client/test-samples/issue-samples.js b/client/src/api-client/test-samples/issue-samples.js deleted file mode 100644 index 5361e8f510..0000000000 --- a/client/src/api-client/test-samples/issue-samples.js +++ /dev/null @@ -1,75 +0,0 @@ -/* eslint-disable */ -export const issues = [ - { - "id": 4, - "iid": 2, - "project_id": 3, - "title": "asdf", - "description": "asdf", - "state": "opened", - "created_at": "2018-02-04T20:07:42.375Z", - "updated_at": "2018-02-04T20:07:42.375Z", - "closed_at": null, - "labels": [], - "milestone": null, - "assignees": [], - "author": { - "id": 1, - "name": "Administrator", - "username": "root", - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost/root" - }, - "assignee": null, - "user_notes_count": 0, - "upvotes": 0, - "downvotes": 0, - "due_date": null, - "confidential": false, - "discussion_locked": null, - "web_url": "http://localhost/root/A-first-project/issues/2", - "time_stats": { - "time_estimate": 0, - "total_time_spent": 0, - "human_time_estimate": null, - "human_total_time_spent": null - } - }, - { - "id": 3, - "iid": 1, - "project_id": 3, - "title": "This is an issue", - "description": "to discuss important things", - "state": "opened", - "created_at": "2018-02-02T15:52:23.674Z", - "updated_at": "2018-02-02T15:52:23.674Z", - "closed_at": null, - "labels": [], - "milestone": null, - "assignees": [], - "author": { - "id": 1, - "name": "Administrator", - "username": "root", - "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", - "web_url": "http://localhost/root" - }, - "assignee": null, - "user_notes_count": 0, - "upvotes": 0, - "downvotes": 0, - "due_date": null, - "confidential": false, - "discussion_locked": null, - "web_url": "http://localhost/root/A-first-project/issues/1", - "time_stats": { - "time_estimate": 0, - "total_time_spent": 0, - "human_time_estimate": null, - "human_total_time_spent": null - } - } -]; diff --git a/client/src/collaboration/issue/Issue.js b/client/src/collaboration/issue/Issue.js deleted file mode 100644 index 5b29777535..0000000000 --- a/client/src/collaboration/issue/Issue.js +++ /dev/null @@ -1,299 +0,0 @@ -/*! - * Copyright 2017 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -/** - * incubator-renku-ui - * - * Issue.js - * Module for issue features. - */ - -import React, { Component } from "react"; -import { Provider, connect } from "react-redux"; -import { Row, Col, Button } from "reactstrap"; -import { faGitlab } from "@fortawesome/free-brands-svg-icons"; -import { faBoxOpen, faBox } from "@fortawesome/free-solid-svg-icons"; -import { API_ERRORS } from "../../api-client"; -import { createStore } from "../../utils/helpers/EnhancedState"; -import State from "./Issue.state"; -import { Contribution, NewContribution } from "../../contribution"; -import { Loader } from "../../utils/components/Loader"; -import { issueFormSchema } from "../../model/RenkuModels"; -import { FormGenerator } from "../../utils/components/formgenerator"; -import _ from "lodash"; -import { ErrorAlert } from "../../utils/components/Alert"; -import { ExternalIconLink } from "../../utils/components/ExternalLinks"; -import { TooltipToggleButton } from "../../utils/components/Tooltip"; -import { GoBackButton } from "../../utils/components/buttons/Button"; -import { RenkuMarkdown } from "../../utils/components/markdown/RenkuMarkdown"; -import { TimeCaption } from "../../utils/components/TimeCaption"; - -let iFormSchema = _.cloneDeep(issueFormSchema); - -function New(props) { - - if (iFormSchema == null) - iFormSchema = _.cloneDeep(issueFormSchema); - - const issuesUrl = `/projects/${props.projectPathWithNamespace}/collaboration/issues`; - - const onCancel = (e, handlers) => { - handlers.removeDraft(); - props.history.push({ pathname: issuesUrl }); - }; - - const submitData = (mappedInputs) => { - let body = {}; - body.confidential = mappedInputs.visibility === "restricted"; - body.title = mappedInputs.title; - body.description = mappedInputs.description; - return [props.projectPathWithNamespace, body]; - }; - - const submitCallback = (e, mappedInputs, handlers) => { - handlers.setSubmitLoader({ value: true, text: "Creating issue, please wait..." }); - props.client.postProjectIssue(...submitData(mappedInputs)) - .then(newIssue => { - handlers.removeDraft(); - handlers.setSubmitLoader({ value: false, text: "" }); - props.history.push({ pathname: issuesUrl }); - }).catch(error=> { - handlers.setSubmitLoader({ value: false, text: "" }); - handlers.setServerErrors(error.message); - }); - }; - - return ( - - - - - - ); -} - -class IssueViewHeader extends Component { - - render() { - if (this.props.error !== undefined && this.props.error.case === API_ERRORS.notFoundError) { - return Error 404: The issue that was selected does not exist or could not be accessed. -

You can go back to the issues list and see available issues for this project.   - -
; - } - - if (this.props.error !== undefined) { - return Error: There was an error retrieving the issue. -

You can go back to the issues list and see available issues for this project.   - -
; - } - - if (this.props.title === undefined) - return ; - - const title = this.props.title || "no title"; - const description = this.props.description || " "; - const buttonText = this.props.state === "opened" ? "Close" : "Re-open"; - const externalUrl = this.props.externalUrl; - const externalIssueUrl = `${externalUrl}/issues/${this.props.iid}`; - const time = this.props.created_at; - const author = this.props.author ? this.props.author.username : null; - const buttonGit = ; - - const actionButton = - ; - - return
- - - -

{title}

- - - {buttonGit} - {actionButton} - -
- - - - - - - - - - - - -
; - } -} - -// We sort the date strings instead of actual Date objects here - ok due to ISO format. -const IssueViewContributions = (props) => props.contributions - .sort((el1, el2) => el1.created_at > el2.created_at ? 1 : -1) - .filter(c => c.system !== true) - .map(cont => ); - - -class IssueView extends Component { - render() { - const components = [ - , - , - ]; - if (this.props.state === "opened") components.push(); - return components; - } -} - -class View extends Component { - - constructor(props) { - super(props); - this._mounted = false; - this.store = createStore(State.View.reducer); - this.store.dispatch(this.retrieveIssue()); - this.state = { contributions: [] }; - } - - async componentDidMount() { - this._mounted = true; - this.retrieveContributions(); - } - - componentWillUnmount() { - this._mounted = false; - } - - retrieveIssue() { - return (dispatch) => { - return this.props.client.getProjectIssue(this.props.projectId, this.props.issueIid) - .then(resp => { - dispatch(State.View.setAll(resp.data)); - }).catch(error => { - dispatch(State.View.setAll({ error: error })); - }); - }; - } - - appendContribution(newContribution) { - this.setState(prevState => { - let newContributions = [...prevState.contributions]; - newContributions.push({ ...newContribution }); - return { ...prevState, contributions: newContributions }; - }); - } - - async retrieveContributions() { - const { client, projectId, issueIid, user, projectPathWithNamespace } = this.props; - let contributions; - try { - // use different query for anonymous and logged user - if (user.logged) { - const resp = await client.getContributions(projectId, issueIid); - contributions = resp.data; - } - else { - const resp = await client.getContributionsAnonymous(projectPathWithNamespace, issueIid); - contributions = resp.data.map(item => item.notes[0]); - } - if (!this._mounted) - return; - this.setState({ contributions }); - } - catch (e) { - this.setState({ contributions: [] }); - } - } - - - mapStateToProps(state, ownProps) { - return state; - } - - mapDispatchToProps(dispatch, ownProps) { - return { - onIssueStateChange: (e) => { - e.preventDefault(); - const issueState = this.store.getState().state; - - // FIXME: This is a terrible hack which relies on the issue being updated on the server before force-updating - // FIXME: the entire project component. The problem here ist that the Issue list and the Issue detail components - // FIXME: are siblings and they both hold the same information in their state (which is therefore duplicated). - // FIXME: On click, the respective state information in both siblings state needs to be updated. - // FIXME: The proper solution would be to elevate this information to the state their common parent and update - // FIXME: it there. - - if (issueState === "opened") { - this.props.client.closeIssue(this.props.projectId, this.props.issueIid) - .then(() => this.props.updateProjectView()); - } - else if (issueState === "closed") { - this.props.client.reopenIssue(this.props.projectId, this.props.issueIid) - .then(() => this.props.updateProjectView()); - } - else { - throw Error(`Unknown state ${this.props.state}`); - } - // We don't even need to dispatch anything as the entire project component needs to be re-rendered - // (and the information reloaded from the server) anyway. - // dispatch(State.View.IssueState.change()); - } - }; - } - - render() { - const VisibleIssueView = connect(this.mapStateToProps, this.mapDispatchToProps.bind(this))(IssueView); - let propsNoStore = { ...this.props, store: null }; - return - - ; - } -} - -export default { New, View }; diff --git a/client/src/collaboration/issue/Issue.state.js b/client/src/collaboration/issue/Issue.state.js deleted file mode 100644 index 7b3b9f3d5f..0000000000 --- a/client/src/collaboration/issue/Issue.state.js +++ /dev/null @@ -1,86 +0,0 @@ -/*! - * Copyright 2017 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -/** - * incubator-renku-ui - * - * IssueState.js - * Redux-based state-management code. - */ - -import { combineReducers } from "redux"; -import { slugFromTitle } from "../../utils/helpers/HelperFunctions"; - -function createSetAction(type, field, value) { - const payload = { [field]: value }; - return { type, payload }; -} - -function reduceState(type, state, action, initial) { - if (state == null) state = initial; - if (action.type !== type) return state; - // Can also use the explicit version below - // return Object.assign({}, state, action.payload) - return { ...state, ...action.payload }; -} - -const Core = { - set: (field, value) => { - const action = createSetAction("core", field, value); - if (field === "title") action.payload["displayId"] = slugFromTitle(value); - return action; - }, - reduce: (state, action) => { - return reduceState("core", state, action, { title: "", description: "", displayId: "" }); - } -}; - -const Visibility = { - set: (level) => { - return createSetAction("visibility", "level", level); - }, - reduce: (state, action) => { - return reduceState("visibility", state, action, { level: "public" }); - } -}; - -const IssueState = { - change: () => ({ type: "change_issue_state", payload: null }), - reduce: (appState, action) => { - if (!appState) return null; - const newIssueState = appState.state === "closed" ? "opened" : "closed"; - return { ...appState, state: newIssueState }; - } -}; - -const combinedFieldReducer = combineReducers({ - core: Core.reduce, - visibility: Visibility.reduce -}); - -const View = { Core, Visibility, IssueState, - setAll: (result) => ({ type: "server_return", payload: result }), - reducer: (state, action) => { - if (action.type === "change_issue_state") return IssueState.reduce(state, action); - if (action.type !== "server_return") return combinedFieldReducer(state, action); - // Take server result and set it to the state - return { ...state, ...action.payload }; - } -}; - -export default { View }; diff --git a/client/src/collaboration/issue/Issue.test.js b/client/src/collaboration/issue/Issue.test.js deleted file mode 100644 index ad9488f0b1..0000000000 --- a/client/src/collaboration/issue/Issue.test.js +++ /dev/null @@ -1,194 +0,0 @@ -/*! - * Copyright 2017 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -/** - * incubator-renku-ui - * - * Issue.test.js - * Tests for issue. - */ - -import React from "react"; -import { createRoot } from "react-dom/client"; - -import { MemoryRouter } from "react-router-dom"; -import { createMemoryHistory } from "history"; - -import Issue from "./Issue"; -import { CollaborationIframe, CollaborationList, collaborationListTypeMap } from "../lists/CollaborationList.container"; -import State from "./Issue.state"; -import { testClient as client } from "../../api-client"; -import { generateFakeUser } from "../../user/User.test"; -import { StateModel, globalSchema } from "../../model"; -import { act } from "react-dom/test-utils"; -import TestRenderer from "react-test-renderer"; -import { sleep } from "../../utils/helpers/HelperFunctions"; - -describe("rendering", () => { - const user = generateFakeUser(true); - - let spy = null; - const model = new StateModel(globalSchema); - - beforeEach(() => { - // ckeditor dumps some junk to the console.error. Ignore it. - spy = jest.spyOn(console, "error").mockImplementation(() => {}); - }); - - afterEach(() => { - spy.mockRestore(); - }); - - it("renders new without crashing", () => { - const div = document.createElement("div"); - const root = createRoot(div); - root.render(); - }); - it("renders list without crashing", () => { - const baseUrl = "base"; - const urlMap = { - issuesUrl: `${baseUrl}/collaboration`, - issueNewUrl: `${baseUrl}/issue_new`, - issueUrl: `${baseUrl}/collaboration/issues/:issueIid(\\d+)`, - }; - const div = document.createElement("div"); - const root = createRoot(div); - const fakeHistory = createMemoryHistory({ - initialEntries: ["/"], - initialIndex: 0, - }); - fakeHistory.push({ - pathname: "/issues", - search: "?page=1&issuesState=opened" - }); - - const props = { - externalUrl: "https://dev.renku.ch/gitlab" - }; - root.render( - - - ); - }); - it("renders view without crashing", () => { - const div = document.createElement("div"); - document.body.appendChild(div); - const root = createRoot(div); - root.render( - - - ); - }); - it("renders iframe when url is valid", async () => { - const mockValidUrl = "https://dev.renku.ch/gitlab"; - const mockUrlServer = "https://dev.renku.ch/ui-server"; - const mockRef = null; - const client = { - uiserverUrl: mockUrlServer, - isValidUrlForIframe: () => true, - }; - - let rendered; - act(() => { - rendered = TestRenderer.create( - - {}} - iframeUrl={mockValidUrl} - listType={collaborationListTypeMap.ISSUES} - client={client}/> - , - ); - }); - const initialRender = rendered.toJSON(); - // Wait until the data is fetched - await sleep(0); - const finalRender = rendered.toJSON(); - expect(initialRender.type).toBe("div"); - expect(finalRender.type).toBe("iframe"); - }); - it("no renders iframe when url is invalid", async () => { - const mockInvalidUrl = "https://dev.renku.ch/gitlab"; - const mockUrlServer = "https://dev.renku.ch/ui-server"; - const mockRef = null; - const client = { uiserverUrl: mockUrlServer, isValidUrlForIframe: () => false }; - - let rendered; - act(() => { - rendered = TestRenderer.create( - - {}} - iframeUrl={mockInvalidUrl} - listType={collaborationListTypeMap.ISSUES} - client={client}/> - , - ); - }); - // Wait until the data is fetched - await sleep(0); - const finalRender = rendered.toJSON(); - expect(finalRender.type).toBe("div"); - expect(finalRender.children[0]).toBe("This Gitlab instance cannot be embedded in RenkuLab. Please"); - }); -}); - -describe("issue view actions", () => { - it("creates a server return action", () => { - expect(State.View.setAll({ core: { title: "A Title", description: "A desc", displayId: "a-title" } })) - .toEqual({ - type: "server_return", payload: { - core: - { title: "A Title", description: "A desc", displayId: "a-title" } - } - }); - }); -}); - -describe("issue view reducer", () => { - const initialState = State.View.reducer(undefined, {}); - it("returns initial state", () => { - expect(initialState).toEqual({ - core: { title: "", description: "", displayId: "" }, - visibility: { level: "public" } - }); - }); - it("advances state", () => { - const state1 = State.View.reducer(initialState, State.View.setAll({ - core: - { title: "A Title", description: "A desc", displayId: "a-title" } - })); - expect(state1) - .toEqual({ - core: { title: "A Title", description: "A desc", displayId: "a-title" }, - visibility: { level: "public" } - }); - }); -}); diff --git a/client/src/collaboration/lists-old/CollaborationList.container.js b/client/src/collaboration/lists-old/CollaborationList.container.js deleted file mode 100644 index 0916e73f7f..0000000000 --- a/client/src/collaboration/lists-old/CollaborationList.container.js +++ /dev/null @@ -1,144 +0,0 @@ -/*! - * Copyright 2018 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 React, { Component } from "react"; -import { connect } from "react-redux"; -import IssueList from "./IssueList.present"; -import MergeRequestList from "./MergeRequestList.present"; -import CollaborationListModel from "./CollaborationList.state"; -import qs from "query-string"; - -const itemsStateMap = { - OPENED: "opened", - MERGED: "merged", - CLOSED: "closed" -}; - -const collaborationListTypeMap = { - ISSUES: "issues", - MREQUESTS: "mrequests" // eslint-disable-line -}; - -class List extends Component { - constructor(props) { - super(props); - this.state = {}; - this.model = new CollaborationListModel(props.client, props.projectId, props.fetchElements); - this.handlers = { - setItemsState: this.setItemsState.bind(this), - onPaginationPageChange: this.onPaginationPageChange.bind(this), - }; - } - - componentDidMount() { - const { pathName, pageNumber, itemsState } = this.getUrlSearchParameters(this.props.location); - this.model.setPathName(pathName); - this.model.setItemsState(itemsState); - this.model.setPage(pageNumber); - this.model.performSearch(); - - const listener = this.props.history.listen(location => { - const { pathName, pageNumber, itemsState } = this.getUrlSearchParameters(location); - this.onUrlParametersChange(pathName, pageNumber, itemsState); - }); - this.setState({ listener }); - } - - urlFromQueryAndPageNumber(pathName, pageNumber, itemsState) { - return `${pathName}?page=${pageNumber}&itemsState=${itemsState}`; - } - - getUrlSearchParameters(location) { - const itemsState = qs.parse(location.search).itemsState || itemsStateMap.OPENED; - const pathName = location.pathname.endsWith("/") ? - location.pathname.substring(0, location.pathname.length - 1) : - location.pathname; - const pageNumber = parseInt(qs.parse(location.search).page, 10) || 1; - return { pathName, pageNumber, itemsState }; - } - - componentWillUnmount() { - const { listener } = this.state; - if (listener) - listener(); - } - - onUrlParametersChange(pathName, pageNumber, itemsState) { - // workaround to prevent the listener of "this.props.history.listen" to trigger in the wrong path - // INFO: check if the path matches [/items$, /items/$, /items?*, /items/\D*] - const regExp = this.props.listType === collaborationListTypeMap.ISSUES ? - /\/issues($|\/$|(\/|\?)\D+.*)$/ : - /\/mergerequests($|\/$|(\/|\?)\D+.*)$/ - ; - if (!regExp.test(pathName)) - return; - - this.model.setQueryAndSearch(pathName, pageNumber, itemsState); - } - - onPaginationPageChange(pageNumber) { - this.model.setPage(pageNumber); - this.pushNewSearchToHistory(); - } - - pushNewSearchToHistory() { - this.props.history.push( - this.urlFromQueryAndPageNumber( - this.model.get("pathName"), - this.model.get("currentPage"), - this.model.get("itemsState") - ) - ); - } - - setItemsState(itemsState) { - this.model.setItemsState(itemsState); - this.pushNewSearchToHistory(); - } - - mapStateToProps(ownProps) { - return { - loading: this.model.get("loading"), - items: this.model.get("items"), - itemsState: this.model.get("itemsState"), - errorMessage: this.model.get("errorMessage"), - currentPage: this.model.get("currentPage"), - perPage: this.model.get("perPage"), - totalItems: this.model.get("totalItems"), - onPageChange: this.handlers.onPaginationPageChange, - }; - } - - render() { - - const VisibleItemsList = this.props.listType === collaborationListTypeMap.ISSUES ? - connect(this.mapStateToProps.bind(this))(IssueList) - : connect(this.mapStateToProps.bind(this))(MergeRequestList); - - return ; - } -} - -export { List as CollaborationList, itemsStateMap, collaborationListTypeMap }; diff --git a/client/src/collaboration/lists-old/CollaborationList.state.js b/client/src/collaboration/lists-old/CollaborationList.state.js deleted file mode 100644 index 0836981499..0000000000 --- a/client/src/collaboration/lists-old/CollaborationList.state.js +++ /dev/null @@ -1,111 +0,0 @@ -/*! - * Copyright 2019 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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. - */ - -/** - * renku-ui - * - * CollaborationList.state.js - * Redux-based state-management code. - */ - -import { Schema, StateKind, StateModel } from "../../model/Model"; - -const collaborationListSchema = new Schema({ - loading: { initial: false }, - currentPage: { initial: 1, mandatory: true }, - totalItems: { initial: 0, mandatory: true }, - perPage: { initial: 10, mandatory: true }, - itemsState: { initial: "opened", mandatory: true }, - initialized: { initial: true }, - projectId: { mandatory: true }, - items: { initial: [] }, - errorMessage: { initial: "" } -}); - -class CollaborationListModel extends StateModel { - constructor(client, projectId, fetchElements) { - super(collaborationListSchema, StateKind.REDUX); - this.client = client; - this.projectId = projectId; - this.fetchElements = fetchElements; - } - - setInitialized(initialized) { - this.setObject({ - items: { $set: [] }, - initialized: initialized, - errorMessage: "", - }); - } - - setPathName(pathName) { - this.set("pathName", pathName); - } - - setItemsState(itemsState) { - this.set("itemsState", itemsState); - } - - setPage(page) { - this.set("currentPage", parseInt(page, 10)); - } - - resetBeforeNewSearch() { - this.setObject({ - currentPage: 1, - pages: { $set: [] } - }); - } - - manageResponse(response) { - const { pagination } = response; - const newData = { - items: { $set: response.data }, - currentPage: pagination.currentPage, - totalItems: pagination.totalItems, - initialized: false, - errorMessage: "", - loading: false - }; - this.setObject(newData); - return newData; - } - - setQueryAndSearch( pathName, pageNumber, itemsState) { - this.setPathName(pathName); - this.setPage(pageNumber); - this.setItemsState(itemsState); - this.performSearch(); - } - - performSearch() { - if (this.get("loading")) return; - this.set("loading", true); - const queryParams = { - per_page: this.get("perPage"), - page: this.get("currentPage"), - state: this.get("itemsState"), - order_by: "updated_at" - }; - - return this.fetchElements(this.projectId, queryParams) - .then(response => this.manageResponse(response)); - } -} - -export default CollaborationListModel; diff --git a/client/src/collaboration/lists-old/IssueList.present.js b/client/src/collaboration/lists-old/IssueList.present.js deleted file mode 100644 index c15b25c435..0000000000 --- a/client/src/collaboration/lists-old/IssueList.present.js +++ /dev/null @@ -1,96 +0,0 @@ - -import React, { Component } from "react"; -import { Link } from "react-router-dom"; -import { Row, Col, Badge } from "reactstrap"; -import { faComments } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { itemsStateMap } from "./CollaborationList.container"; -import { RenkuMarkdown } from "../../utils/components/markdown/RenkuMarkdown"; -import { TimeCaption } from "../../utils/components/TimeCaption"; -import { Loader } from "../../utils/components/Loader"; -import { Pagination } from "../../utils/components/Pagination"; - -function issueStateBadge(issueStateValue) { - let issueState = {issueStateValue}; - if (issueStateValue === itemsStateMap.OPENED) - issueState = open; - if (issueStateValue === itemsStateMap.CLOSED) - issueState = complete; - return issueState; -} - -class IssueListRow extends Component { - render() { - const issueIid = this.props.iid; - const issueUrl = `${this.props.issueBaseUrl}/issues/${issueIid}/`; - const issueState = issueStateBadge(this.props.state); - const titleText = this.props.title || "no title"; - - return - - -
- {titleText} -
-
- -
-
- -
- - - - {this.props.user_notes_count}{" "}{issueState} - - - ; - } -} - -class IssueList extends Component { - render() { - const { items, user } = this.props; - const rows = items.length > 0 ? items.map((d, i) => - ) - : - - No issues to display. - - ; - - const newIssueButton = (user.logged) ? -
- - - New Issue - -
- - : - null; - - return [ - -

Issues List

- {newIssueButton} - -
- , - - {this.props.loading ? - : -
{rows}
- } - -
- , - ]; - } -} - -export default IssueList; diff --git a/client/src/collaboration/lists-old/MergeRequestList.present.js b/client/src/collaboration/lists-old/MergeRequestList.present.js deleted file mode 100644 index 853625d094..0000000000 --- a/client/src/collaboration/lists-old/MergeRequestList.present.js +++ /dev/null @@ -1,114 +0,0 @@ - -import React, { Component } from "react"; -import { Link } from "react-router-dom"; -import { Row, Col, Badge } from "reactstrap"; -import { faComments } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { itemsStateMap } from "./CollaborationList.container"; -import { faLongArrowAltLeft as faLeftArrow } from "@fortawesome/free-solid-svg-icons"; -import { TimeCaption } from "../../utils/components/TimeCaption"; -import { Loader } from "../../utils/components/Loader"; -import { Pagination } from "../../utils/components/Pagination"; - -/** - * Extract the display info from a merge request object. - * @param {object} mr the merge request - * @returns {object} the display information - */ -function mergeRequestRowInfo(mr) { - const status = (mr.closed_at !== null) ? - itemsStateMap.CLOSED : - (mr.merged_at !== null) ? - itemsStateMap.MERGED : - itemsStateMap.OPENED; - let badgeText = "", badgeColor = "", timeCaption = null; - if (status === itemsStateMap.CLOSED) { - badgeText = "Closed"; - badgeColor = "success"; - timeCaption = ; - } - else if (status === itemsStateMap.MERGED) { - badgeText = "Merged"; - badgeColor = "success"; - timeCaption = ; - } - else { - badgeText = mr.merge_status === "can_be_merged" ? "Can be merged" : "Conflicts"; - badgeColor = mr.merge_status === "can_be_merged" ? "success" : "danger"; - timeCaption = ; - } - - return { - badgeText, badgeColor, timeCaption - }; -} - -class MergeRequestListRow extends Component { - render() { - const rowInfo = mergeRequestRowInfo(this.props); - const { badgeText, badgeColor, timeCaption } = rowInfo; - const statusBadge = {badgeText}; - - return - - -
- {this.props.title} -
-
- -
- {this.props.target_branch} - - {this.props.source_branch} -
-
-
-
- {timeCaption} -
- - - - {this.props.user_notes_count} {statusBadge} - - - ; - } -} - - -class MergeRequestList extends Component { - render() { - const { items } = this.props; - - const rows = items.length > 0 ? items.map((d, i) => { - const mrUrl = `${this.props.mergeRequestsOverviewUrl}/${d.iid}/changes`; - return ; - }) - : - - No merge requests to display. - - ; - - return [ - - -

Merge Requests List

- -
, - - {this.props.loading ? - : -
{rows}
- } -
, - - ]; - } -} - -export default MergeRequestList; -export { mergeRequestRowInfo }; diff --git a/client/src/collaboration/lists/CollaborationList.container.js b/client/src/collaboration/lists/CollaborationList.container.js deleted file mode 100644 index 61b1aa628f..0000000000 --- a/client/src/collaboration/lists/CollaborationList.container.js +++ /dev/null @@ -1,97 +0,0 @@ -/*! - * Copyright 2018 - Swiss Data Science Center (SDSC) - * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and - * Eidgenössische Technische Hochschule Zürich (ETHZ). - * - * 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 React, { Fragment, useEffect, useRef, useState } from "react"; -import { ExternalLink } from "../../utils/components/ExternalLinks"; -import { Loader } from "../../utils/components/Loader"; - -const itemsStateMap = { - OPENED: "opened", - MERGED: "merged", - CLOSED: "closed" -}; - -const collaborationListTypeMap = { - ISSUES: "issues", - MREQUESTS: "mrequests" // eslint-disable-line -}; - -const CollaborationIframe = (props) => { - const [isUrlValid, setIsUrlValid] = useState(false); - - useEffect( () => { - async function validateUrl() { - const isValid = await props.client.isValidUrlForIframe(props.iframeUrl); - setIsUrlValid(isValid); - if (!isValid) - props.onIFrameLoad(); - } - validateUrl(); - }, [props.iframeUrl]); // eslint-disable-line - - const type = props.listType === collaborationListTypeMap.ISSUES ? "Issues" : "Merge Requests"; - - return isUrlValid ? -