Allow issue creation via UI
This commit is contained in:
parent
d510116c4b
commit
a7297e3d12
|
@ -2,13 +2,36 @@ import React from 'react';
|
||||||
import { Page } from '../Page';
|
import { Page } from '../Page';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Board from '@lourenci/react-kanban';
|
import Board from '@lourenci/react-kanban';
|
||||||
import { fetchIssues } from '../../store/actions/issues';
|
|
||||||
import { fetchBoards } from '../../store/actions/boards';
|
import { fetchBoards } from '../../store/actions/boards';
|
||||||
|
import { createIssue } from '../../store/actions/issues';
|
||||||
import { buildKanboard, moveCard } from '../../store/actions/kanboards';
|
import { buildKanboard, moveCard } from '../../store/actions/kanboards';
|
||||||
import { Loader } from '../Loader';
|
import { Loader } from '../Loader';
|
||||||
|
import { IssueCard } from './IssueCard';
|
||||||
|
import { Modal } from '../Modal';
|
||||||
|
|
||||||
export class BoardPage extends React.Component {
|
export class BoardPage extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
newCardModalActive: false,
|
||||||
|
newCardLaneID: 0,
|
||||||
|
newCard: {
|
||||||
|
title: "",
|
||||||
|
body: "",
|
||||||
|
project: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.renderLaneHeader = this.renderLaneHeader.bind(this);
|
||||||
|
this.onNewCardClick = this.onNewCardClick.bind(this);
|
||||||
|
this.onNewCardCloseClick = this.onNewCardCloseClick.bind(this);
|
||||||
|
this.onNewCardTitleChange = this.onNewCardAttrChange.bind(this, 'title');
|
||||||
|
this.onNewCardBodyChange = this.onNewCardAttrChange.bind(this, 'body');
|
||||||
|
this.onNewCardProjectChange = this.onNewCardAttrChange.bind(this, 'project');
|
||||||
|
this.onNewCardSaveClick = this.onNewCardSaveClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
@ -34,54 +57,99 @@ export class BoardPage extends React.Component {
|
||||||
onCardDragEnd={this.onCardDragEnd.bind(this)}>
|
onCardDragEnd={this.onCardDragEnd.bind(this)}>
|
||||||
{kanboard}
|
{kanboard}
|
||||||
</Board>
|
</Board>
|
||||||
|
{ this.renderNewCardModal() }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCard(card) {
|
renderNewCardModal() {
|
||||||
|
const { newCardModalActive, newCardLaneID } = this.state;
|
||||||
|
const { board } = this.props;
|
||||||
|
if (!board) return null;
|
||||||
return (
|
return (
|
||||||
<div className="kanboard-card">
|
<Modal active={newCardModalActive}>
|
||||||
<div className="box">
|
<div className="new-card-modal">
|
||||||
<div className="media">
|
<article className="message">
|
||||||
{
|
<div className="message-header">
|
||||||
card.issue.assignee ?
|
<p><span>"{board.lanes[newCardLaneID].title}" - Nouveau ticket</span></p>
|
||||||
<div class="media-left">
|
<button className="delete" aria-label="delete" onClick={this.onNewCardCloseClick}></button>
|
||||||
<figure class="image is-64x64">
|
</div>
|
||||||
<img src={card.issue.assignee.avatar_url} alt="Image" />
|
<div className="message-body">
|
||||||
<small>{`@${card.issue.assignee.login}`}</small>
|
<div className="field">
|
||||||
</figure>
|
<div className="control">
|
||||||
|
<input className="input is-medium" type="text"
|
||||||
|
placeholder="Titre de votre ticket..."
|
||||||
|
value={this.state.newCard.title}
|
||||||
|
onChange={this.onNewCardTitleChange} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: null
|
<div className="field">
|
||||||
}
|
<div className="control">
|
||||||
<div className="media-content">
|
<textarea className="textarea"
|
||||||
<div className="content">
|
placeholder="Description du nouveau ticket..."
|
||||||
<p>
|
value={this.state.newCard.body}
|
||||||
<strong>{`#${card.issue.number}`}</strong>
|
onChange={this.onNewCardBodyChange}
|
||||||
{ card.issue.milestone ? <small>{`- ${card.issue.milestone.title}`}</small> : null }
|
rows="10">
|
||||||
<br />
|
</textarea>
|
||||||
<span className="is-size-6">{card.issue.title}</span>
|
</div>
|
||||||
</p>
|
</div>
|
||||||
<div className="level is-mobile">
|
<div className="field">
|
||||||
<div className="level-left"></div>
|
<div className="control is-expanded">
|
||||||
<div className="level-right">
|
<div className="select is-fullwidth"
|
||||||
<a className="level-item" target="_blank" href={card.issue.url.replace('/api/v1/repos', '')}>
|
value={this.state.newCard.project}
|
||||||
<span className="icon is-small has-text-info">
|
onChange={this.onNewCardProjectChange}>
|
||||||
<i className="fas fa-search" aria-hidden="true"></i>
|
<select>
|
||||||
</span>
|
{
|
||||||
</a>
|
board.projects.map((p, i) => {
|
||||||
|
return <option key={`new-card-project-${i}`} value={p}>{p}</option>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="field is-grouped is-grouped-right">
|
||||||
|
<p className="control">
|
||||||
|
<a className="button is-light"
|
||||||
|
onClick={this.onNewCardCloseClick}>
|
||||||
|
Annuler
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className="control">
|
||||||
|
<a className="button is-primary"
|
||||||
|
onClick={this.onNewCardSaveClick}>
|
||||||
|
Enregistrer
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Modal>
|
||||||
);
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCard(card) {
|
||||||
|
return <IssueCard card={card} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLaneHeader(lane) {
|
renderLaneHeader(lane) {
|
||||||
return (
|
return (
|
||||||
<h3 className="kanboard-lane-title is-size-3">{lane.title}</h3>
|
<div className="kanboard-lane">
|
||||||
|
<div className="level">
|
||||||
|
<div className="level-left">
|
||||||
|
<h3 className="level-item is-size-3">{lane.title}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="level-right">
|
||||||
|
<button className="button is-light level-item is-medium"
|
||||||
|
onClick={this.onNewCardClick.bind(this, lane.id)}>
|
||||||
|
<span className="icon">
|
||||||
|
<i className="fas fa-plus" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +174,48 @@ export class BoardPage extends React.Component {
|
||||||
this.requestBuildKanboard();
|
this.requestBuildKanboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onNewCardClick(laneID) {
|
||||||
|
const { board } = this.props;
|
||||||
|
this.setState({
|
||||||
|
newCardModalActive: true,
|
||||||
|
newCardLaneID: laneID,
|
||||||
|
newCard: {
|
||||||
|
title: "",
|
||||||
|
body: "",
|
||||||
|
project: board.projects[0],
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewCardCloseClick() {
|
||||||
|
this.setState({ newCardModalActive: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewCardAttrChange(attrName, evt) {
|
||||||
|
const value = evt.target.value;
|
||||||
|
this.setState(state => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
newCard: {
|
||||||
|
...state.newCard,
|
||||||
|
[attrName]: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewCardSaveClick() {
|
||||||
|
const { newCard, newCardLaneID } = this.state;
|
||||||
|
const { board } = this.props;
|
||||||
|
this.setState({ newCardModalActive: false });
|
||||||
|
this.props.dispatch(createIssue(
|
||||||
|
newCard.project,
|
||||||
|
newCard.title,
|
||||||
|
newCard.body,
|
||||||
|
board.lanes[newCardLaneID].issueLabel
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (prevProps.board !== this.props.board) this.requestBuildKanboard();
|
if (prevProps.board !== this.props.board) this.requestBuildKanboard();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export class IssueCard extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { card } = this.props;
|
||||||
|
return (
|
||||||
|
<div className="kanboard-card">
|
||||||
|
<div className="box">
|
||||||
|
<div className="media">
|
||||||
|
{
|
||||||
|
card.issue.assignee ?
|
||||||
|
<div className="media-left">
|
||||||
|
<figure className="image is-64x64">
|
||||||
|
<img src={card.issue.assignee.avatar_url} alt="Image" />
|
||||||
|
</figure>
|
||||||
|
<small>{`@${card.issue.assignee.login}`}</small>
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div className="media-content">
|
||||||
|
<div className="content">
|
||||||
|
<p>
|
||||||
|
<strong>{`#${card.issue.number}`}</strong>
|
||||||
|
{ card.issue.milestone ? <small>{`- ${card.issue.milestone.title}`}</small> : null }
|
||||||
|
<br />
|
||||||
|
<span className="is-size-6">{card.issue.title}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="level is-mobile" style={{marginTop:'1rem'}}>
|
||||||
|
<div className="level-left">
|
||||||
|
<small className="level-item"><a href="#">{card.project}</a></small>
|
||||||
|
</div>
|
||||||
|
<div className="level-right">
|
||||||
|
<a className="level-item" target="_blank" href={card.issue.url.replace('/api/v1/repos', '')}>
|
||||||
|
<span className="icon is-small has-text-info">
|
||||||
|
<i className="fas fa-search" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ export class HomePage extends React.Component {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<div className="container is-fluid">
|
<div className="container is-fluid">
|
||||||
<div className="level">
|
<div className="level has-margin-top-normal">
|
||||||
<div className="level-left"></div>
|
<div className="level-left"></div>
|
||||||
<div className="level-right">
|
<div className="level-right">
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export class Modal extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const { children, active, showCloseButton, onClose } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={`modal ${active ? 'is-active': ''}`}>
|
||||||
|
<div className="modal-background"></div>
|
||||||
|
<div className="modal-content">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
showCloseButton ?
|
||||||
|
<button onClick={onClose} className="modal-close is-large" aria-label="close"></button> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export class Navbar extends React.Component {
|
export class Navbar extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<nav className="navbar" role="navigation" aria-label="main navigation" style={{marginBottom: '1.5rem'}}>
|
<nav className="navbar" role="navigation" aria-label="main navigation">
|
||||||
<div className="container is-fluid">
|
<div className="container is-fluid">
|
||||||
<div className="navbar-brand">
|
<div className="navbar-brand">
|
||||||
<a className="navbar-item" href="#/">
|
<a className="navbar-item" href="#/">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Navbar } from './Navbar';
|
import { Navbar } from './Navbar';
|
||||||
|
|
||||||
export class Page extends React.Component {
|
export class Page extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
|
|
@ -7,6 +7,10 @@ html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-margin-top-normal {
|
||||||
|
margin-top: $size-normal;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -34,7 +34,13 @@
|
||||||
margin-bottom: $size-small;
|
margin-bottom: $size-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanboard-lane-title {
|
.kanboard-lane {
|
||||||
margin-bottom: $size-small;
|
margin-bottom: $size-small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 50% !important;
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
.lds-ripple div {
|
.lds-ripple div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border: 4px solid $info;
|
border: 4px solid $grey;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||||
|
|
|
@ -21,3 +21,11 @@ export const REMOVE_LABEL_FAILURE = "REMOVE_LABEL_FAILURE";
|
||||||
export function removeLabel(project, issueNumber, label) {
|
export function removeLabel(project, issueNumber, label) {
|
||||||
return { type: REMOVE_LABEL_REQUEST, project, issueNumber, label };
|
return { type: REMOVE_LABEL_REQUEST, project, issueNumber, label };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CREATE_ISSUE_REQUEST = "CREATE_ISSUE_REQUEST";
|
||||||
|
export const CREATE_ISSUE_SUCCESS = "CREATE_ISSUE_SUCCESS";
|
||||||
|
export const CREATE_ISSUE_FAILURE = "CREATE_ISSUE_FAILURE";
|
||||||
|
|
||||||
|
export function createIssue(project, title, body, label) {
|
||||||
|
return { type: CREATE_ISSUE_REQUEST, project, title, body, label };
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import { FETCH_ISSUES_SUCCESS } from "../actions/issues";
|
import { FETCH_ISSUES_SUCCESS, CREATE_ISSUE_SUCCESS } from "../actions/issues";
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
byProject: {}
|
byProject: {}
|
||||||
|
@ -8,6 +8,8 @@ export function issuesReducer(state = defaultState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case FETCH_ISSUES_SUCCESS:
|
case FETCH_ISSUES_SUCCESS:
|
||||||
return handleFetchIssuesSuccess(state, action);
|
return handleFetchIssuesSuccess(state, action);
|
||||||
|
case CREATE_ISSUE_SUCCESS:
|
||||||
|
return handleCreateIssueSuccess(state, action);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -25,3 +27,16 @@ function handleFetchIssuesSuccess(state, action) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCreateIssueSuccess(state, action) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
byProject: {
|
||||||
|
...state.byProject,
|
||||||
|
[action.project]: [
|
||||||
|
...state.byProject[action.project],
|
||||||
|
action.issue
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { BUILD_KANBOARD_SUCCESS, MOVE_CARD } from "../actions/kanboards";
|
import { BUILD_KANBOARD_SUCCESS, MOVE_CARD } from "../actions/kanboards";
|
||||||
|
import { CREATE_ISSUE_SUCCESS } from "../actions/issues";
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
byID: {},
|
byID: {},
|
||||||
|
|
|
@ -2,8 +2,6 @@ import { put, call } from 'redux-saga/effects';
|
||||||
import { FETCH_BOARDS_SUCCESS, SAVE_BOARD_SUCCESS, SAVE_BOARD_FAILURE, FETCH_BOARDS_FAILURE } from '../actions/boards';
|
import { FETCH_BOARDS_SUCCESS, SAVE_BOARD_SUCCESS, SAVE_BOARD_FAILURE, FETCH_BOARDS_FAILURE } from '../actions/boards';
|
||||||
import { api } from '../../util/api';
|
import { api } from '../../util/api';
|
||||||
|
|
||||||
const boardsLocalStorageKey = 'giteakan.boards';
|
|
||||||
|
|
||||||
export function* fetchBoardsSaga() {
|
export function* fetchBoardsSaga() {
|
||||||
let boards;
|
let boards;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { put, call, retry } from 'redux-saga/effects';
|
import { put, call, retry } from 'redux-saga/effects';
|
||||||
import { FETCH_ISSUES_SUCCESS, FETCH_ISSUES_FAILURE, ADD_LABEL_FAILURE, ADD_LABEL_SUCCESS, REMOVE_LABEL_FAILURE, REMOVE_LABEL_SUCCESS } from '../actions/issues';
|
import { FETCH_ISSUES_SUCCESS, FETCH_ISSUES_FAILURE, ADD_LABEL_FAILURE, ADD_LABEL_SUCCESS, REMOVE_LABEL_FAILURE, REMOVE_LABEL_SUCCESS, CREATE_ISSUE_FAILURE, CREATE_ISSUE_SUCCESS } from '../actions/issues';
|
||||||
import { gitea } from '../../util/gitea';
|
import { gitea } from '../../util/gitea';
|
||||||
|
|
||||||
export function* fetchIssuesSaga(action) {
|
export function* fetchIssuesSaga(action) {
|
||||||
|
@ -64,5 +64,26 @@ export function* removeLabelSaga(action) {
|
||||||
|
|
||||||
|
|
||||||
yield put({ type: REMOVE_LABEL_SUCCESS, project, issueNumber, label });
|
yield put({ type: REMOVE_LABEL_SUCCESS, project, issueNumber, label });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* createIssueSaga(action) {
|
||||||
|
const { project, title, label, body } = action;
|
||||||
|
const labels = yield call(gitea.fetchProjectLabels.bind(gitea), project);
|
||||||
|
const giteaLabel = labels.find(l => l.name === label)
|
||||||
|
|
||||||
|
if (!giteaLabel) {
|
||||||
|
yield put({ type: CREATE_ISSUE_FAILURE, error: new Error(`Label "${label}" not found !`) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let issue;
|
||||||
|
try {
|
||||||
|
issue = yield call(gitea.createIssue.bind(gitea), project, title, body, giteaLabel.id);
|
||||||
|
} catch(error) {
|
||||||
|
yield put({ type: CREATE_ISSUE_FAILURE, error });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
yield put({ type: CREATE_ISSUE_SUCCESS, project, title, label, body, issue });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { select, put } from 'redux-saga/effects';
|
import { select, put } from 'redux-saga/effects';
|
||||||
import { fetchIssues, addLabel, removeLabel } from '../actions/issues';
|
import { fetchIssues, addLabel, removeLabel } from '../actions/issues';
|
||||||
import { fetchIssuesSaga } from './issues';
|
import { fetchIssuesSaga } from './issues';
|
||||||
import { BUILD_KANBOARD_SUCCESS } from '../actions/kanboards';
|
import { BUILD_KANBOARD_SUCCESS, buildKanboard } from '../actions/kanboards';
|
||||||
|
|
||||||
export function* moveCardSaga(action) {
|
export function* moveCardSaga(action) {
|
||||||
const {
|
const {
|
||||||
|
@ -30,7 +30,6 @@ export function* moveCardSaga(action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* buildKanboardSaga(action) {
|
export function* buildKanboardSaga(action) {
|
||||||
|
|
||||||
const { board } = action;
|
const { board } = action;
|
||||||
|
|
||||||
let kanboard;
|
let kanboard;
|
||||||
|
@ -50,7 +49,18 @@ export function* buildKanboardSaga(action) {
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put({ type: BUILD_KANBOARD_SUCCESS, kanboard });
|
yield put({ type: BUILD_KANBOARD_SUCCESS, kanboard });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* refreshKanboardSaga(action) {
|
||||||
|
const { project } = action;
|
||||||
|
const boards = yield select(state => state.boards);
|
||||||
|
const boardValues = Object.values(boards.byID);
|
||||||
|
|
||||||
|
for (let b, i = 0; (b = boardValues[i]); i++) {
|
||||||
|
const hasProject = b.projects.indexOf(project) !== -1;
|
||||||
|
if (!hasProject) continue;
|
||||||
|
yield put(buildKanboard(b));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCards(projects, issues, lane) {
|
function createCards(projects, issues, lane) {
|
||||||
|
|
|
@ -2,14 +2,14 @@ import { all, takeEvery, takeLatest } from 'redux-saga/effects';
|
||||||
import { failuresSaga } from './failure';
|
import { failuresSaga } from './failure';
|
||||||
import { FETCH_BOARDS_REQUEST, SAVE_BOARD_REQUEST } from '../actions/boards';
|
import { FETCH_BOARDS_REQUEST, SAVE_BOARD_REQUEST } from '../actions/boards';
|
||||||
import { fetchBoardsSaga, saveBoardSaga } from './boards';
|
import { fetchBoardsSaga, saveBoardSaga } from './boards';
|
||||||
import { FETCH_ISSUES_REQUEST, ADD_LABEL_REQUEST, REMOVE_LABEL_REQUEST } from '../actions/issues';
|
import { FETCH_ISSUES_REQUEST, ADD_LABEL_REQUEST, REMOVE_LABEL_REQUEST, CREATE_ISSUE_REQUEST, CREATE_ISSUE_SUCCESS } from '../actions/issues';
|
||||||
import { fetchIssuesSaga, addLabelSaga, removeLabelSaga } from './issues';
|
import { fetchIssuesSaga, addLabelSaga, removeLabelSaga, createIssueSaga } from './issues';
|
||||||
import { FETCH_PROJECTS_REQUEST } from '../actions/projects';
|
import { FETCH_PROJECTS_REQUEST } from '../actions/projects';
|
||||||
import { fetchProjectsSaga } from './projects';
|
import { fetchProjectsSaga } from './projects';
|
||||||
import { LOGOUT } from '../actions/logout';
|
import { LOGOUT } from '../actions/logout';
|
||||||
import { logoutSaga } from './logout';
|
import { logoutSaga } from './logout';
|
||||||
import { BUILD_KANBOARD_REQUEST, MOVE_CARD } from '../actions/kanboards';
|
import { BUILD_KANBOARD_REQUEST, MOVE_CARD } from '../actions/kanboards';
|
||||||
import { buildKanboardSaga, moveCardSaga } from './kanboards';
|
import { buildKanboardSaga, moveCardSaga, refreshKanboardSaga } from './kanboards';
|
||||||
|
|
||||||
export function* rootSaga() {
|
export function* rootSaga() {
|
||||||
yield all([
|
yield all([
|
||||||
|
@ -22,6 +22,8 @@ export function* rootSaga() {
|
||||||
takeEvery(MOVE_CARD, moveCardSaga),
|
takeEvery(MOVE_CARD, moveCardSaga),
|
||||||
takeEvery(ADD_LABEL_REQUEST, addLabelSaga),
|
takeEvery(ADD_LABEL_REQUEST, addLabelSaga),
|
||||||
takeEvery(REMOVE_LABEL_REQUEST, removeLabelSaga),
|
takeEvery(REMOVE_LABEL_REQUEST, removeLabelSaga),
|
||||||
|
takeLatest(CREATE_ISSUE_REQUEST, createIssueSaga),
|
||||||
|
takeLatest(CREATE_ISSUE_SUCCESS, refreshKanboardSaga),
|
||||||
takeLatest(LOGOUT, logoutSaga)
|
takeLatest(LOGOUT, logoutSaga)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,23 @@ export class GiteaClient {
|
||||||
.then(this.assertAuthorization)
|
.then(this.assertAuthorization)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createIssue(project, title, body, labelID) {
|
||||||
|
return fetch(`/gitea/api/v1/repos/${project}/issues`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
labels: [labelID],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(this.assertOk)
|
||||||
|
.then(this.assertAuthorization)
|
||||||
|
.then(res => res.json())
|
||||||
|
}
|
||||||
|
|
||||||
assertOk(res) {
|
assertOk(res) {
|
||||||
if (!res.ok) return Promise.reject(new Error('Request failed'));
|
if (!res.ok) return Promise.reject(new Error('Request failed'));
|
||||||
return res;
|
return res;
|
||||||
|
|
Loading…
Reference in New Issue