Skip to content

Commit

Permalink
Hard integrate UI stories (#9220)
Browse files Browse the repository at this point in the history
* Add ui debug page controller

* Add build time tooling to bundle stories

* Add ui debug page frontend

* Add missing types

* Migrate all stories, refactor directory structure

* Fix lint

* Add changes
  • Loading branch information
Etheryte committed Sep 5, 2024
1 parent 13eb38f commit cea1cf8
Show file tree
Hide file tree
Showing 220 changed files with 2,263 additions and 1,306 deletions.
4 changes: 4 additions & 0 deletions java/code/src/com/suse/manager/webui/Router.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import com.suse.manager.webui.controllers.SetController;
import com.suse.manager.webui.controllers.SsmController;
import com.suse.manager.webui.controllers.StatesAPI;
import com.suse.manager.webui.controllers.StorybookController;
import com.suse.manager.webui.controllers.SubscriptionMatchingController;
import com.suse.manager.webui.controllers.SystemsController;
import com.suse.manager.webui.controllers.TaskoTop;
Expand Down Expand Up @@ -249,6 +250,9 @@ public void init() {
// Saltboot
SaltbootController.initRoutes();

// Storybook
StorybookController.initRoutes(jade);

// if the calls above opened Hibernate session, close it now
HibernateFactory.closeSession();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 SUSE LLC
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/

package com.suse.manager.webui.controllers;

import static com.suse.manager.webui.utils.SparkApplicationHelper.withCsrfToken;
import static com.suse.manager.webui.utils.SparkApplicationHelper.withUser;
import static com.suse.manager.webui.utils.SparkApplicationHelper.withUserPreferences;
import static spark.Spark.get;

import com.redhat.rhn.domain.user.User;

import java.util.HashMap;

import spark.ModelAndView;
import spark.Request;
import spark.Response;
import spark.template.jade.JadeTemplateEngine;

public class StorybookController {
private StorybookController() { }

/**
* Initialize routes
*
* @param jade the Jade engine to use to render the pages
*/
public static void initRoutes(JadeTemplateEngine jade) {
get("/manager/storybook",
withUserPreferences(withCsrfToken(withUser(StorybookController::view))), jade);
}

/**
* Returns the ui debug page
*
* @param req the request object
* @param res the response object
* @param user the authorized user
* @return the model and view
*/
public static ModelAndView view(Request req, Response res, User user) {
return new ModelAndView(new HashMap<String, Object>(), "templates/storybook/storybook.jade");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
include ../common.jade

#storybook

script(type='text/javascript').
window.csrfToken = "#{csrf_token}";

script(type='text/javascript').
spaImportReactPage('storybook')
.then(function(module) { module.renderer('storybook') });
3 changes: 2 additions & 1 deletion java/code/src/com/suse/manager/webui/utils/ViewHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ public enum ViewHelper {
"/rhn/errata/manage/CloneErrata.do",
"/rhn/admin/setup/ProxySettings.do",
"/rhn/admin/setup/MirrorCredentials.do",
"/rhn/manager/admin/setup/payg"
"/rhn/manager/admin/setup/payg",
"/rhn/manager/storybook"
);

ViewHelper() { }
Expand Down
1 change: 1 addition & 0 deletions java/spacewalk-java.changes.eth.ui-debug
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Integrate ui debugging stories
2 changes: 2 additions & 0 deletions web/html/src/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ module.exports = {
},
],
"sort-imports": "off",
// We use a `DEPRECATED_` prefix for old components that doesn't conform with this rule
"react/jsx-pascal-case": "off",
...(process.env.NODE_ENV === "production" ? productionRules : {}),
},

Expand Down
96 changes: 96 additions & 0 deletions web/html/src/build/plugins/generate-stories-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const fs = require("fs").promises;
const path = require("path");

/** Automatically gather all imports for story files */
module.exports = class GenerateStoriesPlugin {
didApply = false;
outputFile = undefined;

constructor({ outputFile }) {
if (!outputFile) {
throw new Error("GenerateStoriesPlugin: `outputFile` is not set");
}
this.outputFile = outputFile;
}

/**
* @param {import("webpack").Compiler} compiler
*/
apply(compiler) {
// See https://webpack.js.org/api/compiler-hooks/#hooks
compiler.hooks.watchRun.tapAsync("GenerateStoriesPlugin", async (params, callback) =>
this.beforeOrWatchRun(params, callback)
);
compiler.hooks.beforeRun.tapAsync("GenerateStoriesPlugin", async (params, callback) =>
this.beforeOrWatchRun(params, callback)
);
}

/**
*
* @param {import("webpack").Compiler} compiler
* @param {Function} callback
*/
async beforeOrWatchRun(compiler, callback) {
if (this.didApply) {
callback();
return;
}
this.didApply = true;

/** Source directory for the compilation, an absolute path to `/web/html/src` */
const webHtmlSrc = compiler.context;
if (!this.outputFile.startsWith(webHtmlSrc)) {
throw new RangeError("GenerateStoriesPlugin: `outputFile` is outside of the source code directory");
}

const files = await fs.readdir(webHtmlSrc, { recursive: true });
const storyFilePaths = files
.filter(
(item) => !item.startsWith("node_modules") && (item.endsWith(".stories.ts") || item.endsWith(".stories.tsx"))
)
.sort();

const stories = storyFilePaths.map((filePath) => {
const safeName = this.wordify(filePath);
// We use the parent directory name as the group name
const groupName = path.dirname(filePath).split("/").pop() ?? "Unknown";
return storyTemplate(filePath, safeName, groupName);
});

const output = fileTemplate(stories.join(""));
await fs.writeFile(this.outputFile, output, "utf-8");
console.log(`GenerateStoriesPlugin: wrote ${storyFilePaths.length} stories to ${this.outputFile}`);
callback();
}

wordify(input) {
return input.replace(/[\W_]+/g, "_");
}
};

const storyTemplate = (filePath, safeName, groupName) =>
`
import ${safeName}_component from "${filePath}";
import ${safeName}_raw from "${filePath}?raw";
export const ${safeName} = {
path: "${filePath}",
title: "${path.basename(filePath)}",
groupName: "${groupName}",
component: ${safeName}_component,
raw: ${safeName}_raw,
};
`;

const fileTemplate = (content) =>
`
/**
* NB! This is a generated file!
* Any changes you make here will be lost.
* See: web/html/src/build/plugins/generate-stories-plugin.js
*/
/* eslint-disable */
${content}
`.trim();
31 changes: 26 additions & 5 deletions web/html/src/build/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const autoprefixer = require("autoprefixer");

const GenerateStoriesPlugin = require("./plugins/generate-stories-plugin");

const DEVSERVER_WEBSOCKET_PATHNAME = "/ws";

module.exports = (env, argv) => {
Expand Down Expand Up @@ -82,6 +84,10 @@ module.exports = (env, argv) => {
new MiniCssExtractPlugin({
chunkFilename: "css/[name].css",
}),
new GenerateStoriesPlugin({
inputDir: path.resolve(__dirname, "../manager"),
outputFile: path.resolve(__dirname, "../manager/storybook/stories.generated.ts"),
}),
];

if (isProductionMode) {
Expand Down Expand Up @@ -118,15 +124,30 @@ module.exports = (env, argv) => {
publicPath: "/",
hashFunction: "md5",
},
// context: __dirname,
node: {
__filename: true,
__dirname: true,
},
devtool: isProductionMode ? "source-map" : "eval-source-map",
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
oneOf: [
{
resourceQuery: /raw/,
type: "asset/source",
},
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
},
],
},
],
},
{
// Stylesheets that are imported directly by components
Expand Down
2 changes: 1 addition & 1 deletion web/html/src/components/CoCoScansList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";

import { Messages, MessageType, Utils as MessagesUtils } from "components/messages";
import { Messages, MessageType, Utils as MessagesUtils } from "components/messages/messages";
import { TopPanel } from "components/panels/TopPanel";

import { LocalizedMoment, localizedMoment } from "utils/datetime";
Expand Down
2 changes: 1 addition & 1 deletion web/html/src/components/FormulaForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";

import { Button } from "components/buttons";
import { Messages, MessageType } from "components/messages";
import { Messages, MessageType } from "components/messages/messages";
import { BootstrapPanel } from "components/panels/BootstrapPanel";
import { SectionToolbar } from "components/section-toolbar/section-toolbar";

Expand Down
2 changes: 1 addition & 1 deletion web/html/src/components/action-schedule.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";

import { DateTimePicker } from "components/datetime";
import { Loading } from "components/utils/Loading";
import { Loading } from "components/utils/loading/Loading";

import { localizedMoment } from "utils";
import { DEPRECATED_unsafeEquals } from "utils/legacy";
Expand Down
6 changes: 0 additions & 6 deletions web/html/src/components/action/ActionStatus.stories.md

This file was deleted.

12 changes: 12 additions & 0 deletions web/html/src/components/action/ActionStatus.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ActionStatus } from "./ActionStatus";

