Skip to content

Commit

Permalink
Add variable info search (whatsopt#198)
Browse files Browse the repository at this point in the history
* Add variable search panel

* Implement variable list API

* Implement var infos API

* Fix find source

* Display source and targets discipline

* Add discipline path as tooltip to discipline button

* Improve variable search UI

* Bump version 1.30.1

* Move optional tabs to the left
  • Loading branch information
relf authored Apr 25, 2024
1 parent d87a0e0 commit 4d3e645
Show file tree
Hide file tree
Showing 15 changed files with 342 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<h1>CHANGELOG</h1>

<h3>1.30.1 (18/04/2024)</h3>
<ul>
<li>Add variable search tab: User can query and browse variable source and targets disciplines</li>
</ul>

<h3>1.30.0 (22/03/2024)</h3>
<ul>
<li>Tech: Ruby 3.3, upgrade backend and frontend dependencies</li>
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.30.0
1.30.1
22 changes: 22 additions & 0 deletions app/controllers/api/v1/variables_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

class Api::V1::VariablesController < Api::V1::ApiMdaUpdaterController

# GET /api/v1/analyses/{mda_id}/variables
def index
@mda = Analysis.find(params[:mda_id])
authorize @mda, :show?
variables = policy_scope(Variable).of_analysis(@mda).where(io_mode: WhatsOpt::Variable::OUT)
json_response variables, :ok, each_serializer: VariableSearchSerializer
end

# GET /api/v1/analyses/{mda_id}/variables/{id}
def show
@variable = Variable.find(params[:id])
@mda = @mda = Analysis.find(params[:mda_id])
authorize @mda, :show?
varinfo = @mda.find_info(@variable)
json_response varinfo.to_json
end

end
1 change: 0 additions & 1 deletion app/javascript/mda_viewer/components/ProjectSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ class ProjectSelector extends React.Component {
isLoading: false,
options: [selected],
};
this.typeaheadRef = React.createRef();
this.handleSearch = this.handleSearch.bind(this);
this.handleChange = this.handleChange.bind(this);
}
Expand Down
126 changes: 126 additions & 0 deletions app/javascript/mda_viewer/components/VariableSearchPanel.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Tooltip } from 'bootstrap';
import VariableSelector from './VariableSelector';

function getDiscButtons(api, discs) {
const buttons = discs.map(([disc, path]) => {
const { name, analysis_id } = disc;
const title = path.map((a) => (a.name)).join('/');
const label = name === '__DRIVER__' ? 'User' : `${disc.name}`;
return (
<div key={disc.id} className="btn-group me-2 mt-2 disc-button" role="group" data-bs-toggle="tooltip" title={`${title}`}>
<a href={api.url(`/analyses/${analysis_id}`)} className="btn btn-info" role="button">{label}</a>
</div>
);
});
return buttons;
}

class VariableDisplay extends React.PureComponent {
componentDidUpdate() {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltipTriggerList].map((tooltipTriggerEl) => new Tooltip(tooltipTriggerEl, {
trigger: 'hover',
}));
}

render() {
const { api, varinfo } = this.props;
const disciplineFrom = getDiscButtons(api, varinfo.from);
const disciplineTo = getDiscButtons(api, varinfo.to);

return (
<div className="editor-section">
<div className="editor-section-label">Source</div>
<div className="mb-2">
{ disciplineFrom }
</div>

<div className="editor-section-label">Targets</div>
<div className="mb-2">
{ disciplineTo }
</div>
</div>
);
}
}

VariableDisplay.propTypes = {
api: PropTypes.object.isRequired,
varinfo: PropTypes.object.isRequired,
};

class VariableSearchPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
selected: [],
vars: [],
varinfo: { from: [], to: [] },
};
this.handleVariableSelected = this.handleVariableSelected.bind(this);
}

componentDidMount() {
const { mdaId, api } = this.props;
api.getVariables(mdaId, (response) => {
const vars = response.data;
this.setState({ vars });
});
}

