Skip to content

A Simple React Example

Building a React App

We will build a simple React app on top of TACTIC Project. The app we create will be a simple issue tracker.

If you need help setting up a React project, please refer to the React documention

Job list

First we start with the simple boilerplate React app. The first file is index.js with the following code:

import React from 'react';
import ReactDOM from 'react-dom';

class Jobs extends React.Component {
  render() {
    return (
      <div className="job">
        <div className="job-list">
          <div>Job Code: JOB00XYZ</div>
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <Jobs />,
  document.getElementById('root')
);

Next we will add a state variable which will contain the list of all of the jobs.

class Jobs extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      jobs: []
    }
  }

  render() {
    return (
      <div className="job">
        <div className="job-list">
          {
            this.state.jobs.map( (job, index) => (
              <div key="{job.code}">
                Job Code: {job.code} <br/>
                Name: {job.name} <br/>
                Status: {job.status}
                <hr/>
              </div>
            ))
          }
        </div>
      </div>
    );
  }
}

// ========================================

ReactDOM.render(
  <Jobs />,
  document.getElementById('root')
);

Since the jobs item in the state variable is empty, nothing will be listed. In order to get the jobs data, we will need to access the TACTIC server and get a ticket. For more information about connecting to the TACTIC server, please refer to the connection documentation.

We are going to create a new Server.js file with the following content:

let server_url = "https://portal.southpawtech.com"
let site = "trial"
let project = "api_test"
let user = "trial_api"
let password = "tactic123"

let base_endpoint = server_url+ "/" + site + "/" + project + "/REST";

const get_ticket = async () => {

    let url = base_endpoint + "/" + "get_ticket";
    let headers = {
        Accept: 'application/json',
    }
    let data = {
        'login': user,
        'password': password,
    };
    let r = await fetch( url, {
        method: 'POST',
        body: JSON.stringify(data),
    } )
    let ticket = await r.json()
    return ticket;

}

const get_endpoint = () => {
    return base_endpoint;
}

const call_tactic = async (method, kwargs) => {

    let data = kwargs

    let url = get_endpoint() + "/" + method

    let headers = {
        "Authorization": "Bearer " + ticket,
        Accept: 'application/json',
    }
    let r = await fetch( url, {
        method: 'POST',
        mode: 'cors',
        headers: headers,
        body: JSON.stringify(data),
    } )

    if (r.status == 200) {
        let ret = await r.json();
        return ret;
    }
    else {
        throw("ERROR: " + r.statusText);
    }

}

export { get_endpoint, get_ticket, call_tactic };

We will create Jobs.js to use the connection and query a list of jobs. First, we define a load method which will retrive all of the jobs from the TACTIC server. Here, we will use the TACTIC expression language to easily retrieve this data. This expression will return a list of JSON objects which can be used for drawing a list of job codes.

import React from 'react';

import {
  Link,
} from 'react-router-dom';

import { get_ticket, call_tactic } from "./Server";

class Jobs extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      jobs: []
    }
  }

  load = async () => {
    this.getJobs()
  }

  getJobs = async () => {
    let ticket = await get_ticket()

    //query for a list of jobs
    let search_type = "workflow/job"
    let kwargs = {
        search_type: search_type,
    };
    let sobjects = await call_tactic("query", kwargs);
    this.setState({jobs: sobjects})
  }

  componentDidMount() {
    this.load()
  }

  render() {
    return (
      <div className="job">
        <div className="job-list">
          {
            this.state.jobs.map( (job, index) => (
              <div key="{job.code}">
                Job Code: {job.code} <br/>
                Name: {job.name} <br/>
                Status: {job.status}
                <hr/>
              </div>
            ))
          }
        </div>
      </div>
    );
  }
}

export default Jobs;

We will modify our index.js by first importing Jobs class: import Jobs from './Jobs'; We will also create a new class App with routing capabilities. Please see below.

import React from 'react';
import ReactDOM from 'react-dom';

import {
  BrowserRouter as Router,
  Switch,
  Route,
  Redirect,
  Link
} from 'react-router-dom';

