-
Notifications
You must be signed in to change notification settings - Fork 13
feat: change generate SVG function #88
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
Changes from all commits
57b2515
4714519
66230e6
5978c6f
cb8b77b
6e8b69f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| /node_modules | ||
| /.pnp | ||
| .pnp.js | ||
| /api-go/tools/puppeteer/node_modules | ||
|
|
||
| # testing | ||
| /coverage | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ package main | |
|
|
||
| import ( | ||
| "context" | ||
| "encoding/base64" | ||
| "encoding/json" | ||
| "fmt" | ||
| "log" | ||
|
|
@@ -130,11 +131,28 @@ func getContributorSVG(w http.ResponseWriter, r *http.Request) { | |
| return | ||
| } | ||
| } | ||
| w.Header().Add("content-type", "image/svg+xml;charset=utf-8") | ||
| w.Header().Add("cache-control", "public, max-age=86400") | ||
|
|
||
| svg = strings.Replace(svg, "%", "%%", -1) | ||
| fmt.Fprintf(w, svg) | ||
| if strings.Contains(svg, "svg") { | ||
| w.Header().Add("content-type", "image/svg+xml;charset=utf-8") | ||
| w.Header().Add("cache-control", "public, max-age=86400") | ||
|
|
||
| svg = strings.Replace(svg, "%", "%%", -1) | ||
| fmt.Fprintf(w, svg) | ||
| } else { | ||
| w.Header().Add("content-type", "image/png") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we choose svg but not png before is because svg is relative small (at least 5 times smaller I believe).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can only produce
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe Echarts support both
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| w.Header().Add("cache-control", "public, max-age=86400") | ||
|
|
||
| base64String := strings.Split(svg, "data:image/png;base64,")[1] | ||
| buffer, err := base64.StdEncoding.DecodeString(base64String) | ||
|
|
||
| if err != nil { | ||
| w.WriteHeader(http.StatusInternalServerError) | ||
| json.NewEncoder(w).Encode(err.Error()) | ||
| return | ||
| } | ||
|
|
||
| w.Write(buffer) | ||
| } | ||
| } | ||
|
|
||
| func getRepos(w http.ResponseWriter, r *http.Request) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,6 @@ import ( | |
| "io" | ||
| "io/ioutil" | ||
| "net/http" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "cloud.google.com/go/datastore" | ||
|
|
@@ -29,7 +27,7 @@ func GenerateAndSaveSVG(ctx context.Context, repo string, merge bool, chartType | |
| } | ||
| defer client.Close() | ||
|
|
||
| graphFunctionUrl := "https://cloudfunction.contributor-graph.com/svg?repo=" + repo | ||
| graphFunctionUrl := "https://asia-east2-api7-301102.cloudfunctions.net/png" + repo | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the original url, which is actually the local balancer URL of the cloud function which adds google cloud armor
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t know much about Google Cloud, I just want to point it to my newly deployed function.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you could bind your new function to the load balancer and there are docs talking about it |
||
| if merge { | ||
| graphFunctionUrl += "&merge=true" | ||
| } | ||
|
|
@@ -81,7 +79,7 @@ func GenerateAndSaveSVG(ctx context.Context, repo string, merge bool, chartType | |
|
|
||
| wc := client.Bucket(bucket).Object(object).NewWriter(ctx) | ||
| wc.CacheControl = "public, max-age=86400" | ||
| wc.ContentType = "image/svg+xml;charset=utf-8" | ||
| wc.ContentType = "image/png" | ||
|
|
||
| if _, err = io.Copy(wc, bytes.NewReader(svg)); err != nil { | ||
| return "", fmt.Errorf("upload svg failed: io.Copy: %v", err) | ||
|
|
@@ -161,58 +159,58 @@ func SubGetSVG(w http.ResponseWriter, repo string, merge bool, charType string) | |
| // we need to also tell if the graph is ready to use on this side. | ||
| // Try to get the endpoint of the line drawn and tell if it's on the right-most side | ||
| func svgSucceed(svgBytes []byte) ([]byte, error) { | ||
| svg := string(svgBytes[:]) | ||
| lines := strings.Split(svg, "\n") | ||
| var svgWidth float64 | ||
| for _, l := range lines { | ||
| if strings.Contains(l, "<rect") { | ||
| words := strings.Split(l, " ") | ||
| for _, w := range words { | ||
| if strings.Contains(w, "width") { | ||
| parts := strings.Split(w, `"`) | ||
| var err error | ||
| svgWidth, err = strconv.ParseFloat(parts[1], 64) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| break | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if svgWidth == 0 { | ||
| return nil, fmt.Errorf("could not get svg width") | ||
| } | ||
| lineColor := "39a85a" | ||
| for i, l := range lines { | ||
| if strings.Contains(l, lineColor) { | ||
| lineDrawn := strings.Split(strings.Split(l, `"`)[1], " ") | ||
| endPointX, err := strconv.ParseFloat(lineDrawn[len(lineDrawn)-2], 64) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| if float64(endPointX) < 0.95*float64(svgWidth) { | ||
| return nil, fmt.Errorf("the line is not reach its end") | ||
| } | ||
| break | ||
| } | ||
| if i == len(lines)-1 { | ||
| return nil, fmt.Errorf("could not get endpoint") | ||
| } | ||
| } | ||
| renderLengthMarker := "<path" | ||
| for i := len(lines) - 1; i >= 0; i-- { | ||
| if strings.Contains(lines[i], renderLengthMarker) { | ||
| words := strings.Split(lines[i], " ") | ||
| svgWidthStr := fmt.Sprintf("%f", svgWidth) | ||
| for j := range words { | ||
| if words[j] == "L" && j+1 < len(words) && words[j+1] != svgWidthStr { | ||
| lines[i] = strings.ReplaceAll(lines[i], words[j+1], svgWidthStr) | ||
| break | ||
| } | ||
| } | ||
| return []byte(strings.Join(lines, "\n")), nil | ||
| } | ||
| } | ||
| // svg := string(svgBytes[:]) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we would like to remove this function, we could directly remove it and use git to look for history version |
||
| // lines := strings.Split(svg, "\n") | ||
| // var svgWidth float64 | ||
| // for _, l := range lines { | ||
| // if strings.Contains(l, "<rect") { | ||
| // words := strings.Split(l, " ") | ||
| // for _, w := range words { | ||
| // if strings.Contains(w, "width") { | ||
| // parts := strings.Split(w, `"`) | ||
| // var err error | ||
| // svgWidth, err = strconv.ParseFloat(parts[1], 64) | ||
| // if err != nil { | ||
| // return nil, err | ||
| // } | ||
| // break | ||
| // } | ||
| // } | ||
| // } | ||
| // } | ||
| // if svgWidth == 0 { | ||
| // return nil, fmt.Errorf("could not get svg width") | ||
| // } | ||
| // lineColor := "39a85a" | ||
| // for i, l := range lines { | ||
| // if strings.Contains(l, lineColor) { | ||
| // lineDrawn := strings.Split(strings.Split(l, `"`)[1], " ") | ||
| // endPointX, err := strconv.ParseFloat(lineDrawn[len(lineDrawn)-2], 64) | ||
| // if err != nil { | ||
| // return nil, err | ||
| // } | ||
| // if float64(endPointX) < 0.95*float64(svgWidth) { | ||
| // return nil, fmt.Errorf("the line is not reach its end") | ||
| // } | ||
| // break | ||
| // } | ||
| // if i == len(lines)-1 { | ||
| // return nil, fmt.Errorf("could not get endpoint") | ||
| // } | ||
| // } | ||
| // renderLengthMarker := "<path" | ||
| // for i := len(lines) - 1; i >= 0; i-- { | ||
| // if strings.Contains(lines[i], renderLengthMarker) { | ||
| // words := strings.Split(lines[i], " ") | ||
| // svgWidthStr := fmt.Sprintf("%f", svgWidth) | ||
| // for j := range words { | ||
| // if words[j] == "L" && j+1 < len(words) && words[j+1] != svgWidthStr { | ||
| // lines[i] = strings.ReplaceAll(lines[i], words[j+1], svgWidthStr) | ||
| // break | ||
| // } | ||
| // } | ||
| // return []byte(strings.Join(lines, "\n")), nil | ||
| // } | ||
| // } | ||
| return svgBytes, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # Google Clould Function | ||
|
|
||
| Google Clould Function used to generate contributor statistics picture. | ||
|
|
||
| ## API | ||
|
|
||
| The entry point of the function is `png` in `index.js`. | ||
|
|
||
| | Parameter | Required | Type | Description | Example | | ||
| | ---- | ---- | ---- | ---- | ---- | | ||
| | repo | true | string | The name of repository | apache/apisix,apache/skywalking | | ||
| | merge | false | boolean | Whether to view all repos related to this repo, when chart is `contributorMonthlyActivity`, can not be set true | true | | ||
| | chart | false | contributorOverTime contributorMonthlyActivity | chart type | | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. something goes wrong here |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| const moment = require('moment'); | ||
| const axios = require('axios'); | ||
|
|
||
| const isSameDay = (d1, d2) => { | ||
| return ( | ||
| d1.getFullYear() === d2.getFullYear() && | ||
| d1.getMonth() === d2.getMonth() && | ||
| d1.getDate() === d2.getDate() | ||
| ); | ||
| }; | ||
|
|
||
| const fetchContributorsData = (repo) => { | ||
| if (repo === "null" || repo === null) { | ||
| repo = "apache/apisix"; | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| axios.get( | ||
| `https://contributor-overtime-api.apiseven.com/contributors?repo=${repo}` | ||
| ).then(response => { | ||
| return response.data; | ||
| }).then(data => { | ||
| const { Contributors = [] } = data; | ||
| const sortContributors = Contributors.map(item => ({ | ||
| ...item, | ||
| date: item.date.substring(0, 10) | ||
| })).sort( | ||
| (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() | ||
| ); | ||
| if ( | ||
| !isSameDay( | ||
| new Date(sortContributors[sortContributors.length - 1].date), | ||
| new Date() | ||
| ) | ||
| ) { | ||
| sortContributors.push({ | ||
| repo, | ||
| idx: sortContributors[sortContributors.length - 1].idx, | ||
| date: moment(new Date()).format("YYYY-MM-DD") | ||
| }); | ||
| }; | ||
|
|
||
| const processContributors = []; | ||
| sortContributors.forEach((item, index) => { | ||
| processContributors.push(item); | ||
|
|
||
| if (index !== sortContributors.length - 1) { | ||
| const diffDays = moment(sortContributors[index + 1].date).diff( | ||
| item.date, | ||
| "days" | ||
| ); | ||
| if (diffDays > 1) { | ||
| for (let index = 1; index < diffDays; index++) { | ||
| processContributors.push({ | ||
| ...item, | ||
| date: moment(item.date) | ||
| .add(index, "days") | ||
| .format() | ||
| .substring(0, 10) | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| const filterData = processContributors.filter( | ||
| (item, index) => | ||
| index === 0 || | ||
| index === processContributors.length - 1 || | ||
| new Date(item.date).getDate() % 10 === 5 | ||
| ); | ||
|
|
||
| resolve({ repo, ...{ Contributors: filterData } }); | ||
| }).catch(error => { | ||
| reject(error); | ||
| }) | ||
| }) | ||
| }; | ||
|
|
||
| const fetchMonthlyData = (repo) => { | ||
| if (repo === "null" || repo === null) { | ||
| repo = "apache/apisix"; | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| axios.get( | ||
| `https://contributor-overtime-api.apiseven.com/monthly-contributor?repo=${repo}` | ||
| ) | ||
| .then(response => { | ||
| return response.data; | ||
| }) | ||
| .then(myJson => { | ||
| resolve({ repo, ...myJson }); | ||
| }) | ||
| .catch(e => { | ||
| reject(e); | ||
| }); | ||
| }); | ||
| }; | ||
|
|
||
| const fetchMergeContributor = (repo) => { | ||
| return new Promise((resolve, reject) => { | ||
| axios.get( | ||
| `https://contributor-overtime-api.apiseven.com/contributors-multi?repo=${repo.join( | ||
| "," | ||
| )}` | ||
| ) | ||
| .then(response => { | ||
| return response.data; | ||
| }) | ||
| .then(myJson => { | ||
| console.log('myJson: ', myJson); | ||
| resolve({ repo, ...myJson }); | ||
| }) | ||
| .catch(e => { | ||
| reject(e); | ||
| }); | ||
| }); | ||
| }; | ||
|
|
||
| module.exports = { | ||
| fetchContributorsData, | ||
| fetchMonthlyData, | ||
| fetchMergeContributor, | ||
| } |

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since normal contributors could not get access by default, so maybe we could move it to a separate file and leave a link here, so not all people need to see it on README.
Also maybe we could also add guidance on how to get access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, we do need a guide to tell a new developer how to start this project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it's pretty complicated, since we deploy it on something we pay for, we need to find the way to give enough permission for users to contribute, but also avoid people abusing it