Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version feature #388

Open
wants to merge 22 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e30eed4
Create PULL_REQUEST_TEMPLATE.md
adbharadwaj Mar 4, 2018
6521955
Added GET Api for Graph Versions
jahandaniyal Jun 9, 2018
240fd20
Added POST Api for graph_version
jahandaniyal Jun 10, 2018
64921a3
Added DELETE API for graph_version
jahandaniyal Jun 10, 2018
4d307e3
Added Version selector dropdown
jahandaniyal Jun 10, 2018
321a432
Added API Specifications for Graph Version
jahandaniyal Jun 11, 2018
f99e668
Changes in model for version feature
jahandaniyal Jun 18, 2018
76b4acc
Added changes for upload, add graph
jahandaniyal Jun 18, 2018
50b9c7e
Update models - migrate json from graph to graph_version table
jahandaniyal Jun 23, 2018
1cdcb57
Added migration files for model changes
jahandaniyal Jun 23, 2018
e6b59cf
Added graph_version specific code to views
jahandaniyal Jun 23, 2018
9bd1573
Added graph_version specific code to controllers
jahandaniyal Jun 23, 2018
aed358b
dal changes - moved json from graph to graph_version
jahandaniyal Jun 23, 2018
9141f57
UI changes for graph_version feature
jahandaniyal Jun 23, 2018
5ba4276
Added Python Documentation for Version Feature
jahandaniyal Jun 30, 2018
3e1dffc
Fix indentations and remove unnecessary commented code
jahandaniyal Jun 30, 2018
5f7e224
Added Tests for GraphSpace models
jahandaniyal Jul 1, 2018
c2ed1d8
Fix indentation
jahandaniyal Jul 1, 2018
4135efe
Graph Version minor UI Fixes
jahandaniyal Jul 2, 2018
c2b90c8
Merge branch 'version_feature' of https://github.com/jahandaniyal/Gra…
jahandaniyal Jul 2, 2018
c4465e3
Fix Graph Fit and Layout issues for Graph Versions
jahandaniyal Jul 15, 2018
19e319d
Set correct z-index for Export button & version dropdown
jahandaniyal Jul 15, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Purpose
_Describe the problem or feature in addition to a link to the issues._

Example:
Fixes # .

## Approach
_How does this change address the problem?_

#### Open Questions and Pre-Merge TODOs
- [ ] Use github checklists. When solved, check the box and explain the answer.

## Learning
_Describe the research stage_

_Links to blog posts, patterns, libraries or addons used to solve this problem_

