Strapi blog logo
  • Product

      Why Strapi?Content ArchitectureRoadmapIntegrationsTry live demo
      OverviewContent Types BuilderMedia LibraryRoles & Permissions
      Discover Strapi Enterprise EditionDiscover our partners
  • Pricing

  • Solutions

      Static WebsitesMobile ApplicationsCorporate websitesEditorial SitesEcommerce
      Delivery HeroL'EquipeSociete GeneralePixelDustBanco International
      Discover all our user stories
  • Community

      CommunityWrite for the communityWall of LoveStrapi Conf 2021
      SlackGitHubYoutubeCommunity Forum
      Meet the Strapi Community StarsDiscover the Strapi Showcase
  • Resources

      BlogStartersNewsroomSupport
      Strapi AcademyTutorialsVideos GuidesWebinars
      The Guide to Headless CMS Strapi Community Forum
  • Docs

      Getting StartedContent APIConfigurationInstallationDeploymentMigration
      Getting StartedContent ManagerContent-Types BuilderUsers, Roles & PermissionsPlugins
      Developer DocumentationStrapi User Guide

Looking for our logo ?

Logo Brand download
Download Logo Pack
See more Strapi assets
Strapi blog logo
  • Product

    Product

    • Why Strapi?
    • Content Architecture
    • Roadmap
    • Integrations
    • Try live demo

    Features

    • Overview
    • Content Types Builder
    • Media Library
    • Roles & Permissions
    • Discover Strapi Enterprise Edition
    • Discover our partners
    Features cover

    Unlock the full potential of content management

    See all features
    Strapi Enterprise Edition

    Discover the advanced features included in Strapi Enterprise Edition.

    Get Started
  • Pricing
  • Solutions

    Solutions

    • Static Websites
    • Mobile Applications
    • Corporate websites
    • Editorial Sites
    • Ecommerce

    Stories

    • Delivery Hero
    • L'Equipe
    • Societe Generale
    • PixelDust
    • Banco International
    • Discover all our user stories
    Delivery Hero team

    Delivery Hero manages their partner portal with Strapi.

    Read their story
    turn 10 studios website

    How 1minus1 delivered a creative website for Turn10 Studios 25% faster with Strapi

    Discover their story
  • Community

    Community

    • Community
    • Write for the community
    • Wall of Love
    • Strapi Conf 2021

    Resources

    • Slack
    • GitHub
    • Youtube
    • Community Forum
    • Meet the Strapi Community Stars
    • Discover the Strapi Showcase
    Strapi Conf

    The first Strapi Global User Conference.

    Register now
    Write for the community

    Contribute on educational content for the community

    Discover the program
  • Resources

    Resources

    • Blog
    • Starters
    • Newsroom
    • Support

    Learning

    • Strapi Academy
    • Tutorials
    • Videos Guides
    • Webinars
    • The Guide to Headless CMS
    • Strapi Community Forum
    Introducing Strapi Academy

    Everything you need to know to master Strapi.

    Go to the academy
    Strapi Repository on GitHub

    Get started with the Strapi repository

    Go to repository
  • Docs

    Developers

    • Getting Started
    • Content API
    • Configuration
    • Installation
    • Deployment
    • Migration

    User Guide

    • Getting Started
    • Content Manager
    • Content-Types Builder
    • Users, Roles & Permissions
    • Plugins
    • Developer Documentation
    • Strapi User Guide
    Install Strapi

    Install Strapi locally or wherever you need.

    Get Started
    Migration Guides Strapi

    Using a previous version of Strapi? Migrate to the latest.

    Read Guides
Get Started
Back to articles

How to create an import content plugin (part 4/4)

how-to-create-an-import-content-plugin-part-4-4
  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email
Pouya Miralayi

Pouya Miralayi

This tutorial is part of the « How to create your own plugin »:

Table of contents

  • Upload From file (part 1)
  • Upload from url and raw input (part 2)
  • Services (part 3)
  • History Page (part 4) - current

You can reach the code for this tutorial at this link

HistoryPage

  • Go to containers directory and create a new directory named HistoryPage with an empty index.js file inside:

    admin/src/containers/HistoryPage/index.js

Before we dive into writing our HistoryPage, we need to introduce a route to this new page.

  • Go to App directory inside containers directory and add the following route to the index.js file:
...
<Route
 path={`/plugins/${pluginId}/history`}
 component={HistoryPage}
 exact
/>
...

Notice that NotFound is placed after our HistoryPage and don’t forget to import our container at the top of this file:

  • import HistoryPage from "../HistoryPage";

  • Go back to our HistoryPage directory and paste the below code inside index.js:

/* * * HistoryPage * */
import React, { Component } from "react";
import {
  HeaderNav,
  LoadingIndicator,
  PluginHeader,
  request
} from "strapi-helper-plugin";
import pluginId from "../../pluginId";
import Row from "../../components/Row";
import Block from "../../components/Block";

const getUrl = to =>
  to ? `/plugins/${pluginId}/${to}` : `/plugins/${pluginId}`;

class HistoryPage extends Component {}