import Jobs from './Jobs';

import './index.css';

class App extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <Link to="/jobs">All Jobs</Link>
          <hr/>
        </div>
        <Switch>
          <Route exact path="/jobs" component={Jobs} />
          <Redirect from="/" to="/jobs" exact/>
        </Switch>
      </Router>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Job detail page

Now that we have a simple job list page running. We will create a job detail page. Let's create a new file called JobDetails.js with the following codes.

import React from 'react';

import { get_ticket, call_tactic } from "./Server";

class JobDetails extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      job: []
    }
  }

  load = async () => {
    //get a job info
    this.getJob()

  }

  getJob = async () => {
    let job_code = this.props.match.params.job_code

    let ticket = await get_ticket()

    //query for the job
    let search_type = "workflow/job"
    let kwargs = {
        search_type: search_type,
        filters: [['code', job_code]],
    };

    let sobjects = await call_tactic("query", kwargs)
    this.setState({job: sobjects})
  }


  componentDidMount() {
    this.load()
  }

  render() {
    return (
      <div className="job">
        <div className="job-list">
          {
            this.state.job.map( (job, index) => (
              <div key="{job.code}">
                Job Code: {job.code} <br/>
                Name: {job.name} <br/>
                Status: {job.status}
                <hr/>
              </div>
            ))
          }
        </div>
      </div>
    );
  }
}

export default JobDetails;

We will import this into index.js:

import React from 'react';
import ReactDOM from 'react-dom';

import {
  BrowserRouter as Router,
  Switch,
  Route,
  Redirect,
  Link
} from 'react-router-dom';

import JobDetails from './JobDetails';
import Jobs from './Jobs';

import './index.css';

class App extends React.Component {
  render() {
    return (
      <Router>
        <div>
          <Link to="/jobs">All Jobs</Link>
          <hr/>
        </div>
        <Switch>
          <Route exact path="/jobs" component={Jobs} />
          <Route exact path="/job_details/:job_code" component={JobDetails} />
          <Redirect from="/" to="/jobs" exact/>
        </Switch>
      </Router>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Go back to Jobs.js and add a link to the job detail page through the job code in render():

<Link to={"/job_details/" + job.code }>{job.code}</Link>

render() from Jobs.js will look like this now:

render() {
    return (
      <div className="job">
        <div className="job-list">
          {
            this.state.jobs.map( (job, index) => (
              <div key="{job.code}">
                Job Code: <Link to={"/job_details/" + job.code }>{job.code}</Link>  <br/>
                Name: {job.name} <br/>
                Status: {job.status}
                <hr/>
              </div>
            ))
          }
        </div>
      </div>
    );
  }

Task list

We are going to retrieve tasks associated with a particular job. Let's define a function called getTasks in JobDetails.js:

  getTasks = async () => {

    let job_code = this.props.match.params.job_code

    let ticket = await get_ticket()

    // get tasks
    let search_type = "sthpw/task"
    let kwargs = {
        search_type: search_type,
        filters: [['search_code', job_code]],
    };

    let sobjects = await call_tactic("query", kwargs)
    this.setState({tasks: sobjects})

  }

We will call this from load().

  load = async () => {
    //get a job info
    this.getJob()

    //get tasks
    this.getTasks()
  }

We will modify render() function with the following:

  render() {
    return (
      <div className="job">
        <div className="job-list">
          {
            this.state.job.map( (job, index) => (
              <div key="{job.code}">
                Job Code: {job.code} <br/>
                Name: {job.name} <br/>
                Status: {job.status}

                <h1>Tasks</h1>
                {
                  this.state.tasks.map((tasks,task_index) => (
                    <div class="item">
                      <h2>Process: {tasks.process}</h2> <br/>
                      <h3>Assigned: {tasks.assigned}</h3>
                      <p>Timestamp: {tasks.timestamp}</p>
                    </div>

                  ))
                }
              </div>
            ))
          }
        </div>
      </div>
    );
  }

Congratulations, we have just built a simple app with job list and job detail pages.