GithubHelp home page GithubHelp logo

python-rtkit's People

Contributors

2xyo avatar bitdeli-chef avatar ctrlspc avatar dbravender avatar dehnert avatar jenner avatar miramir avatar pedromorgan avatar sim0nx avatar t0masd avatar z4r avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-rtkit's Issues

Feature: RTResponse should be more pythonic

It should be possible to get a response in dictionary form. List of tuples is a good starting point, but you usually want to access just one field real quick.

Dates returned in an RTResponse should be pre-parsed to datetime objects, instead of returned as strings. (There might be some locale issues here)

This will simplify use of the library.

Implementation wise it should be an easy fix in rtkit/resource.py's RTResponse object, but I'm not sure how it best is made visible interface wise.

I'm happy to provide a patches if these changes sound acceptable.

Comment API with attachment

Hello, it seems there are some issues with the comment API with Attachment. I am able to send .txt file but nothing else. Not even .JPG. I keep getting [ERROR] 'ascii' codec can't decode byte 0xef in position 407: ordinal not in range(128). Here is my code:

file_open = file(full_filename)
content = {

                "content": {
                    'id': _ticket,
                    'Action': 'comment',
                    'Text': comment_text,
                    'Attachment': uploaded_filename
                },
                'attachment_1': file_open
            }

resource = RTResource(uri, username, password,
CookieAuthenticator)

response = resource.post(path='ticket/' + _ticket + '/comment', payload=content, )

Any suggestion?

create ticket having RESOURCE_STATUS: 401 Credentials required

Hello ,

I'm new here . I searched for the similar post, but I don't find the answer .

I'm having trouble with a simple script to create a single ticket and every time I get
[DEBUG] RESOURCE_STATUS: 401 Credentials required

The code in my script is:

#!/usr/bin/python -u
from rtkit.resource import RTResource
from rtkit.errors import RTResourceError
from rtkit.authenticators import BasicAuthenticator


from rtkit import set_logging
import logging


set_logging('debug')
logger = logging.getLogger('rtkit')
resource = RTResource('http://ticket.corp.kk.net/rt3/REST/1.0/', 'user', 'password', BasicAuthenticator)
#create a ticket
content = {
    'content': {
        'Queue': '[email protected]', #General - unassigned is the name of the desired queue
        'Subject' : 'Test Ticket creation in RT with Python', #text to go into subject field
        }
    }
try:
    response = resource.post(path='ticket/new', payload=content,)
    logger.info(response.parsed)
except RTResourceError as e:
    logger.error(e.response.status_int)
    logger.error(e.response.status)
    logger.error(e.response.parsed)

The trace is as below:

[DEBUG] POST ticket/new
[DEBUG] {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Accept': 'text/plain'}
[DEBUG] 'content=Queue: [email protected]\nSubject: Test Ticket creation in RT with Python'
[INFO] POST
[INFO] http://ticket.corp.kk.net/rt3/REST/1.0/ticket/new
[DEBUG] HTTP_STATUS: 200 OK
[DEBUG] 'RT/3.8.13 401 Credentials required\n'
[DEBUG] RESOURCE_STATUS: 401 Credentials required
[INFO] []
[INFO] []

Could someone help me for this ? Thanks a lot .

IndexError while trying to use python-rtkit

Hi, first of all, thanks for posting your code.

I'm trying to make use of it with the following,
however an exception occurs.

from rt_config import rt_base_url, username, password
from rtkit.resource import RTResource
from rtkit.authenticators import BasicAuthenticator
from rtkit.errors import RTResourceError

from rtkit import set_logging
import logging
set_logging('debug')
logger = logging.getLogger('rtkit')

rest_base_url=rt_base_url+'/REST/1.0/'
resource = RTResource(rest_base_url, username, password, BasicAuthenticator)

try:
response = resource.getpath='ticket/3750/show')
for r in response.parsed:
for t in r:
logger.info(t)
except RTResourceError as e:
logger.error(e.response.status_int)
logger.error(e.response.status)
logger.error(e.response.parsed)