handleVariableSelected(selected) {
const [selection] = selected;
const { mdaId, api } = this.props;

api.getVariableInformation(
mdaId,
selection.id,
(response) => {
const varinfo = { from: [response.data.from], to: response.data.to };
this.setState({ varinfo, selected });
},
(error) => {
console.log(error);
},
);
}

render() {
const { selected, vars, varinfo } = this.state;
const { api } = this.props;

console.log(selected);
let varDisplay = null;
if (selected.length !== 0) {
varDisplay = (<VariableDisplay api={api} varinfo={varinfo} />);
}

return (
<div className="container-fluid">
<div className="row">
<div className="editor-section col-4">
<VariableSelector
vars={vars}
message="Search variable..."
selected={selected}
onVariableSelected={this.handleVariableSelected}
disabled={false}
/>
</div>
<div className="editor-section col-12">
{varDisplay}
</div>
</div>
</div>
);
}
}

VariableSearchPanel.propTypes = {
api: PropTypes.object.isRequired,
mdaId: PropTypes.number.isRequired,
};

export default VariableSearchPanel;
49 changes: 49 additions & 0 deletions app/javascript/mda_viewer/components/VariableSelector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';

import { Typeahead } from 'react-bootstrap-typeahead';

class VariableSelector extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}

handleChange(selected) {
if (selected.length) {
const { onVariableSelected } = this.props;
onVariableSelected(selected);
}
}

render() {
const {
message, selected, disabled, vars: options,
} = this.props;
return (
<Typeahead
id="varsearch"
defaultSelected={selected}
clearButton
options={options}
allowNew={false}
multiple={false}
labelKey="name"
minLength={1}
placeholder={message}
onChange={this.handleChange}
disabled={disabled}
/>
);
}
}

VariableSelector.propTypes = {
vars: PropTypes.array.isRequired,
message: PropTypes.string.isRequired,
selected: PropTypes.array.isRequired,
disabled: PropTypes.bool.isRequired,
onVariableSelected: PropTypes.func.isRequired,
};

export default VariableSelector;
25 changes: 21 additions & 4 deletions app/javascript/mda_viewer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ExportPanel from 'mda_viewer/components/ExportPanel';
import HistoryPanel from 'mda_viewer/components/HistoryPanel';
import ComparisonPanel from 'mda_viewer/components/ComparisonPanel';
import DistributionModals from 'mda_viewer/components/DistributionModals';
import VariableSearchPanel from './components/VariableSearchPanel';