export default () => {
return (
<>
<ActionStatus serverId="server123" actionId="456" status="Queued" />,
<ActionStatus serverId="server123" actionId="456" status="Picked Up" />,
<ActionStatus serverId="server123" actionId="456" status="Failed" />,
<ActionStatus serverId="server123" actionId="456" status="Completed" />,
</>
);
};
56 changes: 56 additions & 0 deletions web/html/src/components/buttons.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { StoryRow, StripedStorySection } from "manager/storybook/layout";

import { Button } from "components/buttons";

export default () => {
return (
<StripedStorySection>
<StoryRow>
<Button>&lt;Button /&gt;</Button>
<Button disabled>&lt;Button disabled /&gt;</Button>
</StoryRow>
<StoryRow>
<Button className="btn-link">&lt;Button className="btn-link" /&gt;</Button>
<Button className="btn-link" disabled>
&lt;Button className="btn-link" disabled /&gt;
</Button>
</StoryRow>
<StoryRow>
<Button className="btn-default">&lt;Button className="btn-default" /&gt;</Button>
<Button className="btn-default" disabled>
&lt;Button className="btn-default" disabled /&gt;
</Button>
</StoryRow>
<StoryRow>
<Button className="btn-primary">&lt;Button className="btn-primary" /&gt;</Button>
<Button className="btn-primary" disabled>
&lt;Button className="btn-primary" disabled /&gt;
</Button>
</StoryRow>
<StoryRow>
<Button className="btn-success">&lt;Button className="btn-success" /&gt;</Button>
<Button className="btn-success" disabled>
&lt;Button className="btn-success" disabled /&gt;
</Button>
</StoryRow>
<StoryRow>
<Button className="btn-info">&lt;Button className="btn-info" /&gt;</Button>
<Button className="btn-info" disabled>
&lt;Button className="btn-info" disabled /&gt;
</Button>
</StoryRow>
<StoryRow>
<Button className="btn-warning">&lt;Button className="btn-warning" /&gt;</Button>
<Button className="btn-warning" disabled>
&lt;Button className="btn-warning" disabled /&gt;
</Button>
</StoryRow>
<StoryRow>
<Button className="btn-danger">&lt;Button className="btn-danger" /&gt;</Button>
<Button className="btn-danger" disabled>
&lt;Button className="btn-danger" disabled /&gt;
</Button>
</StoryRow>
</StripedStorySection>
);
};
Loading

0 comments on commit cea1cf8

Please sign in to comment.