end of code

I get the following output and traceback (I edited my server url):

[DEBUG] GET ticket/3750/show
[DEBUG] {'Accept': 'text/plain'}
[DEBUG] None
[INFO] GET
[INFO] https://<my_server>/rt/REST/1.0/ticket/3750/show
[DEBUG] HTTP_STATUS: 200 OK
[DEBUG] 'RT/3.8.8 401 Credentials required\n'
Traceback (most recent call last):
File "test_rt.py", line 16, in
response = resource.get(path='ticket/3750/show')
File "/opt/epd/lib/python2.7/site-packages/python_rtkit-0.2.4-py2.7.egg/rtkit/resource.py", line 15, in get
return self.request('GET', path, headers=headers)
File "/opt/epd/lib/python2.7/site-packages/python_rtkit-0.2.4-py2.7.egg/rtkit/resource.py", line 37, in request
return self.response_cls(req, response)
File "/opt/epd/lib/python2.7/site-packages/python_rtkit-0.2.4-py2.7.egg/rtkit/resource.py", line 63, in init
self.parsed = RTParser.parse(self.body, decoder)
File "/opt/epd/lib/python2.7/site-packages/python_rtkit-0.2.4-py2.7.egg/rtkit/parser.py", line 39, in parse
comment.check(section[0])
File "/opt/epd/lib/python2.7/site-packages/python_rtkit-0.2.4-py2.7.egg/rtkit/comment.py", line 87, in check
_incheck(section, e)
File "/opt/epd/lib/python2.7/site-packages/python_rtkit-0.2.4-py2.7.egg/rtkit/comment.py", line 83, in _incheck
m = e[0].match(section[0])
IndexError: list index out of range

Check login details

Is it possible to add some kind of check if login details are ok? Just check if user is correctly logged in RT and return True / False?

Plans for supporting REST API 2.0?

Hello,

I'm wondering if folks working on this codebase have the intention (or maybe simply the interest) of eventually adding support for version 2.0 of RT's REST API?

see: https://metacpan.org/release/RT-Extension-REST2

I'm asking since version 2.0 returns values in a format that's a lot easier to use with a script than the API version 1.0. For example:

  • returned values are formatted in JSON which is way easier to parse
  • values are not shown as the localized string that would be printed in the web interface for the user.
    ** as an example for this, in version 1.0 of the API, with RT set to a canadian french locale, when I request info about a ticket I see LastUpdated: Jeu 07 Mai 2020 14:06:41 whereas version 2.0 of the API shows: LastUpdated: "2020-05-07T18:06:41Z" the latter is way easier to parse with a script and stays consistant no matter the user's locale.

If there's no plan but an interest to have it, then maybe I can try and work on it in the next few weeks.

Cheers!

AttributeError: 'HTTPError' object has no attribute 'headers'

(kanbanizesync)guandalf@earth:~/Workspace/kanbanizesync/src$ python kize2rt.py
[DEBUG] POST search/ticket?orderby=%2Bid&query=%28Status+%3D+%27open%27+OR+Status+%3D+%27new%27+OR+Status+%3D+%27stalled%27%29+AND+%28Queue+%3D+%27qaupdate%27%29&format=s
[DEBUG] {'Accept': 'text/plain'}
[DEBUG] None
Traceback (most recent call last):
File "kize2rt.py", line 35, in
rt_all_tickets = rt.post(path='search/ticket?%s' % params)
File "/home/guandalf/Virtualenv/kanbanizesync/local/lib/python2.7/site-packages/rtkit/resource.py", line 20, in post
return self.request('POST', path, payload, headers)
File "/home/guandalf/Virtualenv/kanbanizesync/local/lib/python2.7/site-packages/rtkit/resource.py", line 39, in request
return self.response_cls(req, response)
File "/home/guandalf/Virtualenv/kanbanizesync/local/lib/python2.7/site-packages/rtkit/resource.py", line 48, in init
self.headers = response.headers
AttributeError: 'HTTPError' object has no attribute 'headers'

