Skip to content

Commit

Permalink
[4346] Improve the query view with object icons and a loading feedback
Browse files Browse the repository at this point in the history
Bug: #4346
Signed-off-by: Stéphane Bégaudeau <[email protected]>
  • Loading branch information
sbegaudeau committed Jan 6, 2025
1 parent ad44eda commit 979953f
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 13 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ This may have some consequences for downstream applications which are embedding
- https://github.com/eclipse-sirius/sirius-web/issues/4356[#4356] [diagram] Add a minimap to diagram.
+ Added by default to all sirius-web diagrams
- https://github.com/eclipse-sirius/sirius-web/issues/4346[#4346] [query] Add support for a query view.
Specifiers can contribute dedicated AQL services for this feature using implementations of `IInterpreterJavaServiceProvider`.
Specifiers can contribute dedicated AQL services for this feature using implementations of `IInterpreterJavaServiceProvider`
- https://github.com/eclipse-sirius/sirius-web/issues/4346[#4346] [core] The field `Object#iconURLs` has been added to the GraphQL schema in order to retrieve the icon of an object
- https://github.com/eclipse-sirius/sirius-web/issues/4346[#4346] [query] Add some feedback for long running evaluations


=== Improvements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ type Object {
id: ID!
kind: String!
label: String!
iconURLs: [String!]!
queryBasedString(query: String!): String
queryBasedInt(query: String!): Int
queryBasedBoolean(query: String!): Boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2025 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.web.application.object.controllers;

import java.util.List;
import java.util.Objects;

import org.eclipse.sirius.components.annotations.spring.graphql.QueryDataFetcher;
import org.eclipse.sirius.components.core.api.IImageURLSanitizer;
import org.eclipse.sirius.components.core.api.ILabelService;
import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates;
import org.eclipse.sirius.components.graphql.api.URLConstants;

import graphql.schema.DataFetchingEnvironment;

/**
* The data fetcher for the field Object#iconURLs.
*
* @author sbegaudeau
*/
@QueryDataFetcher(type = "Object", field = "iconURLs")
public class ObjectIconURLsDataFetcher implements IDataFetcherWithFieldCoordinates<List<String>> {

private final IImageURLSanitizer imageURLSanitizer;

private final ILabelService labelService;

public ObjectIconURLsDataFetcher(IImageURLSanitizer imageURLSanitizer, ILabelService labelService) {
this.imageURLSanitizer = Objects.requireNonNull(imageURLSanitizer);
this.labelService = Objects.requireNonNull(labelService);
}

@Override
public List<String> get(DataFetchingEnvironment environment) throws Exception {
var object = environment.getSource();

return this.labelService.getImagePath(object).stream()
.map(url -> this.imageURLSanitizer.sanitize(URLConstants.IMAGE_BASE_PATH, url))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
import { WorkbenchViewComponentProps } from '@eclipse-sirius/sirius-components-core';
import { IconOverlay, WorkbenchViewComponentProps } from '@eclipse-sirius/sirius-components-core';
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import { SxProps, Theme } from '@mui/material/styles';
import Skeleton from '@mui/material/Skeleton';
import { SxProps, Theme, useTheme } from '@mui/material/styles';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import { ComponentType, useState } from 'react';
Expand Down Expand Up @@ -51,7 +53,7 @@ export const QueryView = ({ editingContextId, readOnly }: WorkbenchViewComponent
return (
<Box data-representation-kind="interpreter" sx={interpreterStyle}>
<ExpressionArea onEvaluateExpression={handleEvaluateExpression} disabled={loading || readOnly} />
<ResultArea payload={result} />
<ResultArea loading={loading} payload={result} />
</Box>
);
};
Expand Down Expand Up @@ -117,13 +119,23 @@ const ObjectExpressionResultViewer = ({ result }: ExpressionResultViewerProps) =
}

const { objectValue } = result as GQLObjectExpressionResult;

const listItemStyle: SxProps<Theme> = (theme) => ({
gap: theme.spacing(2),
});
const listItemIconStyle: SxProps<Theme> = () => ({
minWidth: '0px',
});
return (
<Box>
<Typography variant="body2" gutterBottom>
One object returned
One object has been returned
</Typography>
<List dense>
<ListItem>
<ListItem sx={listItemStyle}>
<ListItemIcon sx={listItemIconStyle}>
<IconOverlay iconURL={objectValue.iconURLs} alt="Icon of the object" />
</ListItemIcon>
<ListItemText primary={objectValue.label} />
</ListItem>
</List>
Expand All @@ -137,15 +149,25 @@ const ObjectsExpressionResultViewer = ({ result }: ExpressionResultViewerProps)
}

const { objectsValue } = result as GQLObjectsExpressionResult;

const listItemStyle: SxProps<Theme> = (theme) => ({
gap: theme.spacing(2),
});
const listItemIconStyle: SxProps<Theme> = () => ({
minWidth: '0px',
});
return (
<Box>
<Typography variant="body2" gutterBottom>
{objectsValue.length} object(s) returned
A collection of {objectsValue.length} object{objectsValue.length > 1 ? 's' : ''} has been returned
</Typography>
<List dense>
{objectsValue.map((object) => {
return (
<ListItem key={object.id}>
<ListItem key={object.id} sx={listItemStyle}>
<ListItemIcon sx={listItemIconStyle}>
<IconOverlay iconURL={object.iconURLs} alt="Icon of the object" />
</ListItemIcon>
<ListItemText primary={object.label} />
</ListItem>
);
Expand All @@ -164,7 +186,7 @@ const BooleanExpressionResultViewer = ({ result }: ExpressionResultViewerProps)
return (
<Box>
<Typography variant="body2" gutterBottom>
One boolean returned
One boolean has been returned
</Typography>
<List dense>
<ListItem>
Expand All @@ -184,7 +206,7 @@ const StringExpressionResultViewer = ({ result }: ExpressionResultViewerProps) =
return (
<Box>
<Typography variant="body2" gutterBottom>
One string returned
One string has been returned
</Typography>
<List dense>
<ListItem>
Expand All @@ -204,7 +226,7 @@ const IntExpressionResultViewer = ({ result }: ExpressionResultViewerProps) => {
return (
<Box>
<Typography variant="body2" gutterBottom>
One integer returned
One integer has been returned
</Typography>
<List dense>
<ListItem>
Expand All @@ -223,7 +245,38 @@ const resultType2viewer: Record<string, ComponentType<ExpressionResultViewerProp
IntExpressionResult: IntExpressionResultViewer,
};

const ResultArea = ({ payload }: ResultAreaProps) => {
const LoadingViewer = () => {
const theme = useTheme();

const listItemStyle: SxProps<Theme> = (theme) => ({
gap: theme.spacing(2),
});
const skeletonTextStyle: SxProps<Theme> = (theme) => ({
fontSize: theme.typography.body1.fontSize,
width: '60%',
});
return (
<Box>
<Skeleton variant="text" sx={{ fontSize: theme.typography.body2.fontSize }} />
<List dense>
<ListItem sx={listItemStyle}>
<Skeleton variant="circular" width={16} height={16} />
<Skeleton variant="text" sx={skeletonTextStyle} />
</ListItem>
<ListItem sx={listItemStyle}>
<Skeleton variant="circular" width={16} height={16} />
<Skeleton variant="text" sx={skeletonTextStyle} />
</ListItem>
<ListItem sx={listItemStyle}>
<Skeleton variant="circular" width={16} height={16} />
<Skeleton variant="text" sx={skeletonTextStyle} />
</ListItem>
</List>
</Box>
);
};

const ResultArea = ({ loading, payload }: ResultAreaProps) => {
const resultAreaToolbarStyle: SxProps<Theme> = {
display: 'flex',
flexDirection: 'row',
Expand All @@ -237,7 +290,9 @@ const ResultArea = ({ payload }: ResultAreaProps) => {
};

let content: JSX.Element | null = null;
if (payload) {
if (loading) {
content = <LoadingViewer />;
} else if (payload) {
const Viewer = resultType2viewer[payload.result.__typename];
if (Viewer) {
content = <Viewer result={payload.result} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ExpressionAreaState {
}

export interface ResultAreaProps {
loading: boolean;
payload: GQLEvaluateExpressionSuccessPayload | null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ const evaluateExpressionMutation = gql`
id
kind
label
iconURLs
}
}
... on ObjectsExpressionResult {
objectsValue: value {
id
kind
label
iconURLs
}
}
... on BooleanExpressionResult {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface GQLObject {
id: string;
kind: string;
label: string;
iconURLs: string[];
}

export interface GQLObjectsExpressionResult extends GQLExpressionResult {
Expand Down

0 comments on commit 979953f

Please sign in to comment.