Skip to content

Commit

Permalink
GDB-7785 Introduce share saved query action (#51)
Browse files Browse the repository at this point in the history
* GDB-7785 Introduce share saved query action

## What
This MR introduces support for sharing of saved query through a link.

## Why
GDB workbench supports functionality for sharing of saved queries through a specially crafted link.

## How
* Introduced a new action button for each saved query which opens a window with a field containing the share link. The copy link button writes the link into the clipboard and closes the dialog.
* Implemented tests.

* Make the share link field readonly in order to prevent modifications by the user.
  • Loading branch information
svilenvelikov authored Jan 23, 2023
1 parent c7013d7 commit 41b56ea
Show file tree
Hide file tree
Showing 20 changed files with 642 additions and 15 deletions.
36 changes: 36 additions & 0 deletions cypress/e2e/saved-query/share-saved-query.cpec.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {YasqeSteps} from "../../steps/yasqe-steps";
import {QueryStubs} from "../../stubs/query-stubs";
import ActionsPageSteps from "../../steps/actions-page-steps";

describe('Share saved query action', () => {
beforeEach(() => {
QueryStubs.stubDefaultQueryResponse();
// Given I have opened a page with the yasgui
// And there is an open tab with sparql query in it
ActionsPageSteps.visit();
});

it('Should be able to get shareable link for any saved query', () => {
// Given I have opened the saved queries popup
YasqeSteps.showSavedQueries();
YasqeSteps.getSavedQueriesPopup().should('be.visible');
// When I click on share query button on a particular query
YasqeSteps.shareSavedQuery(0);
// Then I expect a dialog with the shareable link to appear
YasqeSteps.getShareSavedQueryDialog().should('be.visible');
YasqeSteps.getShareSavedQueryDialogTitle().should('contain', 'Copy URL to clipboard');
YasqeSteps.getShareSavedQueryLink().should('have.value', 'http://localhost:3333/pages/actions?savedQueryName=Add%20statements');
// When I cancel operation
YasqeSteps.closeShareSavedQueryDialog();
// Then I expect that the dialog should be closed
YasqeSteps.getShareSavedQueryDialog().should('not.exist');
// When I click on share query button on a particular query
YasqeSteps.showSavedQueries();
YasqeSteps.shareSavedQuery(0);
// And I click the copy button
YasqeSteps.copySavedQueryShareLink();
// Then I expect that the share link is copied in the clipboard
YasqeSteps.getShareSavedQueryDialog().should('not.exist');
ActionsPageSteps.getSaveQueryPayload().should('have.value', 'http://localhost:3333/pages/actions?savedQueryName=Add%20statements');
});
});
24 changes: 24 additions & 0 deletions cypress/steps/yasqe-steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ export class YasqeSteps {
this.getSavedQueries().eq(index).realHover().find('.delete-saved-query').click();
}

static shareSavedQuery(index: number) {
this.getSavedQueries().eq(index).realHover().find('.share-saved-query').click();
}

static getDeleteQueryConfirmation() {
return cy.get('.confirmation-dialog');
}
Expand All @@ -155,4 +159,24 @@ export class YasqeSteps {
static rejectQueryDelete() {
this.getDeleteQueryConfirmation().find('.cancel-button').click();
}

static getShareSavedQueryDialog() {
return cy.get('.share-saved-query-dialog');
}

static getShareSavedQueryDialogTitle() {
return this.getShareSavedQueryDialog().find('.dialog-title');
}

static closeShareSavedQueryDialog() {
this.getShareSavedQueryDialog().find('.cancel-button').click();
}

static copySavedQueryShareLink() {
this.getShareSavedQueryDialog().find('.copy-button').click();
}

static getShareSavedQueryLink() {
return this.getShareSavedQueryDialog().find('.share-link-field input')
}
}
58 changes: 58 additions & 0 deletions ontotext-yasgui-web-component/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { ServiceFactory } from "./services/service-factory";
import { ConfirmationDialogConfig } from "./components/confirmation-dialog/confirmation-dialog";
import { DialogConfig } from "./components/ontotext-dialog-web-component/ontotext-dialog-web-component";
import { ExternalYasguiConfiguration } from "./models/external-yasgui-configuration";
import { SavedQueriesData, SavedQueryConfig, SaveQueryData, UpdateQueryData } from "./models/model";
import { QueryEvent, QueryResponseEvent } from "./models/event";
import { ShareSavedQueryDialogConfig } from "./components/share-saved-query-dialog/share-saved-query-dialog";
export namespace Components {
interface ConfirmationDialog {
"config": ConfirmationDialogConfig;
"serviceFactory": ServiceFactory;
}
interface OntotextDialogWebComponent {
"config": DialogConfig;
}
/**
* This is the custom web component which is adapter for the yasgui library. It allows as to
* configure and extend the library without potentially breaking the component clients.
Expand Down Expand Up @@ -56,6 +61,10 @@ export namespace Components {
interface SavedQueriesPopup {
"config": SavedQueriesData;
}
interface ShareSavedQueryDialog {
"config": ShareSavedQueryDialogConfig;
"serviceFactory": ServiceFactory;
}
interface YasguiTooltip {
"dataTooltip": string;
"placement": string;
Expand All @@ -78,13 +87,23 @@ export interface SavedQueriesPopupCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLSavedQueriesPopupElement;
}
export interface ShareSavedQueryDialogCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLShareSavedQueryDialogElement;
}
declare global {
interface HTMLConfirmationDialogElement extends Components.ConfirmationDialog, HTMLStencilElement {
}
var HTMLConfirmationDialogElement: {
prototype: HTMLConfirmationDialogElement;
new (): HTMLConfirmationDialogElement;
};
interface HTMLOntotextDialogWebComponentElement extends Components.OntotextDialogWebComponent, HTMLStencilElement {
}
var HTMLOntotextDialogWebComponentElement: {
prototype: HTMLOntotextDialogWebComponentElement;
new (): HTMLOntotextDialogWebComponentElement;
};
/**
* This is the custom web component which is adapter for the yasgui library. It allows as to
* configure and extend the library without potentially breaking the component clients.
Expand Down Expand Up @@ -119,6 +138,12 @@ declare global {
prototype: HTMLSavedQueriesPopupElement;
new (): HTMLSavedQueriesPopupElement;
};
interface HTMLShareSavedQueryDialogElement extends Components.ShareSavedQueryDialog, HTMLStencilElement {
}
var HTMLShareSavedQueryDialogElement: {
prototype: HTMLShareSavedQueryDialogElement;
new (): HTMLShareSavedQueryDialogElement;
};
interface HTMLYasguiTooltipElement extends Components.YasguiTooltip, HTMLStencilElement {
}
var HTMLYasguiTooltipElement: {
Expand All @@ -127,9 +152,11 @@ declare global {
};
interface HTMLElementTagNameMap {
"confirmation-dialog": HTMLConfirmationDialogElement;
"ontotext-dialog-web-component": HTMLOntotextDialogWebComponentElement;
"ontotext-yasgui": HTMLOntotextYasguiElement;
"save-query-dialog": HTMLSaveQueryDialogElement;
"saved-queries-popup": HTMLSavedQueriesPopupElement;
"share-saved-query-dialog": HTMLShareSavedQueryDialogElement;
"yasgui-tooltip": HTMLYasguiTooltipElement;
}
}
Expand All @@ -146,6 +173,9 @@ declare namespace LocalJSX {
"onInternalConfirmationRejectedEvent"?: (event: ConfirmationDialogCustomEvent<any>) => void;
"serviceFactory"?: ServiceFactory;
}
interface OntotextDialogWebComponent {
"config"?: DialogConfig;
}
/**
* This is the custom web component which is adapter for the yasgui library. It allows as to
* configure and extend the library without potentially breaking the component clients.
Expand Down Expand Up @@ -191,6 +221,14 @@ declare namespace LocalJSX {
* Event emitted when after query response is returned.
*/
"onQueryResponse"?: (event: OntotextYasguiCustomEvent<QueryResponseEvent>) => void;
/**
* Event emitted when saved query share link gets copied in the clipboard.
*/
"onSavedQueryShareLinkCopied"?: (event: OntotextYasguiCustomEvent<any>) => void;
/**
* Event emitted when saved query share link has to be build by the client.
*/
"onShareSavedQuery"?: (event: OntotextYasguiCustomEvent<SaveQueryData>) => void;
/**
* Event emitted when a query payload is updated and the query name is the same as the one being edited. In result the client must perform a query update.
*/
Expand Down Expand Up @@ -237,6 +275,22 @@ declare namespace LocalJSX {
* Event fired when the delete saved query button is triggered.
*/
"onInternalSavedQuerySelectedForDeleteEvent"?: (event: SavedQueriesPopupCustomEvent<SaveQueryData>) => void;
/**
* Event fired when the share saved query button is triggered.
*/
"onInternalSavedQuerySelectedForShareEvent"?: (event: SavedQueriesPopupCustomEvent<SaveQueryData>) => void;
}
interface ShareSavedQueryDialog {
"config"?: ShareSavedQueryDialogConfig;
/**
* Internal event fired when saved query share link is copied in the clipboard.
*/
"onInternalSavedQueryShareLinkCopiedEvent"?: (event: ShareSavedQueryDialogCustomEvent<any>) => void;
/**
* Event fired when the dialog is closed by triggering one of the close controls, e.g. close or cancel button as well as clicking outside of the dialog.
*/
"onInternalShareSavedQueryDialogClosedEvent"?: (event: ShareSavedQueryDialogCustomEvent<any>) => void;
"serviceFactory"?: ServiceFactory;
}
interface YasguiTooltip {
"dataTooltip"?: string;
Expand All @@ -245,9 +299,11 @@ declare namespace LocalJSX {
}
interface IntrinsicElements {
"confirmation-dialog": ConfirmationDialog;
"ontotext-dialog-web-component": OntotextDialogWebComponent;
"ontotext-yasgui": OntotextYasgui;
"save-query-dialog": SaveQueryDialog;
"saved-queries-popup": SavedQueriesPopup;
"share-saved-query-dialog": ShareSavedQueryDialog;
"yasgui-tooltip": YasguiTooltip;
}
}
Expand All @@ -256,6 +312,7 @@ declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"confirmation-dialog": LocalJSX.ConfirmationDialog & JSXBase.HTMLAttributes<HTMLConfirmationDialogElement>;
"ontotext-dialog-web-component": LocalJSX.OntotextDialogWebComponent & JSXBase.HTMLAttributes<HTMLOntotextDialogWebComponentElement>;
/**
* This is the custom web component which is adapter for the yasgui library. It allows as to
* configure and extend the library without potentially breaking the component clients.
Expand All @@ -275,6 +332,7 @@ declare module "@stencil/core" {
"ontotext-yasgui": LocalJSX.OntotextYasgui & JSXBase.HTMLAttributes<HTMLOntotextYasguiElement>;
"save-query-dialog": LocalJSX.SaveQueryDialog & JSXBase.HTMLAttributes<HTMLSaveQueryDialogElement>;
"saved-queries-popup": LocalJSX.SavedQueriesPopup & JSXBase.HTMLAttributes<HTMLSavedQueriesPopupElement>;
"share-saved-query-dialog": LocalJSX.ShareSavedQueryDialog & JSXBase.HTMLAttributes<HTMLShareSavedQueryDialogElement>;
"yasgui-tooltip": LocalJSX.YasguiTooltip & JSXBase.HTMLAttributes<HTMLYasguiTooltipElement>;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
@import 'src/css/common';

:host {
display: block;
}

.dialog-overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
// Because the tooltip we use position itself on z-index: 9999
z-index: 9998;
margin-left: -10px;
background-color: rgba(0, 0, 0, .5);
font-family: 'Roboto', 'Helvetica Neue', Arial, sans-serif;
font-weight: 300;
color: #373a3c;
}

.dialog {
z-index: 1001;
width: 600px;
max-width: 600px;
background-color: #fff;
box-shadow: 0 5px 15px rgb(0 0 0 / 50%);
font-family: inherit;
font-weight: inherit;

.dialog-header {
display: flex;
justify-content: space-between;
padding: 15px;
border-bottom: 1px solid #e5e5e5;
font-size: 20px;

.dialog-title {
margin: .5rem 0;
font-weight: 400;
}

.close-button {
background-color: #fff;
border: none;
outline: none;
cursor: pointer;
transition: all .15s ease-out;

&:hover, &:focus {
font-weight: bold;
}
}
}

.dialog-body {
padding: 15px;
}

.dialog-footer {
display: flex;
justify-content: flex-end;
padding: 15px;
border-top: 1px solid #e5e5e5;

button {
display: inline-block;
font-weight: 400;
line-height: 1.25;
text-align: center;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
border: 1px solid transparent;
outline: none;
padding: .5rem 1rem;
font-size: 1rem;
border-radius: 0;
transition: all .15s ease-out;
}

@include primaryButton;
@include secondaryButton;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {Component, h, Host, Prop} from '@stencil/core';

export type DialogConfig = {
dialogTitle: string;
onClose: (evt: MouseEvent) => void;
}

@Component({
tag: 'ontotext-dialog-web-component',
styleUrl: 'ontotext-dialog-web-component.scss',
shadow: false,
})
export class OntotextDialogWebComponent {

@Prop() config: DialogConfig;

render() {
return (
<Host>
<div class="dialog-overlay" onClick={(evt) => this.config.onClose(evt)}>
<div class="dialog">
<div class="dialog-header">
<h3 class="dialog-title">{this.config.dialogTitle}</h3>
<button class="close-button icon-close" onClick={(evt) => this.config.onClose(evt)}></button>
</div>
<div class="dialog-body">
<slot name="body" />
</div>
<div class="dialog-footer">
<slot name="footer" />
</div>
</div>
</div>
</Host>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# ontotext-dialog-web-component



<!-- Auto Generated Below -->


## Properties

| Property | Attribute | Description | Type | Default |
| -------- | --------- | ----------- | -------------------------------------------------------------- | ----------- |
| `config` | -- | | `{ dialogTitle: string; onClose: (evt: MouseEvent) => void; }` | `undefined` |


## Dependencies

### Used by

- [share-saved-query-dialog](../share-saved-query-dialog)

### Graph
```mermaid
graph TD;
share-saved-query-dialog --> ontotext-dialog-web-component
style ontotext-dialog-web-component fill:#f9f,stroke:#333,stroke-width:4px
```

----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*
Loading

0 comments on commit 41b56ea

Please sign in to comment.