export default HistoryPage;

We want this page to have a similar look with respect to our HomePage.

  • So we will render something like this:
render() {
    return (
      <div className={"container-fluid"} style={{ padding: "18px 30px" }}>
        <PluginHeader
          title={"Import Content"}
          description={"Import CSV and RSS-Feed into your Content Types"}
        />
        <HeaderNav
            links={[
              {
                name: "Import Data",
                to: getUrl("")
              },
              {
                name: "Import History",
                to: getUrl("history")
              }
            ]}
            style={{ marginTop: "4.4rem" }}
        />
        <div className="row">
          <Block
            title="General"
            description="Manage the Initiated Imports"
            style={{ marginBottom: 12 }}
          />
        </div>
      </div>
    );
}

What we need next, is a table that renders all the “import configs” available.

  • Go to components directory and create a new directory named HistoryTable with an empty index.js file inside:

    admin/src/components/HistoryTable/index.js

  • Open the index.js file and paste the below code:

import React, { Component } from "react";
import PropTypes from "prop-types";
import { Table, Button } from "@buffetjs/core";
import moment from "moment";
import { LoadingIndicator, PopUpWarning } from "strapi-helper-plugin";
class HistoryTable extends Component {
  state = {
    showDeleteModal: false,
    showUndoModal: false,
    importToDelete: null,
    importToUndo: null
  };
}
HistoryTable.propTypes = {
  configs: PropTypes.array.isRequired,
  deleteImport: PropTypes.func,
  undoImport: PropTypes.func
};
export default HistoryTable;

In our state, we are defining a couple of variables that are responsible for showing the delete & undo dialogs and in case the user confirms the delete or undo dialogs, we must know which “import config” we are supposed to remove or undo.

  • So the following functions will do the job. Add them just after your state:
...
  deleteImport = id => {
    this.setState({ showDeleteModal: true, importToDelete: id });
  };
  undoImport = id => {
    this.setState({ showUndoModal: true, importToUndo: id });
  };
...

It’s time to use the above functions. For our table we are about to use “Buffet js” table with “custom row” just like what we did for MappingTable.

  • Append the below code as our “custom row” to the class definition just after your state:
  CustomRow = ({ row }) => {
    const { id, contentType, importedCount, ongoing, updated_at } = row;
    const updatedAt = moment(updated_at);
    let source;
    switch (row.source) {
      case "upload":
        source = row.options.filename;
        break;
      case "url":
        source = row.options.url;
        break;
      default:
        source = "unknown";
    }
    return (
      <tr style={{ paddingTop: 18 }}>
        <td>{source}</td> <td>{contentType}</td>
        <td>{updatedAt.format("LLL")}</td> <td>{importedCount}</td>
        <td>{ongoing ? <LoadingIndicator /> : <span>Ready</span>} </td>
        <td>
          <div className={"row"}>
            <div
              style={{
                marginRight: 18,
                marginLeft: 18
              }}
              onClick={() => this.undoImport(id)}
            >
              <i className={"fa fa-undo"} role={"button"} />
            </div>
            <div onClick={() => this.deleteImport(id)}>
              <i className={"fa fa-trash"} role={"button"} />
            </div>
          </div>
        </td>
      </tr>
    );
  };

We are rendering the details of each “import config” as a row in our table. Also we are showing some buttons for delete & undo actions as well.

  • Finally, our render method:
render() {
    const { configs } = this.props;
    const props = {
      title: "Import History",
      subtitle: "Manage the Initiated Imports"
    };
    const headers = [
      { name: "Source", value: "source" },
      { name: "Content Type", value: "contentType" },
      { name: "Updated At", value: "updatedAt" },
      { name: "Items", value: "items" },
      { name: "Progress State", value: "progress" },
      { name: "Actions", value: "actions" }
    ];
    const items = [...configs];
    const {
      importToDelete,
      importToUndo,
      showDeleteModal,
      showUndoModal
    } = this.state;
    return (
      <div className={"col-md-12"} style={{ paddingTop: 12 }}>
        <PopUpWarning
          isOpen={showDeleteModal}
          toggleModal={() => this.setState({ showDeleteModal: null })}
          content={{
            title: `Please confirm`,
            message: `Are you sure you want to delete this entry?`
          }}
          popUpWarningType="danger"
          onConfirm={async () => {
            importToDelete && (await this.props.deleteImport(importToDelete));
          }}
        />
        <PopUpWarning
          isOpen={showUndoModal}
          toggleModal={() => this.setState({ showUndoModal: null })}
          content={{
            title: `Please confirm`,
            message: `Are you sure you want to undo this entry?`
          }}
          popUpWarningType="danger"
          onConfirm={async () => {
            importToUndo && (await this.props.undoImport(importToUndo));
          }}
        />
        <Table
          {...props}
          headers={headers}
          rows={items}
          customRow={this.CustomRow}
        />
      </div>
    );
}

Notice how “Strapi Helper Plugin” PopUpWarning made it easy for us to show a beautiful confirm dialog for the delete & undo actions. Our table is ready!

  • Go back to our HistoryPage and add the following import at the top:

    import HistoryTable from "../../components/HistoryTable";

  • then, define the state as below:
