Skip to content

Commit

Permalink
DevUI: Liquibase
Browse files Browse the repository at this point in the history
  • Loading branch information
melloware committed Mar 23, 2023
1 parent 5c601df commit 378dbb6
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 2 deletions.
4 changes: 4 additions & 0 deletions extensions/liquibase/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-liquibase</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-dev-ui-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem;
import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem;
import io.quarkus.liquibase.runtime.devconsole.LiquibaseDevConsoleRecorder;
import io.quarkus.liquibase.runtime.devconsole.LiquibaseFactoriesSupplier;
import io.quarkus.liquibase.runtime.devui.LiquibaseFactoriesSupplier;

public class LiquibaseDevConsoleProcessor {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.quarkus.liquibase.deployment.devui;

import java.io.InputStream;
import java.net.URL;
import java.util.jar.Manifest;

import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.devui.spi.JsonRPCProvidersBuildItem;
import io.quarkus.devui.spi.page.CardPageBuildItem;
import io.quarkus.devui.spi.page.Page;
import io.quarkus.liquibase.runtime.devui.LiquibaseJsonRpcService;
import liquibase.changelog.DatabaseChangeLog;

/**
* Dev UI card for displaying important details such as the library version.
*/
public class LiquibaseDevUIProcessor {

private static final String EXTENSION_NAME = "Liquibase";

@BuildStep(onlyIf = IsDevelopment.class)
void createCard(BuildProducer<CardPageBuildItem> cardPageBuildItemBuildProducer) {
final CardPageBuildItem card = new CardPageBuildItem(EXTENSION_NAME);

// card
card.setCustomCard("qwc-liquibase-card.js");

// pages
card.addPage(Page.externalPageBuilder("Version")
.icon("font-awesome-solid:book")
.url("https://www.liquibase.org/")
.doNotEmbed()
.staticLabel(getManifest(DatabaseChangeLog.class).getMainAttributes().getValue("Bundle-Version")));

card.addPage(Page.webComponentPageBuilder().title("Datasources")
.componentLink("qwc-liquibase-datasources.js")
.icon("font-awesome-solid:database")
.dynamicLabelJsonRPCMethodName("getDatasourceCount"));

cardPageBuildItemBuildProducer.produce(card);
}

@BuildStep(onlyIf = IsDevelopment.class)
JsonRPCProvidersBuildItem registerJsonRpcBackend() {
return new JsonRPCProvidersBuildItem(EXTENSION_NAME, LiquibaseJsonRpcService.class);
}

private static Manifest getManifest(Class<?> clz) {
String resource = "/" + clz.getName().replace(".", "/") + ".class";
String fullPath = clz.getResource(resource).toString();
String archivePath = fullPath.substring(0, fullPath.length() - resource.length());

try (InputStream input = new URL(archivePath + "/META-INF/MANIFEST.MF").openStream()) {
return new Manifest(input);
} catch (Exception e) {
throw new RuntimeException("Loading MANIFEST for class " + clz + " failed!", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { LitElement, html, css} from 'lit';
import { pages } from 'liquibase-data';
import 'qwc/qwc-extension-link.js';

const NAME = "Liquibase";
export class QwcLiquibaseCard extends LitElement {

static styles = css`
.identity {
display: flex;
justify-content: flex-start;
}
.description {
padding-bottom: 10px;
}
.logo {
padding-bottom: 10px;
margin-right: 5px;
}
.card-content {
color: var(--lumo-contrast-90pct);
display: flex;
flex-direction: column;
justify-content: flex-start;
padding: 2px 2px;
height: 100%;
}
.card-content slot {
display: flex;
flex-flow: column wrap;
padding-top: 5px;
}
`;

static properties = {
description: {type: String}
};

constructor() {
super();
}

connectedCallback() {
super.connectedCallback();
}

render() {
return html`<div class="card-content" slot="content">
<div class="identity">
<div class="logo">
<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjUwMCAyMDAgNTAwIDEwMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTIwMCA2MDA7IiB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCgk8c3R5bGUgdHlwZT0idGV4dC9jc3MiPiAuc3Qwe2ZpbGw6IzI5NjJGRjt9IDwvc3R5bGU+DQoJCTxnPg0KCQkJPGVsbGlwc2UgY2xhc3M9InN0MCIgY3g9Ijc1MC4zIiBjeT0iNzIuNiIgcng9IjE4MC45IiByeT0iNzIuNiIvPg0KCQkJPHBhdGggY2xhc3M9InN0MCIgZD0iTTc1NC40LDM5MS4zQzY5Ni42LDQwNS4xLDYzNyw0MTkuNCw1OTksNDQ0LjZjLTE5LTExLjgtMjkuNy0yNS40LTI5LjctMzguNGMwLTU3LjQsOTUuOS03OS4xLDE4OC42LTEwMC4xIGM2Ni44LTE1LjEsMTM1LTMwLjYsMTczLjItNjEuN3Y1MC42QzkzMS4yLDM0OC45LDg0MS4zLDM3MC41LDc1NC40LDM5MS4zeiIvPg0KCQkJPHBhdGggY2xhc3M9InN0MCIgZD0iTTc1MS4yLDI3Ni4xYy03MC40LDE1LjktMTQyLjMsMzIuMi0xODEuOSw2Ni4ydi0xMS4yYzAtMTAwLDEwOS43LTEyNS42LDIxNS44LTE1MC41IGM3Ny4xLTE4LDExNC42LTI3LjUsMTQ2LjEtNTIuOHY1NkM5MzEuMiwyMzUuNCw4MzkuNywyNTYuMSw3NTEuMiwyNzYuMXoiLz4NCgkJCTxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik05MzEuMiwzNTcuOGMtMzcuNiwzMS43LTEwNC4zLDQ3LjgtMTY5LjYsNjMuNGMtNDYuOCwxMS4yLTk0LjcsMjIuNy0xMjguNiwzOS40IGMzMC43LDExLDcwLjksMTguMiwxMTcuMywxOC4yYzEwNS41LDAsMTgwLjktMzcuOCwxODAuOS03Mi42VjM1Ny44eiIvPg0KCQk8L2c+DQo8L3N2Zz4g"
alt="${NAME}"
title="${NAME}"
width="32"
height="32">
</div>
<div class="description">${this.description}</div>
</div>
${this._renderCardLinks()}
</div>
`;
}

_renderCardLinks(){
return html`${pages.map(page => html`
<qwc-extension-link slot="link"
extensionName="${NAME}"
iconName="${page.icon}"
displayName="${page.title}"
staticLabel="${page.staticLabel}"
dynamicLabel="${page.dynamicLabel}"
streamingLabel="${page.streamingLabel}"
path="${page.id}"
webcomponent="${page.componentLink}" >
</qwc-extension-link>
`)}`;
}

}
customElements.define('qwc-liquibase-card', QwcLiquibaseCard);
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { LitElement, html, css} from 'lit';
import { JsonRpc } from 'jsonrpc';
import '@vaadin/icon';
import '@vaadin/button';
import '@vaadin/confirm-dialog';
import '@vaadin/text-field';
import '@vaadin/text-area';
import '@vaadin/form-layout';
import '@vaadin/progress-bar';
import '@vaadin/checkbox';
import '@vaadin/grid';
import '@vaadin/grid/vaadin-grid-sort-column.js';
import 'qui-alert';
import { until } from 'lit/directives/until.js';
import { columnBodyRenderer } from '@vaadin/grid/lit.js';

export class QwcLiquibaseDatasources extends LitElement {

jsonRpc = new JsonRpc("Liquibase");

static styles = css`
.button {
background-color: transparent;
cursor: pointer;
}
.clearIcon {
color: red;
}
.migrateIcon {
color: yellow;
}
.message {
padding: 15px;
text-align: center;
margin-left: 20%;
margin-right: 20%;
border: 2px solid orange;
border-radius: 10px;
font-size: large;
}
`;

static properties = {
"_factories": {state: true},
"_ds": {state: true},
"_message": {state: true},
"_dialogOpened": {state: true}
}

connectedCallback() {
super.connectedCallback();
this.jsonRpc.getLiquibaseFactories().then(jsonRpcResponse => {
this._factories = jsonRpcResponse.result;
});
}

render() {
return html`${until(this._renderDataSourceTable(), html`<span>Loading datasources...</span>`)}`;
}

_renderDataSourceTable() {
if (this._factories) {
return html`
${this._message}
<vaadin-grid .items="${this._factories}" class="datatable" theme="no-border">
<vaadin-grid-column auto-width
header="Name"
${columnBodyRenderer(this._nameRenderer, [])}>
</vaadin-grid-column>
<vaadin-grid-column auto-width
header="Action"
${columnBodyRenderer(this._actionRenderer, [])}
resizable>
</vaadin-grid-column>
</vaadin-grid>
<vaadin-confirm-dialog
header="Clear Database"
cancel
confirm-text="Clear"
.opened="${this._dialogOpened}"
@confirm="${() => {
this._clear(this._ds);
}}"
@cancel="${() => {
this._dialogOpened = false;
}}"
>
This will drop all objects (tables, views, procedures, triggers, ...) in the configured schema. Do you want to continue?
</vaadin-confirm-dialog>
`;
}
}

_actionRenderer(ds) {
return html`
<vaadin-button theme="primary small" @click=${() => this._confirm(ds)} class="button">
<vaadin-icon class="clearIcon" icon="font-awesome-solid:power-off"></vaadin-icon> Clear
</vaadin-button>
<vaadin-button theme="primary small" @click=${() => this._migrate(ds)} class="button">
<vaadin-icon class="migrateIcon" icon="font-awesome-solid:bolt-lightning"></vaadin-icon> Migrate
</vaadin-button>
`;
}

_nameRenderer(ds) {
return html`${ds.dataSourceName}`;
}

_confirm(ds) {
this._message = '';
this._ds = ds;
this._dialogOpened = true;
}

_clear(ds) {
this._message = '';
this.jsonRpc.clear({ds: ds.dataSourceName}).then(jsonRpcResponse => {
this._message = html`<qui-alert level="success" showIcon>
<span>The datasource <code>${ds.dataSourceName}</code> has been cleared.</span>
</qui-alert>`
});
this._ds = null;
}

_migrate(ds) {
this._message = '';
this.jsonRpc.migrate({ds: ds.dataSourceName}).then(jsonRpcResponse => {
this._message = html`<qui-alert level="success" showIcon>
<span>The datasource <code>${ds.dataSourceName}</code> has been migrated.</span>
</qui-alert>`
});
}

}
customElements.define('qwc-liquibase-datasources', QwcLiquibaseDatasources);
5 changes: 5 additions & 0 deletions extensions/liquibase/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
<artifactId>quarkus-vertx-http</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-dev-console-runtime-spi</artifactId>
<optional>true</optional>
</dependency>

<!-- test dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.liquibase.runtime.devconsole;
package io.quarkus.liquibase.runtime.devui;

import java.util.Collection;
import java.util.Collections;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.quarkus.liquibase.runtime.devui;

import java.util.Collection;

import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.liquibase.LiquibaseFactory;
import liquibase.Liquibase;

@ApplicationScoped
public class LiquibaseJsonRpcService {

private Collection<LiquibaseFactory> factories;

@PostConstruct
void init() {
factories = new LiquibaseFactoriesSupplier().get();
}

public boolean clear(String ds) throws Exception {
for (LiquibaseFactory lf : factories) {
if (ds.equalsIgnoreCase(lf.getDataSourceName())) {
try (Liquibase liquibase = lf.createLiquibase()) {
liquibase.dropAll();
}
return true;
}
}
return false;
}

public boolean migrate(String ds) throws Exception {
for (LiquibaseFactory lf : factories) {
if (ds.equalsIgnoreCase(lf.getDataSourceName())) {
try (Liquibase liquibase = lf.createLiquibase()) {
liquibase.update(lf.createContexts(), lf.createLabels());
}
return true;
}
}
return false;
}

public Integer getDatasourceCount() {
return factories.size();
}

public Collection<LiquibaseFactory> getLiquibaseFactories() {
return factories;
}
}

0 comments on commit 378dbb6

Please sign in to comment.