#### Blog Posts
- [How to Pull Request](https://github.com/flexyford/pull-request) Github Repo with Learning focused Pull Request Template.

36 changes: 36 additions & 0 deletions applications/graphs/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,39 @@ def add_edge(request, name=None, head_node_id=None, tail_node_id=None, is_direct
def delete_edge_by_id(request, edge_id):
db.delete_edge(request.db_session, id=edge_id)
return

def search_graph_versions(request, graph_id=None, names=None, limit=20, offset=0, order='desc', sort='name'):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add python documentation. Document cases where an exception will be raised.

if sort == 'name':
sort_attr = db.GraphVersion.name
elif sort == 'update_at':
sort_attr = db.GraphVersion.updated_at
else:
sort_attr = db.GraphVersion.name

if order == 'desc':
orber_by = db.desc(sort_attr)
else:
orber_by = db.asc(sort_attr)

## TODO: create a util function to relpace the code parse sort and order parameters. This code is repeated again and again.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are planning to?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi.
I did not understand the question. Could you please elaborate.
Thanks


total, graph_versions = db.find_graph_versions(request.db_session,
names=names,
graph_id=graph_id,
limit=limit,
offset=offset,
order_by=orber_by)

return total, graph_versions

def get_graph_version_by_id(request, version_id):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pydoc

return db.get_graph_version_by_id(request.db_session, version_id)

def add_graph_version(request, name=None, description=None, owner_email=None, graph_json=None, graph_id=None):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pydoc

if name is None or graph_id is None or graph_json is None:
raise Exception("Required Parameter is missing!")
return db.add_graph_version(request.db_session, name=name, description=description, owner_email=owner_email, graph_json=graph_json, graph_id=graph_id)

def delete_graph_version_by_id(request, graph_version_id):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pydoc

db.delete_graph_version(request.db_session, id=graph_version_id)
return
39 changes: 39 additions & 0 deletions applications/graphs/dal.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,42 @@ def find_edges(db_session, is_directed=None, names=None, edges=None, graph_id=No
query = query.limit(limit).offset(offset)

return total, query.all()

@with_session
def find_graph_versions(db_session, names=None, graph_id=None, limit=None, offset=None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pydoc for all dal functions

order_by=desc(GraphVersion.updated_at)):
query = db_session.query(GraphVersion)

if graph_id is not None:
query = query.filter(GraphVersion.graph_id == graph_id)

names = [] if names is None else names
if len(names) > 0:
query = query.filter(
or_(*([GraphVersion.name.ilike(name) for name in names])))

total = query.count()

if order_by is not None:
query = query.order_by(order_by)

if offset is not None and limit is not None:
query = query.limit(limit).offset(offset)

return total, query.all()

@with_session
def get_graph_version_by_id(db_session, id):
return db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none()

@with_session
def add_graph_version(db_session, graph_id, name, graph_json, owner_email, description=None):
graph_version = GraphVersion(name=name, graph_id=graph_id, graph_json=graph_json, owner_email=owner_email, description=description)
db_session.add(graph_version)
return graph_version

@with_session
def delete_graph_version(db_session, id):
graph_version = db_session.query(GraphVersion).filter(GraphVersion.id == id).one_or_none()
db_session.delete(graph_version)
return graph_version
25 changes: 25 additions & 0 deletions applications/graphs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class Graph(IDMixin, TimeStampMixin, Base):
edges = relationship("Edge", back_populates="graph", cascade="all, delete-orphan")
nodes = relationship("Node", back_populates="graph", cascade="all, delete-orphan")

graph_version = relationship("GraphVersion", foreign_keys="GraphVersion.graph_id", back_populates="graph",
cascade="all, delete-orphan")

groups = association_proxy('shared_with_groups', 'group')
tags = association_proxy('graph_tags', 'tag')

Expand Down Expand Up @@ -279,3 +282,25 @@ class GraphToTag(TimeStampMixin, Base):
def __table_args__(cls):
args = cls.constraints + cls.indices
return args

class GraphVersion(IDMixin, TimeStampMixin, Base):
__tablename__ = 'graph_version'

name = Column(String, nullable=False, unique=True)
graph_id = Column(Integer, ForeignKey('graph.id', ondelete="CASCADE", onupdate="CASCADE"), primary_key=True)
owner_email = Column(String, ForeignKey('user.email', ondelete="CASCADE", onupdate="CASCADE"), nullable=False)
graph_json = Column(String, nullable=False)
description = Column(String, nullable=True)

graph = relationship("Graph", foreign_keys=[graph_id], back_populates="graph_version", uselist=False)

def serialize(cls, **kwargs):
return {
'id': cls.id,
'name': cls.name,
'description': cls.description,
'graph_json' : cls.graph_json,
'creator': cls.owner_email,
'created_at': cls.created_at.isoformat(),
'updated_at': cls.updated_at.isoformat()
}
6 changes: 6 additions & 0 deletions applications/graphs/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
# Graph Layouts
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/layouts/$', views.graph_layouts_ajax_api, name='graph_layouts_ajax_api'),
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/layouts/(?P<layout_id>[^/]+)$', views.graph_layouts_ajax_api, name='graph_layouts_ajax_api'),
# Graph Versions
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/version/$', views.graph_versions_ajax_api, name='graph_versions_ajax_api'),
url(r'^ajax/graphs/(?P<graph_id>[^/]+)/version/(?P<version_id>[^/]+)$', views.graph_versions_ajax_api, name='graph_versions_ajax_api'),

# REST APIs Endpoints

Expand All @@ -45,5 +48,8 @@
# Graph Layouts
url(r'^api/v1/graphs/(?P<graph_id>[^/]+)/layouts/$', views.graph_layouts_rest_api, name='graph_layouts_rest_api'),
url(r'^api/v1/graphs/(?P<graph_id>[^/]+)/layouts/(?P<layout_id>[^/]+)$', views.graph_layouts_rest_api, name='graph_layouts_rest_api'),
# Graph Nodes
url(r'^api/v1/graphs/(?P<graph_id>[^/]+)/version/$', views.graph_versions_rest_api, name='graph_versions_rest_api'),
url(r'^api/v1/graphs/(?P<graph_id>[^/]+)/version/(?P<version_id>[^/]+)$', views.graph_versions_rest_api, name='graph_versions_rest_api'),

]
229 changes: 229 additions & 0 deletions applications/graphs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1522,3 +1522,232 @@ def _delete_edge(request, graph_id, edge_id):
authorization.validate(request, permission='GRAPH_UPDATE', graph_id=graph_id)

graphs.delete_edge_by_id(request, edge_id)


'''
Graph Version APIs
'''

@csrf_exempt
@is_authenticated()
def graph_versions_rest_api(request, graph_id, version_id=None):
"""
Handles any request sent to following urls:
/api/v1/graphs/<graph_id>/version
/api/v1/graphs/<graph_id>/version/<version_id>

Parameters
----------
request - HTTP Request

Returns
-------
response : JSON Response

"""
return _graph_versions_api(request, graph_id, version_id=version_id)


def graph_versions_ajax_api(request, graph_id, version_id=None):
"""
Handles any request sent to following urls:
/javascript/graphs/<graph_id>/version
/javascript/graphs/<graph_id>/version/<version_id>

Parameters
----------
request - HTTP Request

Returns
-------
response : JSON Response

"""
return _graph_versions_api(request, graph_id, version_id=version_id)


def _graph_versions_api(request, graph_id, version_id=None):
"""
Handles any request (GET/POST) sent to version/ or version/<version_id>.

Parameters
----------
request - HTTP Request
graph_id : string
Unique ID of the graph.
version_id : string
Unique ID of the version.

Returns
-------

"""
if request.META.get('HTTP_ACCEPT', None) == 'application/json':
if request.method == "GET" and version_id is None:
return HttpResponse(json.dumps(_get_graph_versions(request, graph_id, query=request.GET)),
content_type="application/json")
elif request.method == "GET" and version_id is not None:
return HttpResponse(json.dumps(_get_graph_version(request, graph_id, version_id)),
content_type="application/json")
elif request.method == "POST" and version_id is None:
return HttpResponse(json.dumps(_add_graph_version(request, graph_id, graph_version=json.loads(request.body))),
content_type="application/json",
status=201)
elif request.method == "DELETE" and version_id is not None:
_delete_graph_version(request, graph_id, version_id)
return HttpResponse(json.dumps({
"message": "Successfully deleted Graph Version with id=%s" % (version_id)
}), content_type="application/json", status=200)
else:
raise MethodNotAllowed(request) # Handle other type of request methods like OPTIONS etc.
else:
raise BadRequest(request)

def _get_graph_versions(request, graph_id, query={}):
"""

Query Parameters
----------
graph_id : string
Unique ID of the graph.
limit : integer
Number of entities to return. Default value is 20.
offset : integer
Offset the list of returned entities by this number. Default value is 0.
names : list of strings
Search for versions with given names. In order to search for versions with given name as a substring, wrap the name with percentage symbol. For example, %xyz% will search for all versions with xyz in their name.
order : string
Defines the column sort order, can only be 'asc' or 'desc'.
sort : string
Defines which column will be sorted.


Parameters
----------
request : object
HTTP GET Request.

Returns
-------
total : integer
Number of graph versions matching the request.
versions : List of versions.
List of Version Objects with given limit and offset.

Raises
------

Notes
------

"""

authorization.validate(request, permission='GRAPH_READ', graph_id=graph_id)

querydict = QueryDict('', mutable=True)
querydict.update(query)
query = querydict

total, versions_list = graphs.search_graph_versions(request,
graph_id=graph_id,
names=query.getlist('names[]', None),
limit=query.get('limit', 20),
offset=query.get('offset', 0),
order=query.get('order', 'desc'),
sort=query.get('sort', 'name'))

return {
'total': total,
'versions': [utils.serializer(version) for version in versions_list]
}

def _get_graph_version(request, graph_id, version_id):
"""

Parameters
----------
request : object
HTTP GET Request.
version_id : string
Unique ID of the version.

Returns
-------
version: object

Raises
------

Notes
------

"""
authorization.validate(request, permission='GRAPH_READ', graph_id=graph_id)
return utils.serializer(graphs.get_graph_version_by_id(request, version_id))

@is_authenticated()
def _add_graph_version(request, graph_id, graph_version={}):
"""
Node Parameters
----------
name : string
Name of the node. Required
owner_email : string
Email of the Owner of the graph. Required
graph_id : string
Unique ID of the graph. Required


Parameters
----------
graph_json : dict
Dictionary containing the graph_json data of the graph being added.
request : object
HTTP POST Request.

Returns
-------
graph_version : object
Newly created graph_version object.

Raises
------

Notes
------

"""
#

return utils.serializer(graphs.add_graph_version(request,
name=graph_version.get('name', None),
description=graph_version.get('description', None),
owner_email=graph_version.get('owner_email', None),
graph_json=graph_version.get('graph_json', None),
graph_id=graph_id))

@is_authenticated()
def _delete_graph_version(request, graph_id, graph_version_id):
"""

Parameters
----------
request : object
HTTP GET Request.
graph_version_id : string
Unique ID of the Graph Version.

Returns
-------
None

Raises
------

Notes
------

"""
#authorization.validate(request, permission='GRAPH_UPDATE', graph_id=graph_id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we not validating auth for delete operation?


graphs.delete_graph_version_by_id(request, graph_version_id)
Loading