In the first article of the Develop web applications series, I've described the different options to get data from SAS Viya environment to hydrate your web application. Before we implement the different options, we need to build an application that will be the canvas for the different pages.
This article is the second one in the Develop web applications series:
To demonstrate the different options, I've created a simple web application using React JavaScript library. Writing this I already imagine you, thinking: why React? I prefer Angular. Can it be done using jQuery? Vue is far better than React ...
I prefer to stop here the fight between Angular, React, Vue, jQuery and the other JavaScript libraries and frameworks. I've chosen React because it is well documented, it is used by many large companies and more importantly, the code is easy to read and to understand which is I think the most important point as the objective is to learn about developing web applications that use SAS REST APIs as their data source.
With that being said, React is designed to create single page applications (SPA). This means that when the end-user connects to the application, the architecture of the page is loaded and then when the user navigates to the different pages, there is no need to query the back-end server except to get data. It makes a clear separation of concerns between the client application and the back-end server. Which is really nice because we will be using REST APIs to get data from the CAS server, the Compute server, the Viya Job and from MAS. If you need more information about React and how to use it, please refer to this tutorial page.
The application, in this example, has different pages. Each page represents a specific option and reuse the same components a form to select the data to be displayed and a table to display the data retrieved from the REST APIs. The form and the table will be covered in the remaining series. In this article, we will concentrate on building the structure of the application.
Select any image to see a larger version.
Mobile users: To view the images, select the "Full" version at the bottom of the page.
In addition to the four pages (CAS, Jobs, Compute and MAS), the application has two other pages: Logon and Home. The Home page is the first one that the user will see.
And the Logon page is used to authenticate the user.
Behind the scenes, the application will use Axios to call the SAS REST APIs. If you want more information about using React and Axios, please have a look at this tutorial page.
At this stage, you might wonder how this all fits together. Here are the steps to create the common architecture:
npx create-react-app viya_app
npm start
This will start the default application that has been generated by the create-react-app tool. It should also start a browser with the following content:
You can stop the application by running the following command:
npm stop
/* react-router-dom to handle routing */ npm install --save react-router-dom@5.2.0 /* react-bootstrap for the UI elements */
npm install -pm stop-save react-bootstrap@1.5.2 bootstrap@4.6.0
/* Axios for the REST APIs calls */
npm install --save axios@0.21.1 /* Font Awesome for specific icons */ npm install --save @fortawesome/fontawesome-svg-core@1.2.35 npm install --save @fortawesome/free-solid-svg-icons@5.15.3 npm install --save @fortawesome/react-fontawesome@0.1.14
/* On Windows */ set HTTPS=true&&npm start /* On Linux or MacOS */ HTTPS=true npm start
Now that we have everything installed, we need to make some changes to the application that was created by the create-react-app.
Even though you can develop your application using any text editor, I suggest you use an application that is targeted to developers. I will use Visual Studio Code because it is a nice and easy tool to write any kind of code and it has a lot functionalities that makes your life easier when developing web pages.
import 'bootstrap/dist/css/bootstrap.min.css'; import { library } from '@fortawesome/fontawesome-svg-core'; import { faHome } from '@fortawesome/free-solid-svg-icons'; library.add(faHome);
import axios from 'axios'; const Instance = axios.create({ baseURL: "https://live.lts.gel.race.sas.com", headers:{} }); export default Instance;
const PAGES = [ { label: 'CAS', href: "/cas", description: "This page demonstrates how to retrieve data from a CAS table by selecting a global CASLib and the required table."}, { label: 'Compute', href: "/compute", description: "This page demonstrates how to start a computer server session to execute SAS code that retrieves a selected CAS table." }, { label: 'Jobs', href: "/jobs", description: "This page demonstrates how to call a SAS Viya Job that retrieves a selected CAS table." }, { label: 'MAS', href: "/mas", description: "This page demonstrates how to call a decision that has been published to the MAS destination."} ] export default PAGES;
function Compute (){ return ( <h2>Compute page</h2> ); } export default Compute;
import React from 'react'; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Container from 'react-bootstrap/Container'; import HeaderBar from './components/HeaderBar'; import Home from './pages/Home'; import Logon from './pages/Logon'; import Cas from './pages/Cas'; import Compute from './pages/Compute'; import Jobs from './pages/Jobs'; import Mas from './pages/Mas'; function App() { return ( <Router> <Container> <HeaderBar/> <Switch> <Route path="/" exact component={Home}/> <Route path="/cas"exact component={Cas}/> <Route path="/jobs" exact component={Jobs}/> <Route path="/compute" exact component={Compute}/> <Route path="/mas" exact component={Mas}/> <Route path="/logon" exact component={Logon}/> </Switch> </Container> </Router> ); } export default App;
import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Navbar, Nav } from 'react-bootstrap'; import { NavLink } from 'react-router-dom'; import PAGES from '../data/PAGES'; function HeaderBar() { const navItems = PAGES.map((item, index) => <Nav.Link to={item.href} as={NavLink} style={{ color:'white', textDecoration:'none' }} key={index}> {item.label} </Nav.Link> ); return ( <Navbar bg="primary"> <Navbar.Collapse> <Nav.Link to='/' as={NavLink}> <FontAwesomeIcon icon="home" color="white" size="lg"/> </Nav.Link> {navItems} </Navbar.Collapse> </Navbar> ); } export default HeaderBar;
At this stage, our application can navigate between pages. It might be good to add some logic to it. The objective is now to display the description of the different pages on the Home page. As soon as we click on one of the page description, it should open the link but this can only be done for the authenticated users. So, we will update the content of the Home.js file to this:
import { Card, Row, Col } from 'react-bootstrap'; import { useHistory } from 'react-router-dom'; import PAGES from '../data/PAGES'; function Home() { const history = useHistory(); function handleClick(href) { history.push(href); } return ( <Row> {PAGES.map((page, index) => <Col key={index} xs={6} style={{ padding:10 }}> <Card onClick={() =>handleClick(page.href)}> <Card.Header style={{ padding:20 }}> {page.label} </Card.Header> <Card.Body> {page.description} </Card.Body> </Card> </Col> )} </Row> ) } export default Home;
As mentioned, the user needs to be authenticated to access the other pages. This means that we need first to get an authentication token from the SAS Viya Environment. This can be done by updating the Logon.js page like below:
import React, { useContext } from 'react'; import { useHistory } from 'react-router-dom'; import Instance from '../apis/Instance'; import { AuthContext } from '../contexts'; import Form from 'react-bootstrap/Form'; import Button from 'react-bootstrap/Button'; import Col from 'react-bootstrap/Col'; function Logon() { const { authInfo, setAuthInfo } = useContext(AuthContext); const history = useHistory(); let userInput = React.createRef(); let passwordInput = React.createRef(); const authenticate = (event) => { event.preventDefault(); const endpoint = "/SASLogon/oauth/token"; const user = userInput.current.value; const password = passwordInput.current.value; const data = { grant_type:"password", response_type:"bearer", username:user, password:password }; const headers = { 'Authorization':"Basic " + btoa('gel_app:gel_secret'), 'Content-Type':'application/x-www-form-urlencoded' }; Instance.post(endpoint, new URLSearchParams(data), { headers:headers }) .then(response => { if (response.status === 200) { Instance.defaults.headers.common['Authorization'] = `${response.data.token_type} ${response.data.access_token}`; } else { Instance.defaults.headers.common['Authorization'] = null; } setAuthInfo({ ...authInfo, authenticated:true, user:user, tokenInfo:response.data }); history.goBack(); return null; }); } return ( <Form onSubmit={authenticate}> <Form.Row> <Col className='col-6'> <Form.Control ref={userInput} type="input" placeholder="Login"/> </Col> <Col className="col-4"> <Form.Control ref={passwordInput} type="password" placeholder="Password"/> </Col> <Col> <Button variant="primary" type="submit"> Logon </Button> </Col> </Form.Row> </Form> ) } export default Logon;
As you see in the code, we are importing a context. In React, contexts are used to pass data from a parent component to all its descendants. As we will reuse the username and the token in nearly all the components, this is the right time to create an AuthContext to store the authentication information. Let's create a contexts.js under src. The file should contain the following code:
import { createContext } from 'react'; export const AuthContext = createContext({});
Now that we have defined the context, we need to assign some value to it and make sure it is passed to all the components. This can be done in App.js. You can add an import for the context at the top of the file:
import { AuthContext } from './contexts';
In the App function, we should create a new variable and a setter function for it with the following code:
const [authInfo, setAuthInfo] = useState({ authenticated:false, user:"", tokenInfo: {}, session: {}, csrf : {} });
And finally, wrap all the returned components with the context:
<AuthContext.Provider value={{ authInfo, setAuthInfo }}> ... </AuthContext.Provider>
The App.js should now look like:
After saving all the files, if you open https://localhost:3000/logon page, you should see a form. If you submit the form with a username and a password, it will authenticate against SAS Viya environment and store the token in the AuthContext object. In order to check that the user has been authenticated and that the AuthContext has been updated properly, we will display the username in the HeaderBar. To achieve this, add the the lines of code that are highlighted in the HeaderBar.js file.
As mentioned in the objective, a user should not be able to access the different pages if he is not authenticated. We will now force the application to route the user to the Logon page if the user is not yet authenticated. We will do it in the Cas.js file but it should be done in the different pages (Compute.js, Jobs.js, Mas.js). Here is the code:
Now that we have done all these changes, we can save them and test the application. You can execute "npm start" command.
The application works now as expected. We can just improve a bit the look and feel of the HeaderBar. In fact, we don't want the user to interact with the navigation bar unless he is authenticated. We will just add this condition to the Headerbar function.
Here is the new look of the Home page:
Using React, we have created a web application which displays different pages. The application also handles authentication against SAS Viya. The application is structured in such a way that we don't redundant code and that each component has a well defined role. From here, we can build the different pages to access CAS, MAS, Compute Server or Viya Jobs. And this is what we will see in the next article of this series. The code for this article can be downloaded here. This article is the second one in the Develop web applications series:
Find more articles from SAS Global Enablement and Learning here.
Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9.
Early bird rate extended! Save $200 when you sign up by March 31.
Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning and boost your career prospects.