Betrayed by urllib2? Try "requests" :)

RFC5322-like parsing failure

The issue is In resource.py function _build.

Some of the ticket returned by RT have two dashes in the subject. When splitting based on -- these are being categorized as as new sections. This can also lead to exceptions being thrown in build_section when the area after -- has a space but logic_lines is empty.

E.G.

123: Data returned by RT -- has two dashes...

The solution I'm using is use a re to create the split. I've changed the last line in the file to:

return [build_section(b) for b in re.split('(?mu)^\s*--', body)]

This ensures that the --'s start the line with white space removed. multi-line is required to have ^ match at the beginning of the line but u probably isn't necessary.

Unable to create new ticket

Using RT 4.2 with RTIR. Still getting a blank Queue when submitting a new ticket. Searching for a ticket gets the correct result. Not sure what i'm doing wrong here. Code follows result block.

[DEBUG] POST ticket/new
[DEBUG] {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Accept': 'text/plain'}
[DEBUG] 'content=Queue: IST-Security\nText: My+useless%0A+text+on%0A+three+lines.\nSubject: New Ticket Test'
[INFO] POST
[INFO] http://rt.uwaterloo.ca/REST/1.0/ticket/new
[DEBUG] HTTP_STATUS: 200 OK
[DEBUG] 'RT/4.2.9-43-gd7c0cfd 200 Ok\n\n# Required: id, Queue\n\nid: ticket/new\nQueue: \nRequestor: issminion\nSubject: \nCc:\nAdminCc:\nOwner: \nStatus: new\nPriority: \nInitialPriority: \nFinalPriority: \nTimeEstimated: 0\nStarts: 2014-12-02 10:23:19\nDue: \nAttachment: \nText: \n\n'
[DEBUG] RESOURCE_STATUS: 200 Ok
[INFO] [[('id', 'ticket/new'), ('Queue', ''), ('Requestor', 'issminion'), ('Subject', ''), ('Cc', ''), ('AdminCc', ''), ('Owner', ''), ('Status', 'new'), ('Priority', ''), ('InitialPriority', ''), ('FinalPriority', ''), ('TimeEstimated', '0'), ('Starts', '2014-12-02 10:23:19'), ('Due', ''), ('Attachment', ''), ('Text', '')]]
[INFO] [[('id', 'ticket/new'), ('Queue', ''), ('Requestor', 'issminion'), ('Subject', ''), ('Cc', ''), ('AdminCc', ''), ('Owner', ''), ('Status', 'new'), ('Priority', ''), ('InitialPriority', ''), ('FinalPriority', ''), ('TimeEstimated', '0'), ('Starts', '2014-12-02 10:23:19'), ('Due', ''), ('Attachment', ''), ('Text', '')]]

!/usr/bin/env python

import sys
import re
import smtplib
import subprocess
import os
import rt
import ConfigParser
import time
from rtkit.resource import RTResource
from rtkit.authenticators import QueryStringAuthenticator
from rtkit.errors import RTResourceError

from rtkit import set_logging
import logging
set_logging('debug')
logger = logging.getLogger('rtkit')

config = ConfigParser.RawConfigParser()
config.read(os.path.expanduser('~/.rtrcp'))

notkit rtpoint = "https://{0}/REST/1.0/".format(config.get('rt','hostname'))

rtuser = config.get('rt','username')
rtpass = config.get('rt','password')
rthost = config.get('rt','hostname')

resource = RTResource('http://{0}/REST/1.0/', '{1}', '{2}', BasicAuthenticator).format(rthost,rtuser,rtpass)

try:
resource = RTResource('http://rt.uwaterloo.ca/REST/1.0/', rtuser, rtpass, QueryStringAuthenticator)

except:
print rtuser, rtpass, rthost
print "Oops."
sys.exit(0)

content = {
'content': {
'Queue': 'IST-Security',
'Subject': 'New Ticket Test',
'Text': 'My useless\ntext on\nthree lines.',
}
}
try:
response = resource.post(path='ticket/new', payload=content,)
logger.info(response.parsed)
except RTResourceError as e:
logger.error(e.response.status_int)
logger.error(e.response.status)
logger.error(e.response.parsed)

Binary file attachments are mangled

I can get text attachments to work just fine. I used the Comment on a Ticket with Attachments instructions from here, but the ascii text file uploads just fine while RT shows the binary file that I attached as mimetype text/plain, at about double the original size of the file. I even tried using binary mode in my call to file(), like so:

file(filename, 'rb')

However, the result was the same.

trouble authenticating to RT server

I took the demo code posted here to try and create a ticket in a simple python script.

from rtkit.resource import RTResource
from rtkit.authenticators import BasicAuthenticator
from rtkit.errors import RTResourceError

from rtkit import set_logging
import logging
set_logging('debug')
logger = logging.getLogger('rtkit')

resource = RTResource('http://rt.example.com/REST/1.0/, 'user', 'pass', BasicAuthenticator)

#create a ticket
content = {
    'content': {
        'Queue': 'General - unassigned',
        'Subject' : 'Test Ticket Python',
        }
    }
try:
    response = resource.post(path='ticket/new', payload=content,)
    logger.info(response.parsed)
except RTResourceError as e:
    logger.error(e.response.status_int)
    logger.error(e.response.status)
    logger.error(e.response.parsed)

This returns the following output:

[DEBUG] POST ticket/new
[DEBUG] {'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Accept': 'text/plain'}
[DEBUG] 'content=Queue: General - unassigned\nSubject: Test Ticket Python'
[INFO] POST
[INFO] http://rt.example.com/REST/1.0/ticket/new
[DEBUG] HTTP_STATUS: 200 OK
[DEBUG] 'RT/3.6.6 401 Credentials Required\n'
[DEBUG] RESOURCE_STATUS: 401 Credentials Required
[INFO] []
[INFO] []

I've tried Cookie based authentication as well but after that I can't think of anthing to try. Do you know why this is failing? Or have any ideas of what else I can try?

urlencoded text in comment body

Hi,

since the update to 0.6.1 I have the problem that the text in the body of responses and comments is still urlencoded in RT. It seems that it does not get urldecoded by RT. I'm testing with RT Version 4.0.7. What do I do wrong?

Edit API does not save custom fields with comma

We tried to convert the comma to '%2C' Hex before sending it to RT. However, RT returns an error as such:
Can't use an undefined value as an ARRAY reference at /opt/rt4/share/html/REST/1.0/dhandler line 189.

Stack:
[/opt/rt4/share/html/REST/1.0/dhandler:189]
[/opt/rt4/share/html/REST/1.0/autohandler:54]
[/opt/rt4/sbin/../lib/RT/Interface/Web.pm:696]
[/opt/rt4/sbin/../lib/RT/Interface/Web.pm:375]
[/opt/rt4/share/html/autohandler:53]

Other special characters seem to be working with edit.

search all resolved tickets

Hi ,

How can I get a list of those tickets resolved by me ( my username ) , for a list of users/requesters?

thanks

OO Style

I;ve just pushed a little patch to a branch which I'm using..

The idea is that it casts what returned into an Object then little helper makes it into a dict, custom fields etc.

https://rtkit.readthedocs.org/en/latest/resource.html
https://github.com/daffodil/python-rtkit/blob/pedro/rtkit/resource.py

I'm pretty new to RT Rest API, but one thing I dont want to have to do is learn the syntax of fields all over the places..

For exampel I Just want to create a new Ticket
add a few values and just send it..

It looks like the entities is the way to do this, but for my purposed I would love..

tick = Ticket()
tick.Subject = "foo"
tick.Text = "bar"
tick.Queue = "HelpDesk"
tick.DateRequired = datetime.datetime.now()
tick.set_custom("X-ref", myId)
# conn
trackerObject = Tracker("server", USER, PASS, CookieAuthenticator)
success = tracker.new_ticket(tick)

Also I'm using dict's all over the place, from/to json etc.

The only snag I have with the entities is that they are not the same Case. eg self.Subject vs self.subject// and not sure that is a good idea..
And There is no simple way to simple create a "blank" Ticket or Comment" object ready to populate with data

any some thoughts
Pedro

Provide a list for a custom field

Hi,

I have list of IP as custom field. The adding of one IP works fine but adding a list of IP fails. I have tried with the following syntax :

"CF.{IP src}":  "1.1.1.1\n1.1.1.2"
"CF.{IP src}":  "1.1.1.1, 1.1.1.2"
"CF.{IP src}":  ["1.1.1.1","1.1.1.2"]
"CustomField-8":  "1.1.1.1\n1.1.1.2"
"CustomField-8":  ["1.1.1.1","1.1.1.2"]

Any idea ?

Update : I think it's to the bug #99520.

ValueError: not enough values to unpack (expected 2, got 1)

After migrating an app to python 3 and installing the latest version of python-rtkit, I'm suddenly getting errors when trying to create a new ticket:

Traceback (most recent call last):
  File "/home/aaron/code/uitintranet/cw/tasks.py", line 732, in sync_cw_ticket_to_rt
    response = resource.post(path='ticket/new', payload=str(content))
  File "/home/aaron/.virtualenvs/uitintranet/lib/python3.5/site-packages/rtkit/resource.py", line 33, in post
    return self.request('POST', path, payload, headers)
  File "/home/aaron/.virtualenvs/uitintranet/lib/python3.5/site-packages/rtkit/resource.py", line 40, in request
    payload = forms.encode(payload, headers)
  File "/home/aaron/.virtualenvs/uitintranet/lib/python3.5/site-packages/rtkit/forms.py", line 138, in encode
    mf = MultipartForm(value, BOUNDARY)
  File "/home/aaron/.virtualenvs/uitintranet/lib/python3.5/site-packages/rtkit/forms.py", line 22, in __init__
    name, value = param
ValueError: not enough values to unpack (expected 2, got 1)

Can't create links between tickets

I'm trying to use your module to create a ticket and then link it to a previous ticket. I can successfully create the ticket, but cannot create a link to a specific previous ticket. When I go to create the link, instead of linking the new ticket to the specified ticket, it creates a link to the first RT ticket created (ticket id = 1). Below is the code I'm using to create the link:

# Convert args to strings
child = 23
child = str(child)
parent = 20
parent = str(parent)
link_url = 'ticket/' + child + '/links'

# Create content for ticket payload
content = ''
content = { 
    'content': {
        'DependedOnBy': parent,
    }   
}   

# Connect to RT
resource = RTResource(rt_url, rt_user, rt_pass, CookieAuthenticator)

# Try to link tickets
try:
    response = resource.post(path=link_url, payload=content)
    logger.info(response.parsed)

except RTResourceError as e:
    logger.error(e.response.status_int)
    logger.error(e.response.status)
    logger.error(e.response.parsed)

Getting ValueError: need more than 1 value to unpack

I have code in which I'm attempting to create a single RT ticket as follows:

from rtkit.resource import RTResource
from rtkit.authenticators import BasicAuthenticator, CookieAuthenticator
from rtkit.errors import RTResourceError

from rtkit import set_logging
import logging
set_logging('debug')
logger = logging.getLogger('rtkit')

resource = RTResource('https://rt.example.com/REST/1.0/', 'user', 'pass, CookieAuthenticator)

#create a ticket
content = {
    'content': {
        'Queue': 'Vulnerabilities',
        'Subject' : 'Test Vulnerability for Script Dev',
        'Text' : 'I\'m a thing',
        'CF-Priority' : 'Low -Needs to be completed in > 30',
        'CF-Device or Area' : 'Agile Arrow',
        'CF-Time Estimated' : 'Days',
        #'CF-Is This Maintenance?' : 'Not_maintenance',
        #'CF-Type of change/risk' : 'Non-Prod Change/Notes (requires post-approval)',
        'CF-CVSS Score' : '2.6',
        #'CF-Approved?' : 'No',
        'CF-Vulnerability Risk' : 'Low',
        'CF-Discovered By (Company)' : 'MyCompany',
        #'CF-Discovered By (Program)' : 'MyProgram',
        }
    }
try:
    print content
    response = resource.post(path='ticket/new', payload=content,)
    #response = resource.post(path='ticket/4862')
    logger.info(response.parsed)
except RTResourceError as e:
    logger.error(e.response.status_int)
    logger.error(e.response.status)
    logger.error(e.response.parsed)

All the commented fields in the content dict don't work. If I submit that request I get the following error message:

Traceback (most recent call last):
  File "resttest.py", line 33, in <module>
    response = resource.post(path='ticket/new', payload=content,)
  File "build\bdist.win32\egg\rtkit\resource.py", line 29, in post
  File "build\bdist.win32\egg\rtkit\resource.py", line 49, in request
  File "build\bdist.win32\egg\rtkit\resource.py", line 104, in __init__
  File "build\bdist.win32\egg\rtkit\parser.py", line 49, in parse
  File "build\bdist.win32\egg\rtkit\parser.py", line 79, in decode_comment
ValueError: need more than 1 value to unpack

Additionally, I get this error if I omit the CF- at the beginning of any the fields that work otherwise. However, other mispellings of the field name (ie omitting a ?) I get the error for an invalid field name. I'd try and put a print line in decode_comment to show what's in the lines variable that being parsed and throwing the error but it doesn't seem to work.Do you know why this problem happens and how to fix it?

feature request (mixed auth)

Hi Guys,

I work in an environment where our initial login to RT is using kerberos.
After that, we use cookie based authentication.
Would it be possible for your to support a feature like this?

multipart/form-data

The rtkit doesn't seems to accept multipart/form-data. I notice it specifically when I send special characters like *, & and etc. In our scenario those values are coming right out of a form that I am trying to pass to RT. However, custom fields that have special characters are not making it through to RT. RT developers are suggesting that the rtkit needs to be refactored. Any help will be appreciated.

SyntaxError: invalid syntax

Hi,

When running setup.py, I get the following error:

  File "/usr/lib/python3.2/site-packages/rtkit/errors.py", line 34
    except (NameError, ValueError, KeyError), e:
                                            ^
SyntaxError: invalid syntax

This is on 64-bit Arch Linux with Python 3.2.3 (which is the default version on Arch).

I assume you support both Python 2 and Python 3, since there is no mention of either.

Best regards,
Alexander Rødseth

Bypass SSL checking

Hi,

I'm trying to automate ticket creations but the RT server has a self signed SSL which is raising:
urllib2.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)>

Is there any way we could get a toggle for SSL checking?

Parsing Removes Newlines in Comment Field

Hi,

I just started using this API. It's awesome thx! :-)

I noticed on line 111 in parser.py :

if line[0].isspace():
     logic_lines[-1] += ' ' + line.strip(' ')

if I change line 112 to make it '\n' instead of ' ', so :

if line[0].isspace()
     logic_lines[-1] += '\n' + line.strip(' ')

It correctly keeps the newlines that are in the comment field of a ticket. i.e. The email correspondence.

Is this something I'm doing wrong, or does it need to change to '\n' as in my example?

Regards,
Nevar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.