...
state = {
 loading: false,
 importConfigs: []
};
...

As you know we must pass the list of “import configs” down to our HistoryTable.

  • For fetching the list of “import configs” we use the following method. Add it just after your state:
...
getConfigs = async () => {
    try {
      const response = await request("/import-content", { method: "GET" });
      return response;
    } catch (e) {
      strapi.notification.error(`${e}`);
      return [];
    }
};
...
  • to actually call this method we use the following method. Add it just after your state:
...
importConfigs() {
    if (!this.state.loading) {
      this.getConfigs().then(res => {
        this.setState({ importConfigs: res });
      });
    }
}
...

The reason behind this method is that we are going to periodically fetch the list of “import configs” for having an up to date list. When the user goes to our HistoryPage for the first time, we will fetch the list directly by calling getConfigs method and after that we are going to periodically call the importConfigs method for the rest of our lives!

  • To understand this better, see the following functions and add them to your file one at the time:
...
componentDidMount() {
    this.getConfigs().then(res => {
      this.setState({ importConfigs: res, loading: false });
    });
    setTimeout(() => {
      this.fetchInterval = setInterval(() => this.importConfigs(), 4000);
    }, 200);
}
...
  • We will clean our setInterval function call by the below life cycle hook which is called before unmounting the component:
...
componentWillUnmount() {
    if (this.fetchInterval) {
      clearInterval(this.fetchInterval);
    }
}
...

beside the importConfigs, we will pass 2 methods for delete & undo operations down to our HistoryTable.

  • For the delete operation:
...
deleteImport = async id => {
      this.setState({ loading: true }, async () => {
      try {
        await request(`/import-content/${id}`, { method: "DELETE" });

        this.setState(prevState => ({
          ...prevState,
          importConfigs: prevState.importConfigs.filter(imp => imp.id !== id),
          loading: false
        }));

        strapi.notification.success(`Deleted`);
      } catch (e) {
        this.setState({ loading: false }, () => {
          strapi.notification.error(`${e}`);
          strapi.notification.error(`Delete Failed`);
        });
      }
    });
};
...
  • For the undo operation:
...
undoImport = async id => {
    this.setState({ loading: true }, async () => {
      await request(`/import-content/${id}/undo`, { method: "POST" });
      this.setState({ loading: false }, () => {
        strapi.notification.info(`Undo Started`);
      });
    });
};
...
  • Change the render method output as below:
 		 <Block
            title="General"
            description="Manage the Initiated Imports"
            style={{ marginBottom: 12 }}
          >
            {this.state.loading && <LoadingIndicator />}
            {!this.state.loading && this.state.importConfigs && (
              <Row className={"row"}>
                <HistoryTable
                  undoImport={this.undoImport}
                  deleteImport={this.deleteImport}
                  configs={this.state.importConfigs}
                />
              </Row>
            )}
         </Block>

Before visiting the plugin page, make sure that you have allowed public access on all the endpoints of our plugin:

Congratulations! If you have made it this far, you are ready to do some awesome things with Strapi plugins.

In this tutorial we have covered the basics as much as we could, what has been left is to integrate Redux into our plugin, writing tests for our plugin and some other tiny details. So keep an eye on our blog for the upcoming tutorials. Thanks to all of you guys! and special thanks to:

Soupette for his kind edits, testing's and suggestions on the react side to make the code as robust as possible!

Maxime Castres for his kind edits, testing's, suggestions and rewriting the whole tutorial from scratch in the Markup format. This tutorial would not make it without him!

Joe Beuckman who wrote this amazing plugin at the first place. In fact I learned a lot from his plugin myself and appreciate the effort he put into this plugin so far!

Hail Strapi!

One last thing, we are trying to make the best possible tutorials for you, help us in this mission by answering this short survey https://strapisolutions.typeform.com/to/bwXvhA?channel=xxxxx

  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email

Unleash content.

Read the docs
Get Started

Strapi is the most popular open-source Headless CMS. Strapi gives developers the freedom to use their favorite tools and frameworks while allowing editors to easily manage their content and distribute it anywhere.

Product

  • Why Strapi?
  • Content Architecture
  • Features
  • Enterprise Edition
  • Partner Program
  • Roadmap
  • Support
  • Integrations
  • Try live demo

Resources

  • How to get started
  • Meet the community
  • Tutorials
  • API documentation
  • GitHub repository
  • Starters
  • Strapi vs Wordpress
  • The Guide to headless CMS

Integrations

  • Gatsby CMS
  • React CMS
  • Vue.js CMS
  • Nuxt.js CMS
  • Next.js CMS
  • Angular CMS
  • Gridsome CMS
  • Jekyll CMS
  • 11ty CMS
  • Svelte CMS
  • Sapper CMS
  • Ruby CMS
  • Python CMS

Company

  • About us
  • Blog
  • Careers
  • Contact
  • Newsroom
  • © 2021, Strapi
  • LicenseTermsPrivacy