import Error from '../utils/components/Error';
import MetaModelQualification from '../utils/components/MetaModelQualification';
Expand Down Expand Up @@ -913,8 +914,19 @@ class MdaViewer extends React.Component {
Variables
</a>
</li>
{noteItem}
{metaModelItem}
<li className="nav-item">
<a
className="nav-link"
id="var-search-tab"
data-bs-toggle="tab"
href="#var-search"
role="tab"
aria-controls="var-search"
aria-selected="false"
>
Search...
</a>
</li>
<li className="nav-item">
<a
className="nav-link"
Expand Down Expand Up @@ -954,13 +966,16 @@ class MdaViewer extends React.Component {
History
</a>
</li>
{noteItem}
{metaModelItem}
</ul>
<div className="tab-content" id="myTabContent">
<div className="tab-pane fade show active" id="variables" role="tabpanel" aria-labelledby="variables-tab">
{varEditor}
</div>
{noteTab}
{metaModelTab}
<div className="tab-pane fade" id="var-search" role="tabpanel" aria-labelledby="var-search-tab">
<VariableSearchPanel api={this.api} mdaId={db.mda.id} />
</div>
<div className="tab-pane fade" id="exports" role="tabpanel" aria-labelledby="exports-tab">
<ExportPanel
api={this.api}
Expand All @@ -973,6 +988,8 @@ class MdaViewer extends React.Component {
<div className="tab-pane fade" id="history" role="tabpanel" aria-labelledby="history-tab">
<HistoryPanel api={this.api} mdaId={db.mda.id} />
</div>
{noteTab}
{metaModelTab}
</div>
</div>
</div>
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/utils/AnalysisDatabase.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ class AnalysisDatabase {
return this.inputVariables;
}

getAnalysisOutputVariables() {
return this.outputVariables;
}

isConnected(nodeId) {
return (this.mda.vars[nodeId].out.length !== 0 || this.mda.vars[nodeId].in.length !== 0);
}
Expand Down
14 changes: 14 additions & 0 deletions app/javascript/utils/WhatsOptApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,20 @@ class WhatsOptApi {
.catch((error) => console.log(error));
}

getVariables(mdaId, callback) {
const path = `/analyses/${mdaId}/variables`;
axios.get(this.apiUrl(path))
.then(callback)
.catch((error) => console.log(error));
}

getVariableInformation(mdaId, id, callback) {
const path = `/analyses/${mdaId}/variables/${id}`;
axios.get(this.apiUrl(path))
.then(callback)
.catch((error) => console.log(error));
}

importDiscipline(fromMdaId, discId, toMdaId, callback, onError) {
const path = `/analyses/${toMdaId}`;
this.getAnalysis(toMdaId, false, (response) => {
Expand Down
45 changes: 45 additions & 0 deletions app/models/analysis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,51 @@ def impl
self.openmdao_impl || OpenmdaoAnalysisImpl.new(analysis: self)
end

# Find informations about variable usage within the mda

def find_source(var)
out_disc = var.discipline
if out_disc.is_driver? and not self.is_root_analysis?
# should get the only out var of the same name in the parent analysis
var = Variable.of_analysis(self.parent).where(name: var.name, io_mode: WhatsOpt::Variable::OUT).first
self.parent.find_source(var)
elsif out_disc.has_sub_analysis?
var = Variable.of_analysis(out_disc.sub_analysis).where(name: var.name, io_mode: WhatsOpt::Variable::OUT).first
out_disc.sub_analysis.find_source(var)
else
abspath = out_disc.path.to_a
abspath.shift
[out_disc, abspath]
end
end

def find_targets(var)
in_discs = var.outgoing_connections.map(&:to).map(&:discipline)
res = []
in_discs.each do |in_disc|
if in_disc.has_sub_analysis?
# should get the only out var of the same name in the sub analysis
var = Variable.of_analysis(in_disc.sub_analysis).where(name: var.name, io_mode: WhatsOpt::Variable::OUT).first
res += in_disc.sub_analysis.find_targets(var)
else
puts "Add target #{in_disc.name}"
abspath = in_disc.path.to_a
abspath.shift
res.push([in_disc, abspath])
end
end
res
end

def find_info(var)
res = {}
# from
res[:from] = self.find_source(var)
# to
res[:to] = self.find_targets(var)
res
end

private
def _ensure_driver_presence
if self.disciplines.empty?
Expand Down
9 changes: 9 additions & 0 deletions app/policies/variable_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class VariablePolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
end
5 changes: 5 additions & 0 deletions app/serializers/variable_search_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class VariableSearchSerializer < ActiveModel::Serializer
attributes :name, :id
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
resources :analyses, shallow: true, as: :mdas, only: [:index, :show, :create, :update] do
resources :disciplines, only: [:index, :create, :update, :destroy], shallow: false
resources :connections, only: [:create, :update, :destroy], shallow: false
resources :variables, only: [:index, :show], shallow: false
resources :operations, only: [:show, :create, :update, :destroy] do
resource :job, only: [:show, :create, :update] if APP_CONFIG["enable_remote_operations"]
resources :meta_models, only: [:create, :update] do
Expand Down
Loading

0 comments on commit 4d3e645

Please sign in to comment.